rename to transactionInstructions everywhere; still lots to fix in provider

This commit is contained in:
henrye 2023-02-23 17:26:23 +00:00
parent 681a97ba3f
commit b20b5dabb1
10 changed files with 109 additions and 213 deletions

View File

@ -1,5 +1,5 @@
import { Buffer } from "buffer";
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { Keypair, PublicKey, VersionedTransaction } from "@solana/web3.js";
import { Wallet } from "./provider";
/**
@ -30,14 +30,18 @@ export default class NodeWallet implements Wallet {
return new NodeWallet(payer);
}
async signTransaction(tx: Transaction): Promise<Transaction> {
tx.partialSign(this.payer);
async signTransaction(
tx: VersionedTransaction
): Promise<VersionedTransaction> {
tx.sign([this.payer]);
return tx;
}
async signAllTransactions(txs: Transaction[]): Promise<Transaction[]> {
async signAllTransactions(
txs: VersionedTransaction[]
): Promise<VersionedTransaction[]> {
return txs.map((t) => {
t.partialSign(this.payer);
t.sign([this.payer]);
return t;
});
}

View File

@ -6,7 +6,7 @@ import { Coder, BorshCoder } from "../coder/index.js";
import NamespaceFactory, {
RpcNamespace,
InstructionNamespace,
TransactionNamespace,
TransactionInstructionsNamespace,
AccountNamespace,
SimulateNamespace,
MethodsNamespace,
@ -138,13 +138,13 @@ export class Program<IDL extends Idl = Idl> {
readonly instruction: InstructionNamespace<IDL>;
/**
* The namespace provides functions to build [[Transaction]] objects for each
* The namespace provides functions to build [[TransactionInstruction\[\]]] objects for each
* method of a program.
*
* ## Usage
*
* ```javascript
* program.transaction.<method>(...args, ctx);
* program.transactionInstructions.<method>(...args, ctx);
* ```
*
* ## Parameters
@ -159,7 +159,7 @@ export class Program<IDL extends Idl = Idl> {
* To create an instruction for the `increment` method above,
*
* ```javascript
* const tx = await program.transaction.increment({
* const tx = await program.transactionInstructions.increment({
* accounts: {
* counter,
* },
@ -167,7 +167,7 @@ export class Program<IDL extends Idl = Idl> {
* ```
* @deprecated
*/
readonly transaction: TransactionNamespace<IDL>;
readonly transactionInstructions: TransactionInstructionsNamespace<IDL>;
/**
* The namespace provides functions to simulate transactions for each method
@ -283,17 +283,24 @@ export class Program<IDL extends Idl = Idl> {
this._events = new EventManager(this._programId, provider, this._coder);
// Dynamic namespaces.
const [rpc, instruction, transaction, account, simulate, methods, views] =
NamespaceFactory.build(
idl,
this._coder,
programId,
provider,
getCustomResolver ?? (() => undefined)
);
const [
rpc,
instruction,
transactionInstructions,
account,
simulate,
methods,
views,
] = NamespaceFactory.build(
idl,
this._coder,
programId,
provider,
getCustomResolver ?? (() => undefined)
);
this.rpc = rpc;
this.instruction = instruction;
this.transaction = transaction;
this.transactionInstructions = transactionInstructions;
this.account = account;
this.simulate = simulate;
this.methods = methods;

View File

@ -4,7 +4,9 @@ import { Coder } from "../../coder/index.js";
import Provider from "../../provider.js";
import { Idl, IdlInstruction } from "../../idl.js";
import InstructionFactory, { InstructionNamespace } from "./instruction.js";
import TransactionFactory, { TransactionNamespace } from "./transaction.js";
import TransactionInstructionsFactory, {
TransactionInstructionsNamespace,
} from "./transaction-instructions.js";
import RpcFactory, { RpcNamespace } from "./rpc.js";
import AccountFactory, { AccountNamespace } from "./account.js";
import SimulateFactory, { SimulateNamespace } from "./simulate.js";
@ -15,7 +17,10 @@ import { CustomAccountResolver } from "../accounts-resolver.js";
// Re-exports.
export { InstructionNamespace, InstructionFn } from "./instruction.js";
export { TransactionNamespace, TransactionFn } from "./transaction.js";
export {
TransactionInstructionsNamespace,
TransactionInstructionsFn,
} from "./transaction-instructions.js";
export { RpcNamespace, RpcFn } from "./rpc.js";
export { AccountNamespace, AccountClient, ProgramAccount } from "./account.js";
export { SimulateNamespace, SimulateFn } from "./simulate.js";
@ -38,7 +43,7 @@ export default class NamespaceFactory {
): [
RpcNamespace<IDL>,
InstructionNamespace<IDL>,
TransactionNamespace<IDL>,
TransactionInstructionsNamespace<IDL>,
AccountNamespace<IDL>,
SimulateNamespace<IDL>,
MethodsNamespace<IDL>,
@ -46,7 +51,7 @@ export default class NamespaceFactory {
] {
const rpc: RpcNamespace = {};
const instruction: InstructionNamespace = {};
const transaction: TransactionNamespace = {};
const transactionInstructions: TransactionInstructionsNamespace = {};
const simulate: SimulateNamespace = {};
const methods: MethodsNamespace = {};
const view: ViewNamespace = {};
@ -63,11 +68,11 @@ export default class NamespaceFactory {
(ixName, ix) => coder.instruction.encode(ixName, ix),
programId
);
const txItem = TransactionFactory.build(idlIx, ixItem);
const rpcItem = RpcFactory.build(idlIx, txItem, idlErrors, provider);
const txIxsItem = TransactionInstructionsFactory.build(idlIx, ixItem);
const rpcItem = RpcFactory.build(idlIx, txIxsItem, idlErrors, provider);
const simulateItem = SimulateFactory.build(
idlIx,
txItem,
txIxsItem,
idlErrors,
provider,
coder,
@ -80,7 +85,7 @@ export default class NamespaceFactory {
programId,
idlIx,
ixItem,
txItem,
txIxsItem,
rpcItem,
simulateItem,
viewItem,
@ -91,7 +96,7 @@ export default class NamespaceFactory {
const name = camelCase(idlIx.name);
instruction[name] = ixItem;
transaction[name] = txItem;
transactionInstructions[name] = txIxsItem;
rpc[name] = rpcItem;
simulate[name] = simulateItem;
methods[name] = methodItem;
@ -103,7 +108,7 @@ export default class NamespaceFactory {
return [
rpc as RpcNamespace<IDL>,
instruction as InstructionNamespace<IDL>,
transaction as TransactionNamespace<IDL>,
transactionInstructions as TransactionInstructionsNamespace<IDL>,
account,
simulate as SimulateNamespace<IDL>,
methods as MethodsNamespace<IDL>,

View File

@ -3,7 +3,6 @@ import {
ConfirmOptions,
PublicKey,
Signer,
Transaction,
TransactionInstruction,
TransactionSignature,
} from "@solana/web3.js";
@ -20,7 +19,7 @@ import { AccountNamespace } from "./account.js";
import { InstructionFn } from "./instruction.js";
import { RpcFn } from "./rpc.js";
import { SimulateFn, SimulateResponse } from "./simulate.js";
import { TransactionFn } from "./transaction.js";
import { TransactionInstructionsFn } from "./transaction-instructions.js";
import {
AllInstructions,
InstructionAccountAddresses,
@ -40,7 +39,7 @@ export class MethodsBuilderFactory {
programId: PublicKey,
idlIx: AllInstructions<IDL>,
ixFn: InstructionFn<IDL>,
txFn: TransactionFn<IDL>,
txIxsFn: TransactionInstructionsFn<IDL>,
rpcFn: RpcFn<IDL>,
simulateFn: SimulateFn<IDL>,
viewFn: ViewFn<IDL> | undefined,
@ -52,7 +51,7 @@ export class MethodsBuilderFactory {
new MethodsBuilder(
args,
ixFn,
txFn,
txIxsFn,
rpcFn,
simulateFn,
viewFn,
@ -121,7 +120,7 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
constructor(
_args: Array<any>,
private _ixFn: InstructionFn<IDL>,
private _txFn: TransactionFn<IDL>,
private _txIxsFn: TransactionInstructionsFn<IDL>,
private _rpcFn: RpcFn<IDL>,
private _simulateFn: SimulateFn<IDL>,
private _viewFn: ViewFn<IDL> | undefined,
@ -299,13 +298,13 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
};
}
public async transaction(): Promise<Transaction> {
public async transactionInstructions(): Promise<TransactionInstruction[]> {
if (this._autoResolveAccounts) {
await this._accountsResolver.resolve();
}
// @ts-ignore
return this._txFn(...this._args, {
return this._txIxsFn(...this._args, {
accounts: this._accounts,
signers: this._signers,
remainingAccounts: this._remainingAccounts,

View File

@ -2,7 +2,7 @@ import { TransactionSignature } from "@solana/web3.js";
import Provider from "../../provider.js";
import { Idl } from "../../idl.js";
import { splitArgsAndCtx } from "../context.js";
import { TransactionFn } from "./transaction.js";
import { TransactionInstructionsFn } from "./transaction-instructions.js";
import { translateError } from "../../error.js";
import {
AllInstructions,
@ -13,12 +13,12 @@ import {
export default class RpcFactory {
public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
idlIx: I,
txFn: TransactionFn<IDL, I>,
txIxsFn: TransactionInstructionsFn<IDL, I>,
idlErrors: Map<number, string>,
provider: Provider
): RpcFn {
const rpc: RpcFn<IDL, I> = async (...args) => {
const tx = txFn(...args);
const txIxs = txIxsFn(...args);
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
if (provider.sendAndConfirm === undefined) {
throw new Error(
@ -27,7 +27,7 @@ export default class RpcFactory {
}
try {
return await provider.sendAndConfirm(
tx,
txIxs,
ctx.signers ?? [],
ctx.options
);

View File

@ -2,7 +2,7 @@ import { PublicKey } from "@solana/web3.js";
import Provider from "../../provider.js";
import { SuccessfulTxSimulationResponse } from "src/utils/rpc.js";
import { splitArgsAndCtx } from "../context.js";
import { TransactionFn } from "./transaction.js";
import { TransactionInstructionsFn } from "./transaction-instructions.js";
import { EventParser, Event } from "../event.js";
import { Coder } from "../../coder/index.js";
import { Idl, IdlEvent } from "../../idl.js";
@ -17,7 +17,7 @@ import {
export default class SimulateFactory {
public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
idlIx: AllInstructions<IDL>,
txFn: TransactionFn<IDL>,
txIxsFn: TransactionInstructionsFn<IDL>,
idlErrors: Map<number, string>,
provider: Provider,
coder: Coder,
@ -25,7 +25,7 @@ export default class SimulateFactory {
idl: IDL
): SimulateFn<IDL, I> {
const simulate: SimulateFn<IDL> = async (...args) => {
const tx = txFn(...args);
const tx = txIxsFn(...args);
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
let resp: SuccessfulTxSimulationResponse | undefined = undefined;
if (provider.simulate === undefined) {

View File

@ -1,4 +1,4 @@
import { Transaction, TransactionInstruction } from "@solana/web3.js";
import { TransactionInstruction } from "@solana/web3.js";
import { Idl, IdlInstruction } from "../../idl.js";
import { splitArgsAndCtx } from "../context.js";
import { InstructionFn } from "./instruction.js";
@ -8,12 +8,14 @@ import {
MakeInstructionsNamespace,
} from "./types.js";
export default class TransactionFactory {
export default class TransactionInstructionsFactory {
public static build<IDL extends Idl, I extends AllInstructions<IDL>>(
idlIx: I,
ixFn: InstructionFn<IDL, I>
): TransactionFn<IDL, I> {
const txFn: TransactionFn<IDL, I> = (...args): TransactionInstruction[] => {
): TransactionInstructionsFn<IDL, I> {
const txIxsFn: TransactionInstructionsFn<IDL, I> = (
...args
): TransactionInstruction[] => {
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
const tx: TransactionInstruction[] = [];
if (ctx.preInstructions && ctx.instructions) {
@ -26,18 +28,18 @@ export default class TransactionFactory {
return tx;
};
return txFn;
return txIxsFn;
}
}
/**
* The namespace provides functions to build [[Transaction]] objects for each
* The namespace provides functions to build [[TransactionInstruction\[\]]] objects for each
* method of a program.
*
* ## Usage
*
* ```javascript
* program.transaction.<method>(...args, ctx);
* program.transactionInstructions.<method>(...args, ctx);
* ```
*
* ## Parameters
@ -52,22 +54,22 @@ export default class TransactionFactory {
* To create an instruction for the `increment` method above,
*
* ```javascript
* const tx = await program.transaction.increment({
* const tx = await program.transactionInstructions.increment({
* accounts: {
* counter,
* },
* });
* ```
*/
export type TransactionNamespace<
export type TransactionInstructionsNamespace<
IDL extends Idl = Idl,
I extends AllInstructions<IDL> = AllInstructions<IDL>
> = MakeInstructionsNamespace<IDL, I, TransactionInstruction[]>;
/**
* Tx is a function to create a `TransactionInstruction[]` for a given program instruction.
* TxIxs is a function to create a `TransactionInstruction[]` for a given program instruction.
*/
export type TransactionFn<
export type TransactionInstructionsFn<
IDL extends Idl = Idl,
I extends AllInstructions<IDL> = AllInstructions<IDL>
> = InstructionContextFn<IDL, I, TransactionInstruction[]>;

