fix: Nested pda types and account resolution for pdas (#2259)
This commit is contained in:
parent
91a2b7ec96
commit
feff131ab0
|
@ -57,7 +57,7 @@ describe("typescript", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const keys = await tx.pubkeys();
|
const keys = await tx.pubkeys();
|
||||||
expect(keys.account.equals(expectedPDAKey)).is.true;
|
expect(keys.account!.equals(expectedPDAKey)).is.true;
|
||||||
|
|
||||||
await tx.rpc();
|
await tx.rpc();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"include": ["./target/types/pda_derivation.ts"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["mocha", "chai"],
|
"types": ["mocha", "chai"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe("typescript", () => {
|
||||||
await tx.instruction();
|
await tx.instruction();
|
||||||
const keys = await tx.pubkeys();
|
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();
|
await tx.rpc();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"include": ["./target/types/relations_derivation.ts"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": ["mocha", "chai"],
|
"types": ["mocha", "chai"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
|
|
|
@ -73,12 +73,6 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
// Note: We serially resolve PDAs one by one rather than doing them
|
// Note: We serially resolve PDAs one by one rather than doing them
|
||||||
// in parallel because there can be dependencies between
|
// in parallel because there can be dependencies between
|
||||||
// addresses. That is, one PDA can be used as a seed in another.
|
// 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() {
|
public async resolve() {
|
||||||
await this.resolveConst(this._idlIx.accounts);
|
await this.resolveConst(this._idlIx.accounts);
|
||||||
|
|
||||||
|
@ -253,14 +247,16 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
throw new Error("Must have seeds");
|
throw new Error("Must have seeds");
|
||||||
|
|
||||||
const seeds: (Buffer | undefined)[] = await Promise.all(
|
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")) {
|
if (seeds.some((seed) => typeof seed == "undefined")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const programId = await this.parseProgramId(accountDesc);
|
const programId = await this.parseProgramId(accountDesc, path);
|
||||||
if (!programId) {
|
if (!programId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -272,7 +268,10 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
this.set([...path, camelCase(accountDesc.name)], pubkey);
|
this.set([...path, camelCase(accountDesc.name)], pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async parseProgramId(accountDesc: IdlAccount): Promise<PublicKey> {
|
private async parseProgramId(
|
||||||
|
accountDesc: IdlAccount,
|
||||||
|
path: string[] = []
|
||||||
|
): Promise<PublicKey> {
|
||||||
if (!accountDesc.pda?.programId) {
|
if (!accountDesc.pda?.programId) {
|
||||||
return this._programId;
|
return this._programId;
|
||||||
}
|
}
|
||||||
|
@ -284,7 +283,7 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
case "arg":
|
case "arg":
|
||||||
return this.argValue(accountDesc.pda.programId);
|
return this.argValue(accountDesc.pda.programId);
|
||||||
case "account":
|
case "account":
|
||||||
return await this.accountValue(accountDesc.pda.programId);
|
return await this.accountValue(accountDesc.pda.programId, path);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected program seed kind: ${accountDesc.pda.programId.kind}`
|
`Unexpected program seed kind: ${accountDesc.pda.programId.kind}`
|
||||||
|
@ -292,14 +291,17 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async toBuffer(seedDesc: IdlSeed): Promise<Buffer | undefined> {
|
private async toBuffer(
|
||||||
|
seedDesc: IdlSeed,
|
||||||
|
path: string[] = []
|
||||||
|
): Promise<Buffer | undefined> {
|
||||||
switch (seedDesc.kind) {
|
switch (seedDesc.kind) {
|
||||||
case "const":
|
case "const":
|
||||||
return this.toBufferConst(seedDesc);
|
return this.toBufferConst(seedDesc);
|
||||||
case "arg":
|
case "arg":
|
||||||
return await this.toBufferArg(seedDesc);
|
return await this.toBufferArg(seedDesc);
|
||||||
case "account":
|
case "account":
|
||||||
return await this.toBufferAccount(seedDesc);
|
return await this.toBufferAccount(seedDesc, path);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected seed kind: ${seedDesc.kind}`);
|
throw new Error(`Unexpected seed kind: ${seedDesc.kind}`);
|
||||||
}
|
}
|
||||||
|
@ -361,20 +363,24 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async toBufferAccount(
|
private async toBufferAccount(
|
||||||
seedDesc: IdlSeed
|
seedDesc: IdlSeed,
|
||||||
|
path: string[] = []
|
||||||
): Promise<Buffer | undefined> {
|
): Promise<Buffer | undefined> {
|
||||||
const accountValue = await this.accountValue(seedDesc);
|
const accountValue = await this.accountValue(seedDesc, path);
|
||||||
if (!accountValue) {
|
if (!accountValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this.toBufferValue(seedDesc.type, accountValue);
|
return this.toBufferValue(seedDesc.type, accountValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async accountValue(seedDesc: IdlSeed): Promise<any> {
|
private async accountValue(
|
||||||
|
seedDesc: IdlSeed,
|
||||||
|
path: string[] = []
|
||||||
|
): Promise<any> {
|
||||||
const pathComponents = seedDesc.path.split(".");
|
const pathComponents = seedDesc.path.split(".");
|
||||||
|
|
||||||
const fieldName = pathComponents[0];
|
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.
|
// The seed is a pubkey of the account.
|
||||||
if (pathComponents.length === 1) {
|
if (pathComponents.length === 1) {
|
||||||
|
|
|
@ -7,12 +7,13 @@ import {
|
||||||
TransactionInstruction,
|
TransactionInstruction,
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { Idl, IdlTypeDef } from "../../idl.js";
|
import { Idl, IdlAccountItem, IdlAccounts, IdlTypeDef } from "../../idl.js";
|
||||||
import Provider from "../../provider.js";
|
import Provider from "../../provider.js";
|
||||||
import {
|
import {
|
||||||
AccountsResolver,
|
AccountsResolver,
|
||||||
CustomAccountResolver,
|
CustomAccountResolver,
|
||||||
} from "../accounts-resolver.js";
|
} from "../accounts-resolver.js";
|
||||||
|
import { Address } from "../common.js";
|
||||||
import { Accounts } from "../context.js";
|
import { Accounts } from "../context.js";
|
||||||
import { AccountNamespace } from "./account.js";
|
import { AccountNamespace } from "./account.js";
|
||||||
import { InstructionFn } from "./instruction.js";
|
import { InstructionFn } from "./instruction.js";
|
||||||
|
@ -64,6 +65,14 @@ export class MethodsBuilderFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartialAccounts<A extends IdlAccountItem = IdlAccountItem> = Partial<{
|
||||||
|
[N in A["name"]]: PartialAccount<A & { name: N }>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type PartialAccount<A extends IdlAccountItem> = A extends IdlAccounts
|
||||||
|
? Partial<Accounts<A["accounts"][number]>>
|
||||||
|
: Address;
|
||||||
|
|
||||||
export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
private readonly _accounts: { [name: string]: PublicKey } = {};
|
private readonly _accounts: { [name: string]: PublicKey } = {};
|
||||||
private _remainingAccounts: Array<AccountMeta> = [];
|
private _remainingAccounts: Array<AccountMeta> = [];
|
||||||
|
@ -112,12 +121,12 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
if (this._autoResolveAccounts) {
|
if (this._autoResolveAccounts) {
|
||||||
await this._accountsResolver.resolve();
|
await this._accountsResolver.resolve();
|
||||||
}
|
}
|
||||||
return this._accounts as Partial<InstructionAccountAddresses<IDL, I>>;
|
return this._accounts as unknown as Partial<
|
||||||
|
InstructionAccountAddresses<IDL, I>
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public accounts(
|
public accounts(accounts: PartialAccounts): MethodsBuilder<IDL, I> {
|
||||||
accounts: Partial<Accounts<I["accounts"][number]>>
|
|
||||||
): MethodsBuilder<IDL, I> {
|
|
||||||
this._autoResolveAccounts = true;
|
this._autoResolveAccounts = true;
|
||||||
Object.assign(this._accounts, accounts);
|
Object.assign(this._accounts, accounts);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import BN from "bn.js";
|
import BN from "bn.js";
|
||||||
import { Idl } from "../../";
|
import { Idl } from "../../";
|
||||||
import {
|
import {
|
||||||
|
IdlAccounts as IdlIdlAccounts,
|
||||||
|
IdlAccountItem,
|
||||||
IdlEnumFields,
|
IdlEnumFields,
|
||||||
IdlEnumFieldsNamed,
|
IdlEnumFieldsNamed,
|
||||||
IdlEnumFieldsTuple,
|
IdlEnumFieldsTuple,
|
||||||
|
@ -94,10 +96,17 @@ export type InstructionContextFnArgs<
|
||||||
export type InstructionAccountAddresses<
|
export type InstructionAccountAddresses<
|
||||||
IDL extends Idl,
|
IDL extends Idl,
|
||||||
I extends AllInstructions<IDL>
|
I extends AllInstructions<IDL>
|
||||||
> = {
|
> = InstructionAccountsAddresses<I["accounts"][number]>;
|
||||||
[N in keyof Accounts<I["accounts"][number]>]: PublicKey;
|
|
||||||
|
type InstructionAccountsAddresses<A extends IdlAccountItem = IdlAccountItem> = {
|
||||||
|
[N in A["name"]]: InstructionAccountsAddress<A & { name: N }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type InstructionAccountsAddress<A extends IdlAccountItem> =
|
||||||
|
A extends IdlIdlAccounts
|
||||||
|
? InstructionAccountsAddresses<A["accounts"][number]>
|
||||||
|
: PublicKey;
|
||||||
|
|
||||||
export type MethodsFn<
|
export type MethodsFn<
|
||||||
IDL extends Idl,
|
IDL extends Idl,
|
||||||
I extends IDL["instructions"][number],
|
I extends IDL["instructions"][number],
|
||||||
|
|
Loading…
Reference in New Issue