diff --git a/tests/pda-derivation/tests/typescript.spec.ts b/tests/pda-derivation/tests/typescript.spec.ts index 1087bd775..320404439 100644 --- a/tests/pda-derivation/tests/typescript.spec.ts +++ b/tests/pda-derivation/tests/typescript.spec.ts @@ -57,7 +57,7 @@ describe("typescript", () => { }); const keys = await tx.pubkeys(); - expect(keys.account.equals(expectedPDAKey)).is.true; + expect(keys.account!.equals(expectedPDAKey)).is.true; await tx.rpc(); diff --git a/tests/pda-derivation/tsconfig.json b/tests/pda-derivation/tsconfig.json index b3b6656d3..f6f918b7e 100644 --- a/tests/pda-derivation/tsconfig.json +++ b/tests/pda-derivation/tsconfig.json @@ -1,4 +1,5 @@ { + "include": ["./target/types/pda_derivation.ts"], "compilerOptions": { "types": ["mocha", "chai"], "typeRoots": ["./node_modules/@types"], diff --git a/tests/relations-derivation/tests/typescript.spec.ts b/tests/relations-derivation/tests/typescript.spec.ts index 24521d71f..07921da3b 100644 --- a/tests/relations-derivation/tests/typescript.spec.ts +++ b/tests/relations-derivation/tests/typescript.spec.ts @@ -36,7 +36,7 @@ describe("typescript", () => { await tx.instruction(); const keys = await tx.pubkeys(); - expect(keys.myAccount.equals(provider.wallet.publicKey)).is.true; + expect(keys.myAccount!.equals(provider.wallet.publicKey)).is.true; await tx.rpc(); }); diff --git a/tests/relations-derivation/tsconfig.json b/tests/relations-derivation/tsconfig.json index b3b6656d3..3cc6b3ac7 100644 --- a/tests/relations-derivation/tsconfig.json +++ b/tests/relations-derivation/tsconfig.json @@ -1,4 +1,5 @@ { + "include": ["./target/types/relations_derivation.ts"], "compilerOptions": { "types": ["mocha", "chai"], "typeRoots": ["./node_modules/@types"], diff --git a/ts/packages/anchor/src/program/accounts-resolver.ts b/ts/packages/anchor/src/program/accounts-resolver.ts index e1c18e57a..14a1190a8 100644 --- a/ts/packages/anchor/src/program/accounts-resolver.ts +++ b/ts/packages/anchor/src/program/accounts-resolver.ts @@ -73,12 +73,6 @@ export class AccountsResolver> { // Note: We serially resolve PDAs one by one rather than doing them // in parallel because there can be dependencies between // addresses. That is, one PDA can be used as a seed in another. - // - // TODO: PDAs need to be resolved in topological order. For now, we - // require the developer to simply list the accounts in the - // correct order. But in future work, we should create the - // dependency graph and resolve automatically. - // public async resolve() { await this.resolveConst(this._idlIx.accounts); @@ -253,14 +247,16 @@ export class AccountsResolver> { throw new Error("Must have seeds"); const seeds: (Buffer | undefined)[] = await Promise.all( - accountDesc.pda.seeds.map((seedDesc: IdlSeed) => this.toBuffer(seedDesc)) + accountDesc.pda.seeds.map((seedDesc: IdlSeed) => + this.toBuffer(seedDesc, path) + ) ); if (seeds.some((seed) => typeof seed == "undefined")) { return; } - const programId = await this.parseProgramId(accountDesc); + const programId = await this.parseProgramId(accountDesc, path); if (!programId) { return; } @@ -272,7 +268,10 @@ export class AccountsResolver> { this.set([...path, camelCase(accountDesc.name)], pubkey); } - private async parseProgramId(accountDesc: IdlAccount): Promise { + private async parseProgramId( + accountDesc: IdlAccount, + path: string[] = [] + ): Promise { if (!accountDesc.pda?.programId) { return this._programId; } @@ -284,7 +283,7 @@ export class AccountsResolver> { case "arg": return this.argValue(accountDesc.pda.programId); case "account": - return await this.accountValue(accountDesc.pda.programId); + return await this.accountValue(accountDesc.pda.programId, path); default: throw new Error( `Unexpected program seed kind: ${accountDesc.pda.programId.kind}` @@ -292,14 +291,17 @@ export class AccountsResolver> { } } - private async toBuffer(seedDesc: IdlSeed): Promise { + private async toBuffer( + seedDesc: IdlSeed, + path: string[] = [] + ): Promise { switch (seedDesc.kind) { case "const": return this.toBufferConst(seedDesc); case "arg": return await this.toBufferArg(seedDesc); case "account": - return await this.toBufferAccount(seedDesc); + return await this.toBufferAccount(seedDesc, path); default: throw new Error(`Unexpected seed kind: ${seedDesc.kind}`); } @@ -361,20 +363,24 @@ export class AccountsResolver> { } private async toBufferAccount( - seedDesc: IdlSeed + seedDesc: IdlSeed, + path: string[] = [] ): Promise { - const accountValue = await this.accountValue(seedDesc); + const accountValue = await this.accountValue(seedDesc, path); if (!accountValue) { return; } return this.toBufferValue(seedDesc.type, accountValue); } - private async accountValue(seedDesc: IdlSeed): Promise { + private async accountValue( + seedDesc: IdlSeed, + path: string[] = [] + ): Promise { const pathComponents = seedDesc.path.split("."); const fieldName = pathComponents[0]; - const fieldPubkey = this._accounts[camelCase(fieldName)]; + const fieldPubkey = this.get([...path, camelCase(fieldName)]); // The seed is a pubkey of the account. if (pathComponents.length === 1) { diff --git a/ts/packages/anchor/src/program/namespace/methods.ts b/ts/packages/anchor/src/program/namespace/methods.ts index ecd52e3b2..f825d16df 100644 --- a/ts/packages/anchor/src/program/namespace/methods.ts +++ b/ts/packages/anchor/src/program/namespace/methods.ts @@ -7,12 +7,13 @@ import { TransactionInstruction, TransactionSignature, } from "@solana/web3.js"; -import { Idl, IdlTypeDef } from "../../idl.js"; +import { Idl, IdlAccountItem, IdlAccounts, IdlTypeDef } from "../../idl.js"; import Provider from "../../provider.js"; import { AccountsResolver, CustomAccountResolver, } from "../accounts-resolver.js"; +import { Address } from "../common.js"; import { Accounts } from "../context.js"; import { AccountNamespace } from "./account.js"; import { InstructionFn } from "./instruction.js"; @@ -64,6 +65,14 @@ export class MethodsBuilderFactory { } } +type PartialAccounts = Partial<{ + [N in A["name"]]: PartialAccount; +}>; + +type PartialAccount = A extends IdlAccounts + ? Partial> + : Address; + export class MethodsBuilder> { private readonly _accounts: { [name: string]: PublicKey } = {}; private _remainingAccounts: Array = []; @@ -112,12 +121,12 @@ export class MethodsBuilder> { if (this._autoResolveAccounts) { await this._accountsResolver.resolve(); } - return this._accounts as Partial>; + return this._accounts as unknown as Partial< + InstructionAccountAddresses + >; } - public accounts( - accounts: Partial> - ): MethodsBuilder { + public accounts(accounts: PartialAccounts): MethodsBuilder { this._autoResolveAccounts = true; Object.assign(this._accounts, accounts); return this; diff --git a/ts/packages/anchor/src/program/namespace/types.ts b/ts/packages/anchor/src/program/namespace/types.ts index 5d1e908f1..eac8f4b4a 100644 --- a/ts/packages/anchor/src/program/namespace/types.ts +++ b/ts/packages/anchor/src/program/namespace/types.ts @@ -2,6 +2,8 @@ import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { Idl } from "../../"; import { + IdlAccounts as IdlIdlAccounts, + IdlAccountItem, IdlEnumFields, IdlEnumFieldsNamed, IdlEnumFieldsTuple, @@ -94,10 +96,17 @@ export type InstructionContextFnArgs< export type InstructionAccountAddresses< IDL extends Idl, I extends AllInstructions -> = { - [N in keyof Accounts]: PublicKey; +> = InstructionAccountsAddresses; + +type InstructionAccountsAddresses = { + [N in A["name"]]: InstructionAccountsAddress; }; +type InstructionAccountsAddress = + A extends IdlIdlAccounts + ? InstructionAccountsAddresses + : PublicKey; + export type MethodsFn< IDL extends Idl, I extends IDL["instructions"][number],