11import {
22 booleanValueNode ,
3+ BytesTypeNode ,
4+ bytesTypeNode ,
35 bytesValueNode ,
6+ fixedSizeTransformNode ,
7+ floatValueNode ,
8+ integerTypeNode ,
9+ integerValueNode ,
410 isNode ,
5- numberValueNode ,
611 publicKeyValueNode ,
12+ sizePrefixTransformNode ,
13+ StringTypeNode ,
714 stringTypeNode ,
815 stringValueNode ,
916 TypeNode ,
@@ -14,36 +21,79 @@ export function hex(bytes: number[] | Uint8Array): string {
1421 return Array . from ( bytes ) . reduce ( ( str , byte ) => str + byte . toString ( 16 ) . padStart ( 2 , '0' ) , '' ) ;
1522}
1623
24+ /** Join the lines of Anchor docs into a single Markdown string, if any. */
25+ export function docsFromAnchor ( docs : string [ ] | undefined ) : string | undefined {
26+ return docs && docs . length > 0 ? docs . join ( '\n' ) : undefined ;
27+ }
28+
29+ /** A fixed-size byte array, e.g. for Anchor's 8-byte discriminators. */
30+ export function fixedSizeBytesTypeNode ( size : number ) : BytesTypeNode {
31+ return bytesTypeNode ( { transforms : [ fixedSizeTransformNode ( size ) ] } ) ;
32+ }
33+
34+ /** A Borsh string or byte array, prefixed by its `u32` size. */
35+ export function borshSizePrefixedTypeNode ( type : 'bytes' | 'string' ) : BytesTypeNode | StringTypeNode {
36+ const transforms = [ sizePrefixTransformNode ( integerTypeNode ( 'u32' ) ) ] ;
37+ return type === 'string' ? stringTypeNode ( 'utf8' , { transforms } ) : bytesTypeNode ( { transforms } ) ;
38+ }
39+
40+ /**
41+ * Remove the Borsh `u32` size prefix of a string or byte array, if any.
42+ *
43+ * Anchor uses unprefixed strings and byte arrays for PDA seeds even though
44+ * the corresponding arguments are Borsh size-prefixed.
45+ */
46+ export function removeBorshSizePrefix ( type : TypeNode ) : TypeNode {
47+ const isBorshSizePrefixed =
48+ ( isNode ( type , 'bytesTypeNode' ) || ( isNode ( type , 'stringTypeNode' ) && type . encoding === 'utf8' ) ) &&
49+ type . transforms ?. length === 1 &&
50+ isNode ( type . transforms [ 0 ] , 'sizePrefixTransformNode' ) &&
51+ type . transforms [ 0 ] . prefix . format === 'u32' ;
52+ if ( ! isBorshSizePrefixed ) return type ;
53+ return isNode ( type , 'stringTypeNode' ) ? stringTypeNode ( 'utf8' ) : bytesTypeNode ( ) ;
54+ }
55+
1756function isByteArray ( value : unknown ) : value is number [ ] {
1857 return Array . isArray ( value ) && value . every ( n => typeof n === 'number' && Number . isInteger ( n ) && n >= 0 && n <= 255 ) ;
1958}
2059
60+ /** The canonical decimal representation of a float string, if it can be written without an exponent. */
61+ function canonicalFloat ( valueString : string ) : string | undefined {
62+ if ( ! / ^ - ? \d + ( \. \d + ) ? $ / . test ( valueString ) ) return undefined ;
63+ const canonical = String ( Number ( valueString ) ) ;
64+ return canonical . includes ( 'e' ) ? undefined : canonical ;
65+ }
66+
2167export function parseConstantValue ( valueString : string , type : TypeNode ) : { type : TypeNode ; value : ValueNode } {
68+ const stringFallback = { type : stringTypeNode ( 'utf8' ) , value : stringValueNode ( valueString ) } ;
69+
2270 if ( isNode ( type , 'bytesTypeNode' ) ) {
2371 try {
2472 const parsed : unknown = JSON . parse ( valueString ) ;
2573 if ( isByteArray ( parsed ) ) {
2674 return { type, value : bytesValueNode ( 'base16' , hex ( new Uint8Array ( parsed ) ) ) } ;
2775 }
2876 } catch {
29- // fall through to the string fallback below
77+ // Not JSON: fall through to the string fallback below.
3078 }
31- return { type : stringTypeNode ( 'utf8' ) , value : stringValueNode ( valueString ) } ;
79+ return stringFallback ;
3280 }
3381
34- if ( isNode ( type , 'numberTypeNode' ) ) {
35- const isFloat = type . format === 'f32' || type . format === 'f64' ;
36- const numberRegex = isFloat ? / ^ - ? \d + ( \. \d + ) ? $ / : / ^ - ? \d + $ / ;
37- if ( numberRegex . test ( valueString ) ) {
38- return { type, value : numberValueNode ( Number ( valueString ) ) } ;
39- }
40- return { type : stringTypeNode ( 'utf8' ) , value : stringValueNode ( valueString ) } ;
82+ if ( isNode ( type , 'integerTypeNode' ) ) {
83+ // Integers are kept as strings, so 64- and 128-bit values stay lossless.
84+ if ( ! / ^ - ? \d + $ / . test ( valueString ) ) return stringFallback ;
85+ return { type, value : integerValueNode ( BigInt ( valueString ) . toString ( ) ) } ;
86+ }
87+
88+ if ( isNode ( type , 'floatTypeNode' ) ) {
89+ const canonical = canonicalFloat ( valueString ) ;
90+ return canonical === undefined ? stringFallback : { type, value : floatValueNode ( canonical ) } ;
4191 }
4292
4393 if ( isNode ( type , 'booleanTypeNode' ) ) {
4494 if ( valueString === 'true' ) return { type, value : booleanValueNode ( true ) } ;
4595 if ( valueString === 'false' ) return { type, value : booleanValueNode ( false ) } ;
46- return { type : stringTypeNode ( 'utf8' ) , value : stringValueNode ( valueString ) } ;
96+ return stringFallback ;
4797 }
4898
4999 if ( isNode ( type , 'publicKeyTypeNode' ) ) {
0 commit comments