ts: Add program simulate namespace (#266)
This commit is contained in:
parent
425997a12d
commit
9b446dbae1
|
@ -13,6 +13,7 @@ incremented for features.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
* ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
|
||||||
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
|
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
|
||||||
|
|
||||||
## [0.5.0] - 2021-05-07
|
## [0.5.0] - 2021-05-07
|
||||||
|
|
|
@ -57,6 +57,13 @@ pub mod misc {
|
||||||
ctx.accounts.my_account.data = data;
|
ctx.accounts.my_account.data = data;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_simulate(_ctx: Context<TestSimulate>, data: u32) -> ProgramResult {
|
||||||
|
emit!(E1 { data });
|
||||||
|
emit!(E2 { data: 1234 });
|
||||||
|
emit!(E3 { data: 9 });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
|
@ -120,6 +127,9 @@ pub struct TestU16<'info> {
|
||||||
rent: Sysvar<'info, Rent>,
|
rent: Sysvar<'info, Rent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TestSimulate {}
|
||||||
|
|
||||||
#[associated]
|
#[associated]
|
||||||
pub struct TestData {
|
pub struct TestData {
|
||||||
data: u64,
|
data: u64,
|
||||||
|
@ -135,3 +145,18 @@ pub struct Data {
|
||||||
pub struct DataU16 {
|
pub struct DataU16 {
|
||||||
data: u16,
|
data: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[event]
|
||||||
|
pub struct E1 {
|
||||||
|
data: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[event]
|
||||||
|
pub struct E2 {
|
||||||
|
data: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[event]
|
||||||
|
pub struct E3 {
|
||||||
|
data: u32,
|
||||||
|
}
|
||||||
|
|
|
@ -173,4 +173,23 @@ describe("misc", () => {
|
||||||
);
|
);
|
||||||
assert.ok(account.data.toNumber() === 1234);
|
assert.ok(account.data.toNumber() === 1234);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Can retrieve events when simulating a transaction", async () => {
|
||||||
|
const resp = await program.simulate.testSimulate(44);
|
||||||
|
const expectedRaw = [
|
||||||
|
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ invoke [1]",
|
||||||
|
"Program log: NgyCA9omwbMsAAAA",
|
||||||
|
"Program log: fPhuIELK/k7SBAAA",
|
||||||
|
"Program log: jvbowsvlmkcJAAAA",
|
||||||
|
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ consumed 4819 of 200000 compute units",
|
||||||
|
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ success",
|
||||||
|
];
|
||||||
|
assert.ok(JSON.stringify(expectedRaw), resp.raw);
|
||||||
|
assert.ok(resp.events[0].name === "E1");
|
||||||
|
assert.ok(resp.events[0].data.data === 44);
|
||||||
|
assert.ok(resp.events[1].name === "E2");
|
||||||
|
assert.ok(resp.events[1].data.data === 1234);
|
||||||
|
assert.ok(resp.events[2].name === "E3");
|
||||||
|
assert.ok(resp.events[2].data.data === 9);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,20 +2,33 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import * as base64 from "base64-js";
|
import * as base64 from "base64-js";
|
||||||
import * as assert from "assert";
|
import * as assert from "assert";
|
||||||
import Coder, { eventDiscriminator } from "../coder";
|
import Coder, { eventDiscriminator } from "../coder";
|
||||||
|
import { Idl } from "../idl";
|
||||||
|
|
||||||
const LOG_START_INDEX = "Program log: ".length;
|
const LOG_START_INDEX = "Program log: ".length;
|
||||||
|
|
||||||
export class EventParser<T> {
|
// Deserialized event.
|
||||||
|
export type Event = {
|
||||||
|
name: string;
|
||||||
|
data: Object;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EventParser {
|
||||||
private coder: Coder;
|
private coder: Coder;
|
||||||
private programId: PublicKey;
|
private programId: PublicKey;
|
||||||
private eventName: string;
|
// Maps base64 encoded event discriminator to event name.
|
||||||
private discriminator: Buffer;
|
private discriminators: Map<string, string>;
|
||||||
|
|
||||||
constructor(coder: Coder, programId: PublicKey, eventName: string) {
|
constructor(coder: Coder, programId: PublicKey, idl: Idl) {
|
||||||
this.coder = coder;
|
this.coder = coder;
|
||||||
this.programId = programId;
|
this.programId = programId;
|
||||||
this.eventName = eventName;
|
this.discriminators = new Map<string, string>(
|
||||||
this.discriminator = eventDiscriminator(eventName);
|
idl.events === undefined
|
||||||
|
? []
|
||||||
|
: idl.events.map((e) => [
|
||||||
|
base64.fromByteArray(eventDiscriminator(e.name)),
|
||||||
|
e.name,
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each log given, represents an array of messages emitted by
|
// Each log given, represents an array of messages emitted by
|
||||||
|
@ -29,10 +42,9 @@ export class EventParser<T> {
|
||||||
// its emission, thereby allowing us to know if a given log event was
|
// its emission, thereby allowing us to know if a given log event was
|
||||||
// emitted by *this* program. If it was, then we parse the raw string and
|
// emitted by *this* program. If it was, then we parse the raw string and
|
||||||
// emit the event if the string matches the event being subscribed to.
|
// emit the event if the string matches the event being subscribed to.
|
||||||
public parseLogs(logs: string[], callback: (log: T) => void) {
|
public parseLogs(logs: string[], callback: (log: Event) => void) {
|
||||||
const logScanner = new LogScanner(logs);
|
const logScanner = new LogScanner(logs);
|
||||||
const execution = new ExecutionContext(logScanner.next() as string);
|
const execution = new ExecutionContext(logScanner.next() as string);
|
||||||
|
|
||||||
let log = logScanner.next();
|
let log = logScanner.next();
|
||||||
while (log !== null) {
|
while (log !== null) {
|
||||||
let [event, newProgram, didPop] = this.handleLog(execution, log);
|
let [event, newProgram, didPop] = this.handleLog(execution, log);
|
||||||
|
@ -44,11 +56,57 @@ export class EventParser<T> {
|
||||||
}
|
}
|
||||||
if (didPop) {
|
if (didPop) {
|
||||||
execution.pop();
|
execution.pop();
|
||||||
|
// Skip the "success" log, which always follows the consumed log.
|
||||||
|
logScanner.next();
|
||||||
}
|
}
|
||||||
log = logScanner.next();
|
log = logScanner.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main log handler. Returns a three element array of the event, the
|
||||||
|
// next program that was invoked for CPI, and a boolean indicating if
|
||||||
|
// a program has completed execution (and thus should be popped off the
|
||||||
|
// execution stack).
|
||||||
|
private handleLog(
|
||||||
|
execution: ExecutionContext,
|
||||||
|
log: string
|
||||||
|
): [Event | null, string | null, boolean] {
|
||||||
|
// Executing program is this program.
|
||||||
|
if (execution.program() === this.programId.toString()) {
|
||||||
|
return this.handleProgramLog(log);
|
||||||
|
}
|
||||||
|
// Executing program is not this program.
|
||||||
|
else {
|
||||||
|
return [null, ...this.handleSystemLog(log)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles logs from *this* program.
|
||||||
|
private handleProgramLog(
|
||||||
|
log: string
|
||||||
|
): [Event | null, string | null, boolean] {
|
||||||
|
// This is a `msg!` log.
|
||||||
|
if (log.startsWith("Program log:")) {
|
||||||
|
const logStr = log.slice(LOG_START_INDEX);
|
||||||
|
const logArr = Buffer.from(base64.toByteArray(logStr));
|
||||||
|
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
||||||
|
// Only deserialize if the discriminator implies a proper event.
|
||||||
|
let event = null;
|
||||||
|
let eventName = this.discriminators.get(disc);
|
||||||
|
if (eventName !== undefined) {
|
||||||
|
event = {
|
||||||
|
name: eventName,
|
||||||
|
data: this.coder.events.decode(eventName, logArr.slice(8)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return [event, null, false];
|
||||||
|
}
|
||||||
|
// System log.
|
||||||
|
else {
|
||||||
|
return [null, ...this.handleSystemLog(log)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handles logs when the current program being executing is *not* this.
|
// Handles logs when the current program being executing is *not* this.
|
||||||
private handleSystemLog(log: string): [string | null, boolean] {
|
private handleSystemLog(log: string): [string | null, boolean] {
|
||||||
// System component.
|
// System component.
|
||||||
|
@ -68,44 +126,6 @@ export class EventParser<T> {
|
||||||
return [null, false];
|
return [null, false];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles logs from *this* program.
|
|
||||||
private handleProgramLog(log: string): [T | null, string | null, boolean] {
|
|
||||||
// This is a `msg!` log.
|
|
||||||
if (log.startsWith("Program log:")) {
|
|
||||||
const logStr = log.slice(LOG_START_INDEX);
|
|
||||||
const logArr = Buffer.from(base64.toByteArray(logStr));
|
|
||||||
const disc = logArr.slice(0, 8);
|
|
||||||
// Only deserialize if the discriminator implies a proper event.
|
|
||||||
let event = null;
|
|
||||||
if (disc.equals(this.discriminator)) {
|
|
||||||
event = this.coder.events.decode(this.eventName, logArr.slice(8));
|
|
||||||
}
|
|
||||||
return [event, null, false];
|
|
||||||
}
|
|
||||||
// System log.
|
|
||||||
else {
|
|
||||||
return [null, ...this.handleSystemLog(log)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main log handler. Returns a three element array of the event, the
|
|
||||||
// next program that was invoked for CPI, and a boolean indicating if
|
|
||||||
// a program has completed execution (and thus should be popped off the
|
|
||||||
// execution stack).
|
|
||||||
private handleLog(
|
|
||||||
execution: ExecutionContext,
|
|
||||||
log: string
|
|
||||||
): [T | null, string | null, boolean] {
|
|
||||||
// Executing program is this program.
|
|
||||||
if (execution.program() === this.programId.toString()) {
|
|
||||||
return this.handleProgramLog(log);
|
|
||||||
}
|
|
||||||
// Executing program is not this program.
|
|
||||||
else {
|
|
||||||
return [null, ...this.handleSystemLog(log)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stack frame execution context, allowing one to track what program is
|
// Stack frame execution context, allowing one to track what program is
|
||||||
|
|
|
@ -3,7 +3,14 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import Provider from "../provider";
|
import Provider from "../provider";
|
||||||
import { Idl, idlAddress, decodeIdlAccount } from "../idl";
|
import { Idl, idlAddress, decodeIdlAccount } from "../idl";
|
||||||
import Coder from "../coder";
|
import Coder from "../coder";
|
||||||
import NamespaceFactory, { Rpcs, Ixs, Txs, Accounts, State } from "./namespace";
|
import NamespaceFactory, {
|
||||||
|
Rpcs,
|
||||||
|
Ixs,
|
||||||
|
Txs,
|
||||||
|
Accounts,
|
||||||
|
State,
|
||||||
|
Simulate,
|
||||||
|
} from "./namespace";
|
||||||
import { getProvider } from "../";
|
import { getProvider } from "../";
|
||||||
import { decodeUtf8 } from "../utils";
|
import { decodeUtf8 } from "../utils";
|
||||||
import { EventParser } from "./event";
|
import { EventParser } from "./event";
|
||||||
|
@ -23,8 +30,7 @@ export class Program {
|
||||||
readonly idl: Idl;
|
readonly idl: Idl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async functions to invoke instructions against a Solana priogram running
|
* Async functions to invoke instructions against an Anchor program.
|
||||||
* on a cluster.
|
|
||||||
*/
|
*/
|
||||||
readonly rpc: Rpcs;
|
readonly rpc: Rpcs;
|
||||||
|
|
||||||
|
@ -43,6 +49,11 @@ export class Program {
|
||||||
*/
|
*/
|
||||||
readonly transaction: Txs;
|
readonly transaction: Txs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async functions to simulate instructions against an Anchor program.
|
||||||
|
*/
|
||||||
|
readonly simulate: Simulate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coder for serializing rpc requests.
|
* Coder for serializing rpc requests.
|
||||||
*/
|
*/
|
||||||
|
@ -67,7 +78,7 @@ export class Program {
|
||||||
const coder = new Coder(idl);
|
const coder = new Coder(idl);
|
||||||
|
|
||||||
// Build the dynamic namespaces.
|
// Build the dynamic namespaces.
|
||||||
const [rpcs, ixs, txs, accounts, state] = NamespaceFactory.build(
|
const [rpcs, ixs, txs, accounts, state, simulate] = NamespaceFactory.build(
|
||||||
idl,
|
idl,
|
||||||
coder,
|
coder,
|
||||||
programId,
|
programId,
|
||||||
|
@ -79,6 +90,7 @@ export class Program {
|
||||||
this.account = accounts;
|
this.account = accounts;
|
||||||
this.coder = coder;
|
this.coder = coder;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
this.simulate = simulate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,22 +117,20 @@ export class Program {
|
||||||
/**
|
/**
|
||||||
* Invokes the given callback everytime the given event is emitted.
|
* Invokes the given callback everytime the given event is emitted.
|
||||||
*/
|
*/
|
||||||
public addEventListener<T>(
|
public addEventListener(
|
||||||
eventName: string,
|
eventName: string,
|
||||||
callback: (event: T, slot: number) => void
|
callback: (event: any, slot: number) => void
|
||||||
): number {
|
): number {
|
||||||
const eventParser = new EventParser<T>(
|
const eventParser = new EventParser(this.coder, this.programId, this.idl);
|
||||||
this.coder,
|
|
||||||
this.programId,
|
|
||||||
eventName
|
|
||||||
);
|
|
||||||
return this.provider.connection.onLogs(this.programId, (logs, ctx) => {
|
return this.provider.connection.onLogs(this.programId, (logs, ctx) => {
|
||||||
if (logs.err) {
|
if (logs.err) {
|
||||||
console.error(logs);
|
console.error(logs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eventParser.parseLogs(logs.logs, (event) => {
|
eventParser.parseLogs(logs.logs, (event) => {
|
||||||
callback(event, ctx.slot);
|
if (event.name === eventName) {
|
||||||
|
callback(event.data, ctx.slot);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import InstructionNamespace, { Ixs } from "./instruction";
|
||||||
import TransactionNamespace, { Txs } from "./transaction";
|
import TransactionNamespace, { Txs } from "./transaction";
|
||||||
import RpcNamespace, { Rpcs } from "./rpc";
|
import RpcNamespace, { Rpcs } from "./rpc";
|
||||||
import AccountNamespace, { Accounts } from "./account";
|
import AccountNamespace, { Accounts } from "./account";
|
||||||
|
import SimulateNamespace, { Simulate } from "./simulate";
|
||||||
|
|
||||||
// Re-exports.
|
// Re-exports.
|
||||||
export { State } from "./state";
|
export { State } from "./state";
|
||||||
|
@ -16,6 +17,7 @@ export { Ixs } from "./instruction";
|
||||||
export { Txs, TxFn } from "./transaction";
|
export { Txs, TxFn } from "./transaction";
|
||||||
export { Rpcs, RpcFn } from "./rpc";
|
export { Rpcs, RpcFn } from "./rpc";
|
||||||
export { Accounts, AccountFn, ProgramAccount } from "./account";
|
export { Accounts, AccountFn, ProgramAccount } from "./account";
|
||||||
|
export { Simulate } from "./simulate";
|
||||||
|
|
||||||
export default class NamespaceFactory {
|
export default class NamespaceFactory {
|
||||||
/**
|
/**
|
||||||
|
@ -28,12 +30,14 @@ export default class NamespaceFactory {
|
||||||
coder: Coder,
|
coder: Coder,
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): [Rpcs, Ixs, Txs, Accounts, State] {
|
): [Rpcs, Ixs, Txs, Accounts, State, Simulate] {
|
||||||
const idlErrors = parseIdlErrors(idl);
|
const idlErrors = parseIdlErrors(idl);
|
||||||
|
|
||||||
const rpcs: Rpcs = {};
|
const rpcs: Rpcs = {};
|
||||||
const ixFns: Ixs = {};
|
const ixFns: Ixs = {};
|
||||||
const txFns: Txs = {};
|
const txFns: Txs = {};
|
||||||
|
const simulateFns: Simulate = {};
|
||||||
|
|
||||||
const state = StateNamespace.build(
|
const state = StateNamespace.build(
|
||||||
idl,
|
idl,
|
||||||
coder,
|
coder,
|
||||||
|
@ -46,18 +50,28 @@ export default class NamespaceFactory {
|
||||||
const ix = InstructionNamespace.build(idlIx, coder, programId);
|
const ix = InstructionNamespace.build(idlIx, coder, programId);
|
||||||
const tx = TransactionNamespace.build(idlIx, ix);
|
const tx = TransactionNamespace.build(idlIx, ix);
|
||||||
const rpc = RpcNamespace.build(idlIx, tx, idlErrors, provider);
|
const rpc = RpcNamespace.build(idlIx, tx, idlErrors, provider);
|
||||||
|
const simulate = SimulateNamespace.build(
|
||||||
|
idlIx,
|
||||||
|
tx,
|
||||||
|
idlErrors,
|
||||||
|
provider,
|
||||||
|
coder,
|
||||||
|
programId,
|
||||||
|
idl
|
||||||
|
);
|
||||||
|
|
||||||
const name = camelCase(idlIx.name);
|
const name = camelCase(idlIx.name);
|
||||||
|
|
||||||
ixFns[name] = ix;
|
ixFns[name] = ix;
|
||||||
txFns[name] = tx;
|
txFns[name] = tx;
|
||||||
rpcs[name] = rpc;
|
rpcs[name] = rpc;
|
||||||
|
simulateFns[name] = simulate;
|
||||||
});
|
});
|
||||||
|
|
||||||
const accountFns = idl.accounts
|
const accountFns = idl.accounts
|
||||||
? AccountNamespace.build(idl, coder, programId, provider)
|
? AccountNamespace.build(idl, coder, programId, provider)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
return [rpcs, ixFns, txFns, accountFns, state];
|
return [rpcs, ixFns, txFns, accountFns, state, simulateFns];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import Provider from "../../provider";
|
||||||
|
import { IdlInstruction } from "../../idl";
|
||||||
|
import { translateError } from "../common";
|
||||||
|
import { splitArgsAndCtx } from "../context";
|
||||||
|
import { TxFn } from "./transaction";
|
||||||
|
import { EventParser } from "../event";
|
||||||
|
import Coder from "../../coder";
|
||||||
|
import { Idl } from "../../idl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically generated simualte namespace.
|
||||||
|
*/
|
||||||
|
export interface Simulate {
|
||||||
|
[key: string]: SimulateFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RpcFn is a single rpc method generated from an IDL.
|
||||||
|
*/
|
||||||
|
export type SimulateFn = (...args: any[]) => Promise<SimulateResponse>;
|
||||||
|
|
||||||
|
type SimulateResponse = {
|
||||||
|
events: Event[];
|
||||||
|
raw: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SimulateNamespace {
|
||||||
|
// Builds the rpc namespace.
|
||||||
|
public static build(
|
||||||
|
idlIx: IdlInstruction,
|
||||||
|
txFn: TxFn,
|
||||||
|
idlErrors: Map<number, string>,
|
||||||
|
provider: Provider,
|
||||||
|
coder: Coder,
|
||||||
|
programId: PublicKey,
|
||||||
|
idl: Idl
|
||||||
|
): SimulateFn {
|
||||||
|
const simulate = async (...args: any[]): Promise<SimulateResponse> => {
|
||||||
|
const tx = txFn(...args);
|
||||||
|
const [_, ctx] = splitArgsAndCtx(idlIx, [...args]);
|
||||||
|
let resp = undefined;
|
||||||
|
try {
|
||||||
|
resp = await provider.simulate(tx, ctx.signers, ctx.options);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Translating error", err);
|
||||||
|
let translatedErr = translateError(idlErrors, err);
|
||||||
|
if (translatedErr === null) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
throw translatedErr;
|
||||||
|
}
|
||||||
|
if (resp === undefined) {
|
||||||
|
throw new Error("Unable to simulate transaction");
|
||||||
|
}
|
||||||
|
if (resp.value.err) {
|
||||||
|
throw new Error(`Simulate error: ${resp.value.err.toString()}`);
|
||||||
|
}
|
||||||
|
const logs = resp.value.logs;
|
||||||
|
if (!logs) {
|
||||||
|
throw new Error("Simulated logs not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
if (idl.events) {
|
||||||
|
let parser = new EventParser(coder, programId, idl);
|
||||||
|
parser.parseLogs(logs, (event) => {
|
||||||
|
events.push(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { events, raw: logs };
|
||||||
|
};
|
||||||
|
|
||||||
|
return simulate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ import {
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
ConfirmOptions,
|
ConfirmOptions,
|
||||||
sendAndConfirmRawTransaction,
|
sendAndConfirmRawTransaction,
|
||||||
|
RpcResponseAndContext,
|
||||||
|
SimulatedTransactionResponse,
|
||||||
|
Commitment,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
export default class Provider {
|
export default class Provider {
|
||||||
|
@ -134,6 +137,35 @@ export default class Provider {
|
||||||
|
|
||||||
return sigs;
|
return sigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async simulate(
|
||||||
|
tx: Transaction,
|
||||||
|
signers?: Array<Account | undefined>,
|
||||||
|
opts?: ConfirmOptions
|
||||||
|
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
||||||
|
if (signers === undefined) {
|
||||||
|
signers = [];
|
||||||
|
}
|
||||||
|
if (opts === undefined) {
|
||||||
|
opts = this.opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signerKps = signers.filter((s) => s !== undefined) as Array<Account>;
|
||||||
|
const signerPubkeys = [this.wallet.publicKey].concat(
|
||||||
|
signerKps.map((s) => s.publicKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
tx.setSigners(...signerPubkeys);
|
||||||
|
tx.recentBlockhash = (
|
||||||
|
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
||||||
|
).blockhash;
|
||||||
|
|
||||||
|
await this.wallet.signTransaction(tx);
|
||||||
|
signerKps.forEach((kp) => {
|
||||||
|
tx.partialSign(kp);
|
||||||
|
});
|
||||||
|
return await simulateTransaction(this.connection, tx, opts.commitment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendTxRequest = {
|
export type SendTxRequest = {
|
||||||
|
@ -182,3 +214,30 @@ export class NodeWallet implements Wallet {
|
||||||
return this.payer.publicKey;
|
return this.payer.publicKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy of Connection.simulateTransaction that takes a commitment parameter.
|
||||||
|
async function simulateTransaction(
|
||||||
|
connection: Connection,
|
||||||
|
transaction: Transaction,
|
||||||
|
commitment: Commitment
|
||||||
|
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
||||||
|
// @ts-ignore
|
||||||
|
transaction.recentBlockhash = await connection._recentBlockhash(
|
||||||
|
// @ts-ignore
|
||||||
|
connection._disableBlockhashCaching
|
||||||
|
);
|
||||||
|
|
||||||
|
const signData = transaction.serializeMessage();
|
||||||
|
// @ts-ignore
|
||||||
|
const wireTransaction = transaction._serialize(signData);
|
||||||
|
const encodedTransaction = wireTransaction.toString("base64");
|
||||||
|
const config: any = { encoding: "base64", commitment };
|
||||||
|
const args = [encodedTransaction, config];
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const res = await connection._rpcRequest("simulateTransaction", args);
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error("failed to simulate transaction: " + res.error.message);
|
||||||
|
}
|
||||||
|
return res.result;
|
||||||
|
}
|
||||||
|
|
|
@ -29,9 +29,7 @@ export default new Proxy({} as any, {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectRoot === undefined) {
|
if (projectRoot === undefined) {
|
||||||
throw new Error(
|
throw new Error("Could not find workspace root.");
|
||||||
"Could not find workspace root. Perhaps set the `OASIS_WORKSPACE` env var?"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
find
|
find
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"include": ["src"],
|
"include": ["./src/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"typeRoots": ["types/", "node_modules/@types"],
|
"typeRoots": ["types/", "node_modules/@types"],
|
||||||
|
|
Loading…
Reference in New Issue