ts: builder api (#1324)
This commit is contained in:
parent
1f95929f1d
commit
e121e4e09d
|
@ -15,6 +15,7 @@ incremented for features.
|
|||
|
||||
* lang: Add `seeds::program` constraint for specifying which program_id to use when deriving PDAs.([#1197](https://github.com/project-serum/anchor/pull/1197))
|
||||
* ts: Remove error logging in the event parser when log websocket encounters a program error. ([#1313](https://github.com/project-serum/anchor/pull/1313))
|
||||
* ts: Add new `methods` namespace to the program client, introducing a more ergonomic builder API ([#1324](https://github.com/project-serum/anchor/pull/1324)).
|
||||
|
||||
### Breaking
|
||||
|
||||
|
|
|
@ -1021,7 +1021,7 @@ fn docker_build_bpf(
|
|||
println!(
|
||||
"Building {} manifest: {:?}",
|
||||
binary_name,
|
||||
manifest_path.display().to_string()
|
||||
manifest_path.display()
|
||||
);
|
||||
|
||||
// Execute the build.
|
||||
|
|
|
@ -225,29 +225,24 @@ describe("cfo", () => {
|
|||
stake: stakeBump,
|
||||
treasury: treasuryBump,
|
||||
};
|
||||
await program.rpc.createOfficer(
|
||||
bumps,
|
||||
distribution,
|
||||
registrar,
|
||||
msrmRegistrar,
|
||||
{
|
||||
accounts: {
|
||||
officer,
|
||||
srmVault,
|
||||
usdcVault,
|
||||
stake,
|
||||
treasury,
|
||||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
usdcMint: ORDERBOOK_ENV.usdc,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
dexProgram: DEX_PID,
|
||||
swapProgram: SWAP_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
}
|
||||
);
|
||||
await program.methods
|
||||
.createOfficer(bumps, distribution, registrar, msrmRegistrar)
|
||||
.accounts({
|
||||
officer,
|
||||
srmVault,
|
||||
usdcVault,
|
||||
stake,
|
||||
treasury,
|
||||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
usdcMint: ORDERBOOK_ENV.usdc,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
dexProgram: DEX_PID,
|
||||
swapProgram: SWAP_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
})
|
||||
.rpc();
|
||||
|
||||
officerAccount = await program.account.officer.fetch(officer);
|
||||
assert.ok(
|
||||
|
@ -260,8 +255,9 @@ describe("cfo", () => {
|
|||
});
|
||||
|
||||
it("Creates a token account for the officer associated with the market", async () => {
|
||||
await program.rpc.createOfficerToken(bBump, {
|
||||
accounts: {
|
||||
await program.methods
|
||||
.createOfficerToken(bBump)
|
||||
.accounts({
|
||||
officer,
|
||||
token: bVault,
|
||||
mint: ORDERBOOK_ENV.mintB,
|
||||
|
@ -269,16 +265,17 @@ describe("cfo", () => {
|
|||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PID,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
const tokenAccount = await B_TOKEN_CLIENT.getAccountInfo(bVault);
|
||||
assert.ok(tokenAccount.state === 1);
|
||||
assert.ok(tokenAccount.isInitialized);
|
||||
});
|
||||
|
||||
it("Creates an open orders account for the officer", async () => {
|
||||
await program.rpc.createOfficerOpenOrders(openOrdersBump, {
|
||||
accounts: {
|
||||
await program.methods
|
||||
.createOfficerOpenOrders(openOrdersBump)
|
||||
.accounts({
|
||||
officer,
|
||||
openOrders,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
|
@ -286,8 +283,8 @@ describe("cfo", () => {
|
|||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
market: ORDERBOOK_ENV.marketA.address,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
await program.rpc.createOfficerOpenOrders(openOrdersBumpB, {
|
||||
accounts: {
|
||||
officer,
|
||||
|
@ -310,8 +307,9 @@ describe("cfo", () => {
|
|||
program.provider,
|
||||
sweepVault
|
||||
);
|
||||
await program.rpc.sweepFees({
|
||||
accounts: {
|
||||
await program.methods
|
||||
.sweepFees()
|
||||
.accounts({
|
||||
officer,
|
||||
sweepVault,
|
||||
mint: ORDERBOOK_ENV.usdc,
|
||||
|
@ -323,8 +321,8 @@ describe("cfo", () => {
|
|||
dexProgram: DEX_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
const afterTokenAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
sweepVault
|
||||
|
@ -336,26 +334,28 @@ describe("cfo", () => {
|
|||
});
|
||||
|
||||
it("Creates a market auth token", async () => {
|
||||
await program.rpc.authorizeMarket(marketAuthBump, {
|
||||
accounts: {
|
||||
await program.methods
|
||||
.authorizeMarket(marketAuthBump)
|
||||
.accounts({
|
||||
officer,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
marketAuth,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
market: ORDERBOOK_ENV.marketA.address,
|
||||
systemProgram: SystemProgram.programId,
|
||||
},
|
||||
});
|
||||
await program.rpc.authorizeMarket(marketAuthBumpB, {
|
||||
accounts: {
|
||||
})
|
||||
.rpc();
|
||||
await program.methods
|
||||
.authorizeMarket(marketAuthBumpB)
|
||||
.accounts({
|
||||
officer,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
marketAuth: marketAuthB,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
market: ORDERBOOK_ENV.marketB.address,
|
||||
systemProgram: SystemProgram.programId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
});
|
||||
|
||||
it("Transfers into the mintB vault", async () => {
|
||||
|
@ -378,8 +378,9 @@ describe("cfo", () => {
|
|||
quoteDecimals: 6,
|
||||
strict: false,
|
||||
};
|
||||
await program.rpc.swapToUsdc(minExchangeRate, {
|
||||
accounts: {
|
||||
await program.methods
|
||||
.swapToUsdc(minExchangeRate)
|
||||
.accounts({
|
||||
officer,
|
||||
market: {
|
||||
market: marketBClient.address,
|
||||
|
@ -402,8 +403,8 @@ describe("cfo", () => {
|
|||
tokenProgram: TOKEN_PID,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const bVaultAfter = await B_TOKEN_CLIENT.getAccountInfo(bVault);
|
||||
const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
@ -424,8 +425,9 @@ describe("cfo", () => {
|
|||
quoteDecimals: 6,
|
||||
strict: false,
|
||||
};
|
||||
await program.rpc.swapToSrm(minExchangeRate, {
|
||||
accounts: {
|
||||
await program.methods
|
||||
.swapToSrm(minExchangeRate)
|
||||
.accounts({
|
||||
officer,
|
||||
market: {
|
||||
market: marketAClient.address,
|
||||
|
@ -449,8 +451,8 @@ describe("cfo", () => {
|
|||
tokenProgram: TOKEN_PID,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
@ -467,8 +469,9 @@ describe("cfo", () => {
|
|||
const stakeBefore = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
|
||||
const mintInfoBefore = await SRM_TOKEN_CLIENT.getMintInfo();
|
||||
|
||||
await program.rpc.distribute({
|
||||
accounts: {
|
||||
await program.methods
|
||||
.distribute()
|
||||
.accounts({
|
||||
officer,
|
||||
treasury,
|
||||
stake,
|
||||
|
@ -476,8 +479,8 @@ describe("cfo", () => {
|
|||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
tokenProgram: TOKEN_PID,
|
||||
dexProgram: DEX_PID,
|
||||
},
|
||||
});
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const treasuryAfter = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
|
||||
|
|
|
@ -10,6 +10,7 @@ import NamespaceFactory, {
|
|||
AccountNamespace,
|
||||
StateClient,
|
||||
SimulateNamespace,
|
||||
MethodsNamespace,
|
||||
} from "./namespace/index.js";
|
||||
import { utf8 } from "../utils/bytes/index.js";
|
||||
import { EventManager } from "./event.js";
|
||||
|
@ -206,6 +207,12 @@ export class Program<IDL extends Idl = Idl> {
|
|||
*/
|
||||
readonly state?: StateClient<IDL>;
|
||||
|
||||
/**
|
||||
* The namespace provides a builder API for all APIs on the program.
|
||||
* This is an alternative to using namespace the other namespaces..
|
||||
*/
|
||||
readonly methods: MethodsNamespace<IDL>;
|
||||
|
||||
/**
|
||||
* Address of the program.
|
||||
*/
|
||||
|
@ -275,6 +282,7 @@ export class Program<IDL extends Idl = Idl> {
|
|||
transaction,
|
||||
account,
|
||||
simulate,
|
||||
methods,
|
||||
state,
|
||||
] = NamespaceFactory.build(idl, this._coder, programId, provider);
|
||||
this.rpc = rpc;
|
||||
|
@ -282,6 +290,7 @@ export class Program<IDL extends Idl = Idl> {
|
|||
this.transaction = transaction;
|
||||
this.account = account;
|
||||
this.simulate = simulate;
|
||||
this.methods = methods;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import AccountFactory, { AccountNamespace } from "./account.js";
|
|||
import SimulateFactory, { SimulateNamespace } from "./simulate.js";
|
||||
import { parseIdlErrors } from "../common.js";
|
||||
import { AllInstructions } from "./types.js";
|
||||
import { MethodsBuilderFactory, MethodsNamespace } from "./methods";
|
||||
|
||||
// Re-exports.
|
||||
export { StateClient } from "./state.js";
|
||||
|
@ -20,6 +21,7 @@ export { RpcNamespace, RpcFn } from "./rpc.js";
|
|||
export { AccountNamespace, AccountClient, ProgramAccount } from "./account.js";
|
||||
export { SimulateNamespace, SimulateFn } from "./simulate.js";
|
||||
export { IdlAccounts, IdlTypes } from "./types.js";
|
||||
export { MethodsBuilderFactory, MethodsNamespace } from "./methods";
|
||||
|
||||
export default class NamespaceFactory {
|
||||
/**
|
||||
|
@ -36,12 +38,14 @@ export default class NamespaceFactory {
|
|||
TransactionNamespace<IDL>,
|
||||
AccountNamespace<IDL>,
|
||||
SimulateNamespace<IDL>,
|
||||
MethodsNamespace<IDL>,
|
||||
StateClient<IDL> | undefined
|
||||
] {
|
||||
const rpc: RpcNamespace = {};
|
||||
const instruction: InstructionNamespace = {};
|
||||
const transaction: TransactionNamespace = {};
|
||||
const simulate: SimulateNamespace = {};
|
||||
const methods: MethodsNamespace = {};
|
||||
|
||||
const idlErrors = parseIdlErrors(idl);
|
||||
|
||||
|
@ -64,6 +68,12 @@ export default class NamespaceFactory {
|
|||
programId,
|
||||
idl
|
||||
);
|
||||
const methodItem = MethodsBuilderFactory.build(
|
||||
ixItem,
|
||||
txItem,
|
||||
rpcItem,
|
||||
simulateItem
|
||||
);
|
||||
|
||||
const name = camelCase(idlIx.name);
|
||||
|
||||
|
@ -71,6 +81,7 @@ export default class NamespaceFactory {
|
|||
transaction[name] = txItem;
|
||||
rpc[name] = rpcItem;
|
||||
simulate[name] = simulateItem;
|
||||
methods[name] = methodItem;
|
||||
});
|
||||
|
||||
const account: AccountNamespace<IDL> = idl.accounts
|
||||
|
@ -83,6 +94,7 @@ export default class NamespaceFactory {
|
|||
transaction as TransactionNamespace<IDL>,
|
||||
account,
|
||||
simulate as SimulateNamespace<IDL>,
|
||||
methods as MethodsNamespace<IDL>,
|
||||
state,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
ConfirmOptions,
|
||||
AccountMeta,
|
||||
Signer,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
TransactionSignature,
|
||||
PublicKey,
|
||||
} from "@solana/web3.js";
|
||||
import { SimulateResponse } from "./simulate";
|
||||
import { TransactionFn } from "./transaction.js";
|
||||
import { Idl } from "../../idl.js";
|
||||
import {
|
||||
AllInstructions,
|
||||
InstructionContextFn,
|
||||
MakeInstructionsNamespace,
|
||||
} from "./types";
|
||||
import { InstructionFn } from "./instruction";
|
||||
import { RpcFn } from "./rpc";
|
||||
import { SimulateFn } from "./simulate";
|
||||
|
||||
export class MethodsBuilderFactory {
|
||||
public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
|
||||
ixFn: InstructionFn<IDL>,
|
||||
txFn: TransactionFn<IDL>,
|
||||
rpcFn: RpcFn<IDL>,
|
||||
simulateFn: SimulateFn<IDL>
|
||||
): MethodFn {
|
||||
const request: MethodFn<IDL, I> = (...args) => {
|
||||
return new MethodsBuilder(args, ixFn, txFn, rpcFn, simulateFn);
|
||||
};
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
|
||||
private _accounts: { [name: string]: PublicKey } = {};
|
||||
private _remainingAccounts: Array<AccountMeta> = [];
|
||||
private _signers: Array<Signer> = [];
|
||||
private _preInstructions: Array<TransactionInstruction> = [];
|
||||
private _postInstructions: Array<TransactionInstruction> = [];
|
||||
|
||||
constructor(
|
||||
private _args: Array<any>,
|
||||
private _ixFn: InstructionFn<IDL>,
|
||||
private _txFn: TransactionFn<IDL>,
|
||||
private _rpcFn: RpcFn<IDL>,
|
||||
private _simulateFn: SimulateFn<IDL>
|
||||
) {}
|
||||
|
||||
// TODO: don't use any.
|
||||
public accounts(accounts: any): MethodsBuilder<IDL, I> {
|
||||
Object.assign(this._accounts, accounts);
|
||||
return this;
|
||||
}
|
||||
|
||||
public remainingAccounts(
|
||||
accounts: Array<AccountMeta>
|
||||
): MethodsBuilder<IDL, I> {
|
||||
this._remainingAccounts = this._remainingAccounts.concat(accounts);
|
||||
return this;
|
||||
}
|
||||
|
||||
public preInstructions(
|
||||
ixs: Array<TransactionInstruction>
|
||||
): MethodsBuilder<IDL, I> {
|
||||
this._preInstructions = this._preInstructions.concat(ixs);
|
||||
return this;
|
||||
}
|
||||
|
||||
public postInstructions(
|
||||
ixs: Array<TransactionInstruction>
|
||||
): MethodsBuilder<IDL, I> {
|
||||
this._postInstructions = this._postInstructions.concat(ixs);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async rpc(options: ConfirmOptions): Promise<TransactionSignature> {
|
||||
await this.resolvePdas();
|
||||
// @ts-ignore
|
||||
return this._rpcFn(...this._args, {
|
||||
accounts: this._accounts,
|
||||
signers: this._signers,
|
||||
remainingAccounts: this._remainingAccounts,
|
||||
preInstructions: this._preInstructions,
|
||||
postInstructions: this._postInstructions,
|
||||
options: options,
|
||||
});
|
||||
}
|
||||
|
||||
public async simulate(
|
||||
options: ConfirmOptions
|
||||
): Promise<SimulateResponse<any, any>> {
|
||||
await this.resolvePdas();
|
||||
// @ts-ignore
|
||||
return this._simulateFn(...this._args, {
|
||||
accounts: this._accounts,
|
||||
signers: this._signers,
|
||||
remainingAccounts: this._remainingAccounts,
|
||||
preInstructions: this._preInstructions,
|
||||
postInstructions: this._postInstructions,
|
||||
options: options,
|
||||
});
|
||||
}
|
||||
|
||||
public async instruction(): Promise<TransactionInstruction> {
|
||||
await this.resolvePdas();
|
||||
// @ts-ignore
|
||||
return this._ixFn(...this._args, {
|
||||
accounts: this._accounts,
|
||||
signers: this._signers,
|
||||
remainingAccounts: this._remainingAccounts,
|
||||
preInstructions: this._preInstructions,
|
||||
postInstructions: this._postInstructions,
|
||||
});
|
||||
}
|
||||
|
||||
public async transaction(): Promise<Transaction> {
|
||||
await this.resolvePdas();
|
||||
// @ts-ignore
|
||||
return this._txFn(...this._args, {
|
||||
accounts: this._accounts,
|
||||
signers: this._signers,
|
||||
remainingAccounts: this._remainingAccounts,
|
||||
preInstructions: this._preInstructions,
|
||||
postInstructions: this._postInstructions,
|
||||
});
|
||||
}
|
||||
|
||||
private async resolvePdas() {
|
||||
// TODO: resolve all PDAs and accounts not provided.
|
||||
}
|
||||
}
|
||||
|
||||
export type MethodsNamespace<
|
||||
IDL extends Idl = Idl,
|
||||
I extends AllInstructions<IDL> = AllInstructions<IDL>
|
||||
> = MakeInstructionsNamespace<IDL, I, any>; // TODO: don't use any.
|
||||
|
||||
export type MethodFn<
|
||||
IDL extends Idl = Idl,
|
||||
I extends AllInstructions<IDL> = AllInstructions<IDL>
|
||||
> = InstructionContextFn<IDL, I, MethodsBuilder<IDL, I>>;
|
|
@ -134,7 +134,7 @@ export type SimulateFn<
|
|||
Promise<SimulateResponse<NullableEvents<IDL>, IdlTypes<IDL>>>
|
||||
>;
|
||||
|
||||
type SimulateResponse<E extends IdlEvent, Defined> = {
|
||||
export type SimulateResponse<E extends IdlEvent, Defined> = {
|
||||
events: readonly Event<E, Defined>[];
|
||||
raw: readonly string[];
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue