feat: Add has_one relations inference so you don't need to pass accounts that are referenced by a has_one (#2160)
This commit is contained in:
parent
0c70d183ef
commit
e69e50daaf
|
@ -289,6 +289,8 @@ jobs:
|
||||||
path: tests/multiple-suites
|
path: tests/multiple-suites
|
||||||
- cmd: cd tests/pda-derivation && anchor test --skip-lint && npx tsc --noEmit
|
- cmd: cd tests/pda-derivation && anchor test --skip-lint && npx tsc --noEmit
|
||||||
path: tests/pda-derivation
|
path: tests/pda-derivation
|
||||||
|
- cmd: cd tests/relations-derivation && anchor test --skip-lint && npx tsc --noEmit
|
||||||
|
path: tests/relations-derivation
|
||||||
- cmd: cd tests/anchor-cli-idl && ./test.sh
|
- cmd: cd tests/anchor-cli-idl && ./test.sh
|
||||||
path: tests/anchor-cli-idl
|
path: tests/anchor-cli-idl
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -398,6 +398,8 @@ jobs:
|
||||||
path: tests/multiple-suites
|
path: tests/multiple-suites
|
||||||
- cmd: cd tests/pda-derivation && anchor test --skip-lint && npx tsc --noEmit
|
- cmd: cd tests/pda-derivation && anchor test --skip-lint && npx tsc --noEmit
|
||||||
path: tests/pda-derivation
|
path: tests/pda-derivation
|
||||||
|
- cmd: cd tests/relations-derivation && anchor test --skip-lint && npx tsc --noEmit
|
||||||
|
path: tests/relations-derivation
|
||||||
- cmd: cd tests/anchor-cli-idl && ./test.sh
|
- cmd: cd tests/anchor-cli-idl && ./test.sh
|
||||||
path: tests/anchor-cli-idl
|
path: tests/anchor-cli-idl
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -18,6 +18,9 @@ The minor version will be incremented upon a breaking change and the patch versi
|
||||||
* lang: Add parsing for consts from impl blocks for IDL PDA seeds generation ([#2128](https://github.com/coral-xyz/anchor/pull/2014))
|
* lang: Add parsing for consts from impl blocks for IDL PDA seeds generation ([#2128](https://github.com/coral-xyz/anchor/pull/2014))
|
||||||
* lang: Account closing reassigns to system program and reallocates ([#2169](https://github.com/coral-xyz/anchor/pull/2169)).
|
* lang: Account closing reassigns to system program and reallocates ([#2169](https://github.com/coral-xyz/anchor/pull/2169)).
|
||||||
* ts: Add coders for SPL programs ([#2143](https://github.com/coral-xyz/anchor/pull/2143)).
|
* ts: Add coders for SPL programs ([#2143](https://github.com/coral-xyz/anchor/pull/2143)).
|
||||||
|
* ts: Add `has_one` relations inference so accounts mapped via has_one relationships no longer need to be provided
|
||||||
|
* ts: Add ability to set args after setting accounts and retriving pubkyes
|
||||||
|
* ts: Add `.prepare()` to builder pattern
|
||||||
* spl: Add `freeze_delegated_account` and `thaw_delegated_account` wrappers ([#2164](https://github.com/coral-xyz/anchor/pull/2164))
|
* spl: Add `freeze_delegated_account` and `thaw_delegated_account` wrappers ([#2164](https://github.com/coral-xyz/anchor/pull/2164))
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
|
@ -645,6 +645,7 @@ fn idl_accounts(
|
||||||
},
|
},
|
||||||
docs: if !no_docs { acc.docs.clone() } else { None },
|
docs: if !no_docs { acc.docs.clone() } else { None },
|
||||||
pda: pda::parse(ctx, accounts, acc, seeds_feature),
|
pda: pda::parse(ctx, accounts, acc, seeds_feature),
|
||||||
|
relations: relations::parse(acc, seeds_feature),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|
|
@ -3,6 +3,7 @@ use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod pda;
|
pub mod pda;
|
||||||
|
pub mod relations;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Idl {
|
pub struct Idl {
|
||||||
|
@ -77,6 +78,8 @@ pub struct IdlAccount {
|
||||||
pub docs: Option<Vec<String>>,
|
pub docs: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub pda: Option<IdlPda>,
|
pub pda: Option<IdlPda>,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
pub relations: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
use crate::Field;
|
||||||
|
use syn::Expr;
|
||||||
|
|
||||||
|
pub fn parse(acc: &Field, seeds_feature: bool) -> Vec<String> {
|
||||||
|
if !seeds_feature {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
acc.constraints
|
||||||
|
.has_one
|
||||||
|
.iter()
|
||||||
|
.flat_map(|s| match &s.join_target {
|
||||||
|
Expr::Path(path) => path.path.segments.first().map(|l| l.ident.to_string()),
|
||||||
|
_ => {
|
||||||
|
println!("WARNING: unexpected seed: {:?}", s);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
"multisig",
|
"multisig",
|
||||||
"permissioned-markets",
|
"permissioned-markets",
|
||||||
"pda-derivation",
|
"pda-derivation",
|
||||||
|
"relations-derivation",
|
||||||
"pyth",
|
"pyth",
|
||||||
"realloc",
|
"realloc",
|
||||||
"spl/token-proxy",
|
"spl/token-proxy",
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
[features]
|
||||||
|
seeds = true
|
||||||
|
|
||||||
|
[provider]
|
||||||
|
cluster = "localnet"
|
||||||
|
wallet = "~/.config/solana/id.json"
|
||||||
|
|
||||||
|
[programs.localnet]
|
||||||
|
relations_derivation = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["programs/relations-derivation"]
|
||||||
|
|
||||||
|
[scripts]
|
||||||
|
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
|
@ -0,0 +1,4 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"programs/*"
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Migrations are an early feature. Currently, they're nothing more than this
|
||||||
|
// single deploy script that's invoked from the CLI, injecting a provider
|
||||||
|
// configured from the workspace's Anchor.toml.
|
||||||
|
|
||||||
|
const anchor = require("@project-serum/anchor");
|
||||||
|
|
||||||
|
module.exports = async function (provider) {
|
||||||
|
// Configure client to use the provider.
|
||||||
|
anchor.setProvider(provider);
|
||||||
|
|
||||||
|
// Add your deploy script here.
|
||||||
|
async function deployAsync(exampleString: string): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(exampleString);
|
||||||
|
resolve();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await deployAsync("Typescript migration example complete.");
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "relations-derivation",
|
||||||
|
"version": "0.25.0",
|
||||||
|
"license": "(MIT OR Apache-2.0)",
|
||||||
|
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/coral-xyz/anchor.git"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=11"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "anchor test"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "relations-derivation"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Created with Anchor"
|
||||||
|
rust-version = "1.56"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
name = "relations_derivation"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
no-entrypoint = []
|
||||||
|
no-idl = []
|
||||||
|
cpi = ["no-entrypoint"]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anchor-lang = { path = "../../../../lang" }
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -0,0 +1,68 @@
|
||||||
|
//! The typescript example serves to show how one would setup an Anchor
|
||||||
|
//! workspace with TypeScript tests and migrations.
|
||||||
|
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||||
|
|
||||||
|
#[program]
|
||||||
|
pub mod relations_derivation {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn init_base(ctx: Context<InitBase>) -> Result<()> {
|
||||||
|
ctx.accounts.account.my_account = ctx.accounts.my_account.key();
|
||||||
|
ctx.accounts.account.bump = ctx.bumps["account"];
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn test_relation(_ctx: Context<TestRelation>) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct InitBase<'info> {
|
||||||
|
/// CHECK: yeah I know
|
||||||
|
#[account(mut)]
|
||||||
|
my_account: Signer<'info>,
|
||||||
|
#[account(
|
||||||
|
init,
|
||||||
|
payer = my_account,
|
||||||
|
seeds = [b"seed"],
|
||||||
|
space = 100,
|
||||||
|
bump,
|
||||||
|
)]
|
||||||
|
account: Account<'info, MyAccount>,
|
||||||
|
system_program: Program<'info, System>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Nested<'info> {
|
||||||
|
/// CHECK: yeah I know
|
||||||
|
my_account: UncheckedAccount<'info>,
|
||||||
|
#[account(
|
||||||
|
has_one = my_account,
|
||||||
|
seeds = [b"seed"],
|
||||||
|
bump = account.bump
|
||||||
|
)]
|
||||||
|
account: Account<'info, MyAccount>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TestRelation<'info> {
|
||||||
|
/// CHECK: yeah I know
|
||||||
|
my_account: UncheckedAccount<'info>,
|
||||||
|
#[account(
|
||||||
|
has_one = my_account,
|
||||||
|
seeds = [b"seed"],
|
||||||
|
bump = account.bump
|
||||||
|
)]
|
||||||
|
account: Account<'info, MyAccount>,
|
||||||
|
nested: Nested<'info>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[account]
|
||||||
|
pub struct MyAccount {
|
||||||
|
pub my_account: Pubkey,
|
||||||
|
pub bump: u8
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import * as anchor from "@project-serum/anchor";
|
||||||
|
import { AnchorProvider, Program } from "@project-serum/anchor";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { RelationsDerivation } from "../target/types/relations_derivation";
|
||||||
|
|
||||||
|
describe("typescript", () => {
|
||||||
|
// Configure the client to use the local cluster.
|
||||||
|
anchor.setProvider(anchor.AnchorProvider.env());
|
||||||
|
|
||||||
|
const program = anchor.workspace
|
||||||
|
.RelationsDerivation as Program<RelationsDerivation>;
|
||||||
|
const provider = anchor.getProvider() as AnchorProvider;
|
||||||
|
|
||||||
|
it("Inits the base account", async () => {
|
||||||
|
await program.methods
|
||||||
|
.initBase()
|
||||||
|
.accounts({
|
||||||
|
myAccount: provider.wallet.publicKey,
|
||||||
|
})
|
||||||
|
.rpc();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Derives relationss", async () => {
|
||||||
|
const tx = await program.methods.testRelation().accounts({
|
||||||
|
nested: {
|
||||||
|
account: (
|
||||||
|
await PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from("seed", "utf-8")],
|
||||||
|
program.programId
|
||||||
|
)
|
||||||
|
)[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.instruction();
|
||||||
|
const keys = await tx.pubkeys();
|
||||||
|
|
||||||
|
expect(keys.myAccount.equals(provider.wallet.publicKey)).is.true;
|
||||||
|
|
||||||
|
await tx.rpc();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["mocha", "chai"],
|
||||||
|
"typeRoots": ["./node_modules/@types"],
|
||||||
|
"lib": ["es2015"],
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,6 +63,18 @@ export class BorshAccountsCoder<A extends string = string>
|
||||||
return this.decodeUnchecked(accountName, data);
|
return this.decodeUnchecked(accountName, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public decodeAny<T = any>(data: Buffer): T {
|
||||||
|
const accountDescriminator = data.slice(0, 8);
|
||||||
|
const accountName = Array.from(this.accountLayouts.keys()).find((key) =>
|
||||||
|
BorshAccountsCoder.accountDiscriminator(key).equals(accountDescriminator)
|
||||||
|
);
|
||||||
|
if (!accountName) {
|
||||||
|
throw new Error("Account descriminator not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.decodeUnchecked<T>(accountName as any, data);
|
||||||
|
}
|
||||||
|
|
||||||
public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
|
public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
|
||||||
// Chop off the discriminator before decoding.
|
// Chop off the discriminator before decoding.
|
||||||
const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE);
|
const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE);
|
||||||
|
|
|
@ -57,6 +57,7 @@ export type IdlAccount = {
|
||||||
isMut: boolean;
|
isMut: boolean;
|
||||||
isSigner: boolean;
|
isSigner: boolean;
|
||||||
docs?: string[];
|
docs?: string[];
|
||||||
|
relations?: string[];
|
||||||
pda?: IdlPda;
|
pda?: IdlPda;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
import camelCase from "camelcase";
|
import camelCase from "camelcase";
|
||||||
import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
|
import { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
|
||||||
import { Idl, IdlSeed, IdlAccount } from "../idl.js";
|
import {
|
||||||
|
Idl,
|
||||||
|
IdlSeed,
|
||||||
|
IdlAccount,
|
||||||
|
IdlAccountItem,
|
||||||
|
IdlAccounts,
|
||||||
|
} from "../idl.js";
|
||||||
import * as utf8 from "../utils/bytes/utf8.js";
|
import * as utf8 from "../utils/bytes/utf8.js";
|
||||||
import { TOKEN_PROGRAM_ID, ASSOCIATED_PROGRAM_ID } from "../utils/token.js";
|
import { TOKEN_PROGRAM_ID, ASSOCIATED_PROGRAM_ID } from "../utils/token.js";
|
||||||
import { AllInstructions } from "./namespace/types.js";
|
import { AllInstructions } from "./namespace/types.js";
|
||||||
import Provider from "../provider.js";
|
import Provider from "../provider.js";
|
||||||
import { AccountNamespace } from "./namespace/account.js";
|
import { AccountNamespace } from "./namespace/account.js";
|
||||||
import { coder } from "../spl/token";
|
import { coder } from "../spl/token";
|
||||||
|
import { BorshAccountsCoder } from "src/coder/index.js";
|
||||||
|
|
||||||
|
type Accounts = { [name: string]: PublicKey | Accounts };
|
||||||
|
|
||||||
// Populates a given accounts context with PDAs and common missing accounts.
|
// Populates a given accounts context with PDAs and common missing accounts.
|
||||||
export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
|
_args: Array<any>;
|
||||||
static readonly CONST_ACCOUNTS = {
|
static readonly CONST_ACCOUNTS = {
|
||||||
associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
|
associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
|
||||||
rent: SYSVAR_RENT_PUBKEY,
|
rent: SYSVAR_RENT_PUBKEY,
|
||||||
|
@ -20,16 +30,21 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
private _accountStore: AccountStore<IDL>;
|
private _accountStore: AccountStore<IDL>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _args: Array<any>,
|
_args: Array<any>,
|
||||||
private _accounts: { [name: string]: PublicKey },
|
private _accounts: Accounts,
|
||||||
private _provider: Provider,
|
private _provider: Provider,
|
||||||
private _programId: PublicKey,
|
private _programId: PublicKey,
|
||||||
private _idlIx: AllInstructions<IDL>,
|
private _idlIx: AllInstructions<IDL>,
|
||||||
_accountNamespace: AccountNamespace<IDL>
|
_accountNamespace: AccountNamespace<IDL>
|
||||||
) {
|
) {
|
||||||
|
this._args = _args;
|
||||||
this._accountStore = new AccountStore(_provider, _accountNamespace);
|
this._accountStore = new AccountStore(_provider, _accountNamespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public args(_args: Array<any>): void {
|
||||||
|
this._args = _args;
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -85,6 +100,76 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto populate has_one relationships until we stop finding new accounts
|
||||||
|
while ((await this.resolveRelations(this._idlIx.accounts)) > 0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get(path: string[]): PublicKey | undefined {
|
||||||
|
// Only return if pubkey
|
||||||
|
const ret = path.reduce(
|
||||||
|
(acc, subPath) => acc && acc[subPath],
|
||||||
|
this._accounts
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ret && ret.toBase58) {
|
||||||
|
return ret as PublicKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private set(path: string[], value: PublicKey): void {
|
||||||
|
let curr = this._accounts;
|
||||||
|
path.forEach((p, idx) => {
|
||||||
|
const isLast = idx == path.length - 1;
|
||||||
|
if (isLast) {
|
||||||
|
curr[p] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
curr[p] = curr[p] || {};
|
||||||
|
curr = curr[p] as Accounts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveRelations(
|
||||||
|
accounts: IdlAccountItem[],
|
||||||
|
path: string[] = []
|
||||||
|
): Promise<number> {
|
||||||
|
let found = 0;
|
||||||
|
for (let k = 0; k < accounts.length; k += 1) {
|
||||||
|
const accountDesc = accounts[k];
|
||||||
|
const subAccounts = (accountDesc as IdlAccounts).accounts;
|
||||||
|
if (subAccounts) {
|
||||||
|
found += await this.resolveRelations(subAccounts, [
|
||||||
|
...path,
|
||||||
|
accountDesc.name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
const relations = (accountDesc as IdlAccount).relations || [];
|
||||||
|
const accountDescName = camelCase(accountDesc.name);
|
||||||
|
const newPath = [...path, accountDescName];
|
||||||
|
|
||||||
|
// If we have this account and there's some missing accounts that are relations to this account, fetch them
|
||||||
|
const accountKey = this.get(newPath);
|
||||||
|
if (accountKey) {
|
||||||
|
const matching = relations.filter(
|
||||||
|
(rel) => !this.get([...path, camelCase(rel)])
|
||||||
|
);
|
||||||
|
|
||||||
|
found += matching.length;
|
||||||
|
if (matching.length > 0) {
|
||||||
|
const account = await this._accountStore.fetchAccount(accountKey);
|
||||||
|
await Promise.all(
|
||||||
|
matching.map(async (rel) => {
|
||||||
|
const relName = camelCase(rel);
|
||||||
|
|
||||||
|
this.set([...path, relName], account[relName]);
|
||||||
|
return account[relName];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async autoPopulatePda(accountDesc: IdlAccount) {
|
private async autoPopulatePda(accountDesc: IdlAccount) {
|
||||||
|
@ -176,8 +261,8 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
//
|
//
|
||||||
// Fetch and deserialize it.
|
// Fetch and deserialize it.
|
||||||
const account = await this._accountStore.fetchAccount(
|
const account = await this._accountStore.fetchAccount(
|
||||||
seedDesc.account,
|
fieldPubkey as PublicKey,
|
||||||
fieldPubkey
|
seedDesc.account
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dereference all fields in the path to get the field value
|
// Dereference all fields in the path to get the field value
|
||||||
|
@ -239,8 +324,8 @@ export class AccountStore<IDL extends Idl> {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async fetchAccount<T = any>(
|
public async fetchAccount<T = any>(
|
||||||
name: string,
|
publicKey: PublicKey,
|
||||||
publicKey: PublicKey
|
name?: string
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const address = publicKey.toString();
|
const address = publicKey.toString();
|
||||||
if (!this._cache.has(address)) {
|
if (!this._cache.has(address)) {
|
||||||
|
@ -253,9 +338,25 @@ export class AccountStore<IDL extends Idl> {
|
||||||
}
|
}
|
||||||
const data = coder().accounts.decode("token", accountInfo.data);
|
const data = coder().accounts.decode("token", accountInfo.data);
|
||||||
this._cache.set(address, data);
|
this._cache.set(address, data);
|
||||||
} else {
|
} else if (name) {
|
||||||
const account = this._accounts[camelCase(name)].fetch(publicKey);
|
const account = this._accounts[camelCase(name)].fetch(publicKey);
|
||||||
this._cache.set(address, account);
|
this._cache.set(address, account);
|
||||||
|
} else {
|
||||||
|
const account = await this._provider.connection.getAccountInfo(
|
||||||
|
publicKey
|
||||||
|
);
|
||||||
|
if (account === null) {
|
||||||
|
throw new Error(`invalid account info for ${address}`);
|
||||||
|
}
|
||||||
|
const data = account.data;
|
||||||
|
const firstAccountLayout = Object.values(this._accounts)[0] as any;
|
||||||
|
if (!firstAccountLayout) {
|
||||||
|
throw new Error("No accounts for this program");
|
||||||
|
}
|
||||||
|
const result = (
|
||||||
|
firstAccountLayout.coder.accounts as BorshAccountsCoder
|
||||||
|
).decodeAny(data);
|
||||||
|
this._cache.set(address, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this._cache.get(address);
|
return this._cache.get(address);
|
||||||
|
|
|
@ -66,9 +66,10 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
private _postInstructions: Array<TransactionInstruction> = [];
|
private _postInstructions: Array<TransactionInstruction> = [];
|
||||||
private _accountsResolver: AccountsResolver<IDL, I>;
|
private _accountsResolver: AccountsResolver<IDL, I>;
|
||||||
private _autoResolveAccounts: boolean = true;
|
private _autoResolveAccounts: boolean = true;
|
||||||
|
private _args: Array<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _args: Array<any>,
|
_args: Array<any>,
|
||||||
private _ixFn: InstructionFn<IDL>,
|
private _ixFn: InstructionFn<IDL>,
|
||||||
private _txFn: TransactionFn<IDL>,
|
private _txFn: TransactionFn<IDL>,
|
||||||
private _rpcFn: RpcFn<IDL>,
|
private _rpcFn: RpcFn<IDL>,
|
||||||
|
@ -79,6 +80,7 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
_idlIx: AllInstructions<IDL>,
|
_idlIx: AllInstructions<IDL>,
|
||||||
_accountNamespace: AccountNamespace<IDL>
|
_accountNamespace: AccountNamespace<IDL>
|
||||||
) {
|
) {
|
||||||
|
this._args = _args;
|
||||||
this._accountsResolver = new AccountsResolver(
|
this._accountsResolver = new AccountsResolver(
|
||||||
_args,
|
_args,
|
||||||
this._accounts,
|
this._accounts,
|
||||||
|
@ -89,6 +91,11 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public args(_args: Array<any>): void {
|
||||||
|
this._args = _args;
|
||||||
|
this._accountsResolver.args(_args);
|
||||||
|
}
|
||||||
|
|
||||||
public async pubkeys(): Promise<
|
public async pubkeys(): Promise<
|
||||||
Partial<InstructionAccountAddresses<IDL, I>>
|
Partial<InstructionAccountAddresses<IDL, I>>
|
||||||
> {
|
> {
|
||||||
|
@ -209,6 +216,22 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient shortcut to get instructions and pubkeys via
|
||||||
|
* const { pubkeys, instructions } = await prepare();
|
||||||
|
*/
|
||||||
|
public async prepare(): Promise<{
|
||||||
|
pubkeys: Partial<InstructionAccountAddresses<IDL, I>>;
|
||||||
|
instruction: TransactionInstruction;
|
||||||
|
signers: Signer[];
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
instruction: await this.instruction(),
|
||||||
|
pubkeys: await this.pubkeys(),
|
||||||
|
signers: await this._signers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async transaction(): Promise<Transaction> {
|
public async transaction(): Promise<Transaction> {
|
||||||
if (this._autoResolveAccounts) {
|
if (this._autoResolveAccounts) {
|
||||||
await this._accountsResolver.resolve();
|
await this._accountsResolver.resolve();
|
||||||
|
|
Loading…
Reference in New Issue