22 CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING ,
33 CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED ,
44 CodamaError ,
5+ logWarn ,
56} from '@codama/errors' ;
67import {
78 accountValueNode ,
@@ -15,17 +16,22 @@ import {
1516 pdaSeedValueNode ,
1617 publicKeyTypeNode ,
1718 stringTypeNode ,
19+ TypeNode ,
1820 variablePdaSeedNode ,
1921} from '@codama/nodes' ;
2022import { getBase58Codec } from '@solana/codecs' ;
2123
22- import { IdlV01Seed } from './idl' ;
24+ import { IdlV01Field , IdlV01Seed , IdlV01TypeDef } from './idl' ;
25+ import { typeNodeFromAnchorV01 } from './typeNodes' ;
26+ import type { GenericsV01 } from './unwrapGenerics' ;
2327
2428export function pdaSeedNodeFromAnchorV01 (
2529 seed : IdlV01Seed ,
2630 instructionArguments : InstructionArgumentNode [ ] ,
2731 prefix ?: string ,
28- ) : Readonly < { definition : PdaSeedNode ; value ?: PdaSeedValueNode } > {
32+ idlTypes : IdlV01TypeDef [ ] = [ ] ,
33+ generics : GenericsV01 = { constArgs : { } , typeArgs : { } , types : { } } ,
34+ ) : Readonly < { definition : PdaSeedNode ; value ?: PdaSeedValueNode } > | undefined {
2935 const kind = seed . kind ;
3036
3137 switch ( kind ) {
@@ -34,40 +40,115 @@ export function pdaSeedNodeFromAnchorV01(
3440 definition : constantPdaSeedNodeFromBytes ( 'base58' , getBase58Codec ( ) . decode ( new Uint8Array ( seed . value ) ) ) ,
3541 } ;
3642 case 'account' : {
37- // Ignore nested paths.
38- const [ accountName ] = seed . path . split ( '.' ) ;
43+ const pathParts = seed . path . split ( '.' ) ;
44+ const [ accountName ] = pathParts ;
3945 const prefixedAccountName = prefix ? `${ prefix } _${ accountName } ` : accountName ;
46+
47+ if ( pathParts . length > 1 ) {
48+ const accountTypeName = seed . account ?? accountName ;
49+ const rootType = typeNodeFromAnchorV01 ( { defined : { name : accountTypeName } } , generics ) ;
50+ const resolved = resolveNestedFieldType ( rootType , pathParts . slice ( 1 ) , idlTypes , generics ) ;
51+ if ( ! resolved ) {
52+ logWarn ( `Could not resolve nested account path "${ seed . path } " for PDA seed.` ) ;
53+ return undefined ;
54+ }
55+ const combinedName = camelCase ( `${ prefixedAccountName } _${ pathParts [ pathParts . length - 1 ] } ` ) ;
56+ return {
57+ definition : variablePdaSeedNode ( combinedName , resolved ) ,
58+ value : pdaSeedValueNode ( combinedName , accountValueNode ( prefixedAccountName ) ) ,
59+ } ;
60+ }
61+
4062 return {
4163 definition : variablePdaSeedNode ( prefixedAccountName , publicKeyTypeNode ( ) ) ,
4264 value : pdaSeedValueNode ( prefixedAccountName , accountValueNode ( prefixedAccountName ) ) ,
4365 } ;
4466 }
4567 case 'arg' : {
46- // Ignore nested paths.
47- const [ originalArgumentName ] = seed . path . split ( '.' ) ;
48- const argumentName = camelCase ( originalArgumentName ) ;
49- const argumentNode = instructionArguments . find ( ( { name } ) => name === argumentName ) ;
50- if ( ! argumentNode ) {
51- throw new CodamaError ( CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING , { name : originalArgumentName } ) ;
68+ const pathParts = seed . path . split ( '.' ) ;
69+ const argumentName = camelCase ( pathParts . length > 1 ? pathParts [ pathParts . length - 1 ] : pathParts [ 0 ] ) ;
70+
71+ let argumentType : TypeNode ;
72+ if ( pathParts . length > 1 ) {
73+ const rootArgName = camelCase ( pathParts [ 0 ] ) ;
74+ const rootArgNode = instructionArguments . find ( ( { name } ) => name === rootArgName ) ;
75+ if ( ! rootArgNode ) {
76+ throw new CodamaError ( CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING , { name : pathParts [ 0 ] } ) ;
77+ }
78+ const resolved = resolveNestedFieldType ( rootArgNode . type , pathParts . slice ( 1 ) , idlTypes , generics ) ;
79+ if ( ! resolved ) {
80+ logWarn ( `Could not resolve nested arg path "${ seed . path } " for PDA seed.` ) ;
81+ return undefined ;
82+ }
83+ argumentType = resolved ;
84+ } else {
85+ const argumentNode = instructionArguments . find ( ( { name } ) => name === argumentName ) ;
86+ if ( ! argumentNode ) {
87+ throw new CodamaError ( CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING , { name : pathParts [ 0 ] } ) ;
88+ }
89+ argumentType = argumentNode . type ;
5290 }
5391
54- // Anchor uses unprefixed strings for PDA seeds even though the
55- // argument itself uses a Borsh size-prefixed string. Thus, we
56- // must recognize this case and convert the type accordingly.
57- const isBorshString =
58- isNode ( argumentNode . type , 'sizePrefixTypeNode' ) &&
59- isNode ( argumentNode . type . type , 'stringTypeNode' ) &&
60- argumentNode . type . type . encoding === 'utf8' &&
61- isNode ( argumentNode . type . prefix , 'numberTypeNode' ) &&
62- argumentNode . type . prefix . format === 'u32' ;
63- const argumentType = isBorshString ? stringTypeNode ( 'utf8' ) : argumentNode . type ;
92+ // Anchor uses unprefixed strings for PDA seeds.
93+ if (
94+ isNode ( argumentType , 'sizePrefixTypeNode' ) &&
95+ isNode ( argumentType . type , 'stringTypeNode' ) &&
96+ argumentType . type . encoding === 'utf8' &&
97+ isNode ( argumentType . prefix , 'numberTypeNode' ) &&
98+ argumentType . prefix . format === 'u32'
99+ ) {
100+ argumentType = stringTypeNode ( 'utf8' ) ;
101+ }
102+
103+ if ( pathParts . length > 1 ) {
104+ return {
105+ definition : variablePdaSeedNode ( argumentName , argumentType ) ,
106+ value : pdaSeedValueNode ( argumentName , argumentValueNode ( argumentName ) ) ,
107+ } ;
108+ }
64109
65110 return {
66- definition : variablePdaSeedNode ( argumentNode . name , argumentType ) ,
67- value : pdaSeedValueNode ( argumentNode . name , argumentValueNode ( argumentNode . name ) ) ,
111+ definition : variablePdaSeedNode ( argumentName , argumentType ) ,
112+ value : pdaSeedValueNode ( argumentName , argumentValueNode ( argumentName ) ) ,
68113 } ;
69114 }
70115 default :
71116 throw new CodamaError ( CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED , { kind } ) ;
72117 }
73118}
119+
120+ function resolveNestedFieldType (
121+ rootType : TypeNode ,
122+ fieldPath : string [ ] ,
123+ idlTypes : IdlV01TypeDef [ ] ,
124+ generics : GenericsV01 ,
125+ ) : TypeNode | undefined {
126+ let currentType = rootType ;
127+
128+ for ( const fieldName of fieldPath ) {
129+ const target = camelCase ( fieldName ) ;
130+
131+ if ( isNode ( currentType , 'structTypeNode' ) ) {
132+ const field = currentType . fields . find ( f => f . name === target ) ;
133+ if ( ! field ) return undefined ;
134+ currentType = field . type ;
135+ continue ;
136+ }
137+
138+ if ( isNode ( currentType , 'definedTypeLinkNode' ) ) {
139+ const linkName = currentType . name ;
140+ const typeDef = idlTypes . find ( t => camelCase ( t . name ) === linkName ) ;
141+ if ( ! typeDef || typeDef . type . kind !== 'struct' || ! typeDef . type . fields ) return undefined ;
142+ const { fields } = typeDef . type ;
143+ if ( ! fields . length || typeof fields [ 0 ] !== 'object' || ! ( 'name' in fields [ 0 ] ) ) return undefined ;
144+ const field = ( fields as IdlV01Field [ ] ) . find ( f => camelCase ( f . name ) === target ) ;
145+ if ( ! field ) return undefined ;
146+ currentType = typeNodeFromAnchorV01 ( field . type , generics ) ;
147+ continue ;
148+ }
149+
150+ return undefined ;
151+ }
152+
153+ return currentType ;
154+ }
0 commit comments