View File

@ -15,31 +15,28 @@ import {
} from "@solana/web3.js";
import { bs58 } from "./utils/bytes/index.js";
import { isBrowser } from "./utils/common.js";
import {
simulateTransaction,
SuccessfulTxSimulationResponse,
} from "./utils/rpc.js";
import { SuccessfulTxSimulationResponse } from "./utils/rpc.js";
export default interface Provider {
readonly connection: Connection;
readonly publicKey?: PublicKey;
send?(
tx: TransactionInstruction[],
txIxs: TransactionInstruction[],
signers?: Signer[],
opts?: SendOptions
): Promise<TransactionSignature>;
sendAndConfirm?(
tx: TransactionInstruction[],
txIxs: TransactionInstruction[],
signers?: Signer[],
opts?: ConfirmOptions
): Promise<TransactionSignature>;
sendAll?(
txWithSigners: { tx: TransactionInstruction[]; signers?: Signer[] }[],
txWithSigners: { txIxs: TransactionInstruction[]; signers?: Signer[] }[],
opts?: ConfirmOptions
): Promise<Array<TransactionSignature>>;
simulate?(
tx: TransactionInstruction[],
txIxs: TransactionInstruction[],
signers?: Signer[],
commitment?: Commitment,
includeAccounts?: boolean | PublicKey[]
@ -278,8 +275,10 @@ export class AnchorProvider implements Provider {
if (signers) {
tx = await this.wallet.signTransaction(tx);
}
const result = await simulateTransaction(
this.connection,
// TODO: update to the latest version that passes in versioned transactions
// We have to figure out how to update all of the things above as well.
// Since it has to go txIxs -> TxMsg -> VersionedTransaction
const result = await this.connection.simulateTransaction(
tx,
signers,
commitment,

View File

@ -5,7 +5,6 @@ import {
Connection,
PublicKey,
TransactionSignature,
Transaction,
TransactionInstruction,
Commitment,
Signer,
@ -13,6 +12,7 @@ import {
SimulatedTransactionResponse,
SendTransactionError,
Context,
VersionedTransaction,
} from "@solana/web3.js";
import { chunks } from "../utils/common.js";
import { Address, translateAddress } from "../program/common.js";
@ -49,8 +49,8 @@ export async function invoke(
provider = getProvider();
}
const tx = new Transaction();
tx.add(
const txInstructions: TransactionInstruction[] = [];
txInstructions.push(
new TransactionInstruction({
programId,
keys: accounts ?? [],
@ -64,7 +64,7 @@ export async function invoke(
);
}
return await provider.sendAndConfirm(tx, []);
return await provider.sendAndConfirm(txInstructions, []);
}
const GET_MULTIPLE_ACCOUNTS_LIMIT: number = 99;
@ -150,139 +150,6 @@ async function getMultipleAccountsAndContextCore(
return accounts;
}
// copy from @solana/web3.js that has a commitment param
export async function simulateTransaction(
connection: Connection,
transaction: Transaction,
signers?: Array<Signer>,
commitment?: Commitment,
includeAccounts?: boolean | Array<PublicKey>
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
if (signers && signers.length > 0) {
transaction.sign(...signers);
}
// @ts-expect-error
const message = transaction._compile();
const signData = message.serialize();
// @ts-expect-error
const wireTransaction = transaction._serialize(signData);
const encodedTransaction = wireTransaction.toString("base64");
const config: any = {
encoding: "base64",
commitment: commitment ?? connection.commitment,
};
if (includeAccounts) {
const addresses = (
Array.isArray(includeAccounts) ? includeAccounts : message.nonProgramIds()
).map((key) => key.toBase58());
config["accounts"] = {
encoding: "base64",
addresses,
};
}
if (signers) {
config.sigVerify = true;
}
const args = [encodedTransaction, config];
// @ts-expect-error
const unsafeRes = await connection._rpcRequest("simulateTransaction", args);
const res = create(unsafeRes, SimulatedTransactionResponseStruct);
if ("error" in res) {
let logs;
if ("data" in res.error) {
logs = res.error.data.logs;
if (logs && Array.isArray(logs)) {
const traceIndent = "\n ";
const logTrace = traceIndent + logs.join(traceIndent);
console.error(res.error.message, logTrace);
}
}
throw new SendTransactionError(
"failed to simulate transaction: " + res.error.message,
logs
);
}
return res.result;
}
// copy from @solana/web3.js
function jsonRpcResult<T, U>(schema: Struct<T, U>) {
return coerce(createRpcResult(schema), UnknownRpcResult, (value) => {
if ("error" in value) {
return value;
} else {
return {
...value,
result: create(value.result, schema),
};
}
});
}
// copy from @solana/web3.js
const UnknownRpcResult = createRpcResult(unknown());
// copy from @solana/web3.js
function createRpcResult<T, U>(result: Struct<T, U>) {
return union([
pick({
jsonrpc: literal("2.0"),
id: string(),
result,
}),
pick({
jsonrpc: literal("2.0"),
id: string(),
error: pick({
code: unknown(),
message: string(),
data: optional(any()),
}),
}),
]);
}
// copy from @solana/web3.js
function jsonRpcResultAndContext<T, U>(value: Struct<T, U>) {
return jsonRpcResult(
pick({
context: pick({
slot: number(),
}),
value,
})
);
}
// copy from @solana/web3.js
const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(
pick({
err: nullable(union([pick({}), string()])),
logs: nullable(array(string())),
accounts: optional(
nullable(
array(
nullable(
pick({
executable: boolean(),
owner: string(),
lamports: number(),
data: array(string()),
rentEpoch: optional(number()),
})
)
)
)
),
unitsConsumed: optional(number()),
})
);
export type SuccessfulTxSimulationResponse = Omit<
SimulatedTransactionResponse,
"err"

View File

@ -1,9 +1,9 @@
import TransactionFactory from "../src/program/namespace/transaction";
import TransactionInstructionsFactory from "../src/program/namespace/transaction-instructions";
import InstructionFactory from "../src/program/namespace/instruction";
import { BorshCoder } from "../src";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
describe("Transaction", () => {
describe("Transaction Instructions", () => {
const preIx = new TransactionInstruction({
keys: [],
programId: PublicKey.default,
@ -34,10 +34,13 @@ describe("Transaction", () => {
(ixName, ix) => coder.instruction.encode(ixName, ix),
programId
);
const txItem = TransactionFactory.build(idl.instructions[0], ixItem);
const tx = txItem({ accounts: {}, preInstructions: [preIx] });
expect(tx.instructions.length).toBe(2);
expect(tx.instructions[0]).toMatchObject(preIx);
const txIxsItem = TransactionInstructionsFactory.build(
idl.instructions[0],
ixItem
);
const txIxs = txIxsItem({ accounts: {}, preInstructions: [preIx] });
expect(txIxs.length).toBe(2);
expect(txIxs[0]).toMatchObject(preIx);
});
it("should add post instructions after method ix", async () => {
@ -48,10 +51,13 @@ describe("Transaction", () => {
(ixName, ix) => coder.instruction.encode(ixName, ix),
programId
);
const txItem = TransactionFactory.build(idl.instructions[0], ixItem);
const tx = txItem({ accounts: {}, postInstructions: [postIx] });
expect(tx.instructions.length).toBe(2);
expect(tx.instructions[1]).toMatchObject(postIx);
const txIxsItem = TransactionInstructionsFactory.build(
idl.instructions[0],
ixItem
);
const txIxs = txIxsItem({ accounts: {}, postInstructions: [postIx] });
expect(txIxs.length).toBe(2);
expect(txIxs[1]).toMatchObject(postIx);
});
it("should throw error if both preInstructions and instructions are used", async () => {
@ -62,10 +68,17 @@ describe("Transaction", () => {
(ixName, ix) => coder.instruction.encode(ixName, ix),
programId
);
const txItem = TransactionFactory.build(idl.instructions[0], ixItem);
const txIxsItem = TransactionInstructionsFactory.build(
idl.instructions[0],
ixItem
);
expect(() =>
txItem({ accounts: {}, preInstructions: [preIx], instructions: [preIx] })
txIxsItem({
accounts: {},
preInstructions: [preIx],
instructions: [preIx],
})
).toThrow(new Error("instructions is deprecated, use preInstructions"));
});
});