ts: make `Provider` an interface, add `AnchorProvider` class, and update provider functions (#1707)

This commit is contained in:
Paul 2022-04-11 15:48:58 -04:00 committed by GitHub
parent 4d9bd6adc6
commit 537d470954
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 399 additions and 160 deletions

3
.gitmodules vendored
View File

@ -7,12 +7,15 @@
[submodule "examples/cfo/deps/swap"]
path = tests/cfo/deps/swap
url = https://github.com/project-serum/swap.git
branch = armani/cfo
[submodule "examples/cfo/deps/stake"]
path = tests/cfo/deps/stake
url = https://github.com/project-serum/stake.git
branch = armani/cfo
[submodule "examples/permissioned-markets/deps/serum-dex"]
path = tests/permissioned-markets/deps/serum-dex
url = https://github.com/project-serum/serum-dex
[submodule "tests/auction-house"]
path = tests/auction-house
url = https://github.com/armaniferrante/auction-house
branch = armani/pda

View File

@ -35,6 +35,7 @@ The minor version will be incremented upon a breaking change and the patch versi
* spl: Re-export the `spl_token` crate ([#1665](https://github.com/project-serum/anchor/pull/1665)).
* lang, cli, spl: Update solana toolchain to v1.9.13 ([#1653](https://github.com/project-serum/anchor/pull/1653) and [#1751](https://github.com/project-serum/anchor/pull/1751)).
* lang: `Program` type now deserializes `programdata_address` only on demand ([#1723](https://github.com/project-serum/anchor/pull/1723)).
* ts: Make `Provider` an interface and adjust its signatures and add `AnchorProvider` implementor class ([#1707](https://github.com/project-serum/anchor/pull/1707)).
* spl: Change "to" to "from" in `token::burn` ([#1080](https://github.com/project-serum/anchor/pull/1080)).
## [0.23.0] - 2022-03-20

View File

@ -5,7 +5,7 @@
const anchor = require("@project-serum/anchor");
// Configure the local cluster.
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(anchor.AnchorProvider.local());
async function main() {
// #region main

View File

@ -2,7 +2,7 @@ const anchor = require("@project-serum/anchor");
describe("basic-0", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(anchor.AnchorProvider.local());
it("Uses the workspace to invoke the initialize instruction", async () => {
// #region code

View File

@ -4,7 +4,7 @@ const { SystemProgram } = anchor.web3;
describe("basic-1", () => {
// Use a local provider.
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

View File

@ -3,7 +3,7 @@ const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-2", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

View File

@ -3,7 +3,7 @@ const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-3", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

View File

@ -2,7 +2,7 @@ const assert = require("assert");
const anchor = require("@project-serum/anchor");
describe("basic-4", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

@ -1 +1 @@
Subproject commit 967650c531ba0f23c88374875ccfcecb9b1a7800
Subproject commit 2d4d9583467d697267b87b250af8c8bd360f3745

View File

@ -6,7 +6,7 @@ import { assert } from "chai";
import { BpfUpgradeableState } from "../target/types/bpf_upgradeable_state";
describe("bpf_upgradeable_state", () => {
const provider = anchor.Provider.env();
const provider = anchor.AnchorProvider.env();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

View File

@ -5,7 +5,11 @@ const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
describe("cashiers-check", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const provider = anchor.AnchorProvider.env();
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
provider.send = provider.sendAndConfirm;
anchor.setProvider(provider);
const program = anchor.workspace.CashiersCheck;

@ -1 +1 @@
Subproject commit 9c41642dffbb334e1e39c616cd6a645d91768d3e
Subproject commit b68b9a6fdea2c8befe95aa0f1fcca394579fc3bd

@ -1 +1 @@
Subproject commit 3da36aaae7af6ce901d68c0280aac34817fe7fd8
Subproject commit 2ac6045cf37436c4702fbe12678f3a6243feecb1

View File

@ -6,7 +6,7 @@ const anchor = require("@project-serum/anchor");
const { Market, OpenOrders } = require("@project-serum/serum");
const Account = anchor.web3.Account;
const Program = anchor.Program;
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
const secret = JSON.parse(fs.readFileSync("./scripts/market-maker.json"));
const MARKET_MAKER = new Account(secret);
const PublicKey = anchor.web3.PublicKey;

View File

@ -5,7 +5,10 @@
const utils = require("../tests/utils");
const fs = require("fs");
const anchor = require("@project-serum/anchor");
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
provider.send = provider.sendAndConfirm;
async function main() {
ORDERBOOK_ENV = await utils.initMarket({

View File

@ -9,7 +9,7 @@ const { runTradeBot } = require("../tests/utils");
async function main() {
const market = new PublicKey(process.argv[2]);
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
runTradeBot(market, provider);
}

View File

@ -23,9 +23,13 @@ const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
const FEES = "6160355581";
describe("cfo", () => {
anchor.setProvider(anchor.Provider.env());
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Cfo;
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
program.provider.send = provider.sendAndConfirm;
const sweepAuthority = program.provider.wallet.publicKey;
let officer, srmVault, usdcVault, bVault, stake, treasury;
let officerBump, srmBump, usdcBump, bBump, stakeBump, treasuryBump;

View File

@ -125,7 +125,7 @@ async function fundAccount({ provider, mints }) {
};
// Transfer lamports to market maker.
await provider.send(
await provider.sendAndConfirm(
(() => {
const tx = new Transaction();
tx.add(
@ -155,7 +155,7 @@ async function fundAccount({ provider, mints }) {
MARKET_MAKER.publicKey
);
await provider.send(
await provider.sendAndConfirm(
(() => {
const tx = new Transaction();
tx.add(
@ -220,7 +220,10 @@ async function setupMarket({
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
await provider.send(transaction, signers.concat(marketMaker.account));
await provider.sendAndConfirm(
transaction,
signers.concat(marketMaker.account)
);
}
for (let k = 0; k < bids.length; k += 1) {
@ -239,7 +242,10 @@ async function setupMarket({
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
await provider.send(transaction, signers.concat(marketMaker.account));
await provider.sendAndConfirm(
transaction,
signers.concat(marketMaker.account)
);
}
return [MARKET_A_USDC, vaultOwner];
@ -527,7 +533,7 @@ async function runTradeBot(market, provider, iterations = undefined) {
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
let txSig = await provider.send(tx_ask, sigs_ask.concat(maker));
let txSig = await provider.sendAndConfirm(tx_ask, sigs_ask.concat(maker));
console.log("Ask", txSig);
// Take.
@ -545,7 +551,7 @@ async function runTradeBot(market, provider, iterations = undefined) {
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
txSig = await provider.send(tx_bid, sigs_bid.concat(taker));
txSig = await provider.sendAndConfirm(tx_bid, sigs_bid.concat(taker));
console.log("Bid", txSig);
await sleep(1000);

View File

@ -5,7 +5,10 @@ const utils = require("../../deps/stake/tests/utils");
const lockup = anchor.workspace.Lockup;
const registry = anchor.workspace.Registry;
const provider = anchor.Provider.env();
const provider = anchor.AnchorProvider.env();
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
provider.send = provider.sendAndConfirm;
let lockupAddress = null;
let mint = null;

View File

@ -4,7 +4,7 @@ const { PublicKey } = anchor.web3;
describe("chat", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
// Program client handle.
const program = anchor.workspace.Chat;

View File

@ -2,7 +2,7 @@ const { assert } = require("chai");
const anchor = require("@project-serum/anchor");
describe("composite", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);

View File

@ -8,7 +8,7 @@ import { Caller } from "../target/types/caller";
const { SystemProgram } = anchor.web3;
describe("CPI return", () => {
const provider = anchor.Provider.env();
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const callerProgram = anchor.workspace.Caller as Program<Caller>;

View File

@ -6,7 +6,7 @@ import { Keypair, SYSVAR_RENT_PUBKEY } from "@solana/web3.js";
describe("custom-coder", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
// Client.
const program = Spl.token();

View File

@ -5,7 +5,7 @@ import { DeclareId } from "../target/types/declare_id";
import { assert } from "chai";
describe("declare_id", () => {
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(anchor.AnchorProvider.local());
const program = anchor.workspace.DeclareId as Program<DeclareId>;
it("throws error!", async () => {

View File

@ -64,7 +64,7 @@ const withLogTest = async (callback, expectedLogs) => {
describe("errors", () => {
// Configure the client to use the local cluster.
const localProvider = anchor.Provider.local();
const localProvider = anchor.AnchorProvider.local();
localProvider.opts.skipPreflight = true;
// processed failed tx do not result in AnchorErrors in the client
// because we cannot get logs for them (only through overkill `onLogs`)
@ -279,7 +279,7 @@ describe("errors", () => {
data: program.coder.instruction.encode("signer_error", {}),
})
);
await program.provider.send(tx);
await program.provider.sendAndConfirm(tx);
assert.ok(false);
} catch (err) {
anchor.getProvider().connection.removeOnLogsListener(listener);

View File

@ -8,7 +8,7 @@ import { Escrow } from "../target/types/escrow";
type EscrowAccount = IdlAccounts<Escrow>["escrowAccount"];
describe("escrow", () => {
const provider = anchor.Provider.env();
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Escrow as Program<Escrow>;

View File

@ -3,7 +3,7 @@ const { assert } = require("chai");
describe("events", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;
it("Is initialized!", async () => {

View File

@ -6,7 +6,7 @@ import { assert } from "chai";
describe("floats", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Floats as Program<Floats>;

View File

@ -14,7 +14,7 @@ const {
const { token } = require("@project-serum/anchor/dist/cjs/utils");
describe("ido-pool", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);
@ -182,7 +182,7 @@ describe("ido-pool", () => {
let createUserUsdcTrns = new anchor.web3.Transaction().add(
createUserUsdcInstr
);
await provider.send(createUserUsdcTrns);
await provider.sendAndConfirm(createUserUsdcTrns);
await usdcMintAccount.mintTo(
userUsdc,
provider.wallet.publicKey,
@ -283,7 +283,7 @@ describe("ido-pool", () => {
let createSecondUserUsdcTrns = new anchor.web3.Transaction();
createSecondUserUsdcTrns.add(transferSolInstr);
createSecondUserUsdcTrns.add(createSecondUserUsdcInstr);
await provider.send(createSecondUserUsdcTrns);
await provider.sendAndConfirm(createSecondUserUsdcTrns);
await usdcMintAccount.mintTo(
secondUserUsdc,
provider.wallet.publicKey,

View File

@ -4,7 +4,7 @@ const nativeAssert = require("assert");
describe("interface", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const counter = anchor.workspace.Counter;
const counterAuth = anchor.workspace.CounterAuth;

View File

@ -9,7 +9,10 @@ anchor.utils.features.set("anchor-deprecated-state");
describe("Lockup and Registry", () => {
// Read the provider from the configured environmnet.
const provider = anchor.Provider.env();
const provider = anchor.AnchorProvider.env();
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
provider.send = provider.sendAndConfirm;
// Configure the client to use the provider.
anchor.setProvider(provider);

View File

@ -11,7 +11,7 @@ const { assert } = require("chai");
describe("miscNonRentExempt", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Misc as Program<Misc>;
it("init_if_needed checks rent_exemption if init is not needed", async () => {

View File

@ -20,7 +20,7 @@ const miscIdl = require("../target/idl/misc.json");
describe("misc", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Misc as Program<Misc>;
const misc2Program = anchor.workspace.Misc2 as Program<Misc2>;
@ -760,7 +760,7 @@ describe("misc", () => {
const anotherProgram = new anchor.Program(
miscIdl,
program.programId,
new anchor.Provider(
new anchor.AnchorProvider(
program.provider.connection,
new anchor.Wallet(anchor.web3.Keypair.generate()),
{ commitment: program.provider.connection.commitment }

View File

@ -6,7 +6,7 @@ import { MultipleSuites } from "../../target/types/multiple_suites";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -6,7 +6,7 @@ import { MultipleSuites } from "../../../target/types/multiple_suites";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -6,7 +6,7 @@ import { MultipleSuites } from "../../../target/types/multiple_suites";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -6,7 +6,7 @@ import { MultipleSuites } from "../../target/types/multiple_suites";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -6,7 +6,7 @@ import { assert } from "chai";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -6,7 +6,7 @@ import { MultipleSuites } from "../../../target/types/multiple_suites";
describe("multiple-suites", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.MultipleSuites as Program<MultipleSuites>;

View File

@ -3,7 +3,7 @@ const { assert } = require("chai");
describe("multisig", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Multisig;

View File

@ -9,7 +9,7 @@ const encode = anchor.utils.bytes.utf8.encode;
describe("typescript", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.PdaDerivation as Program<PdaDerivation>;
const base = Keypair.generate();

View File

@ -4,7 +4,7 @@ import { assert } from "chai";
import { createPriceFeed, setFeedPrice, getFeedData } from "./oracleUtils";
describe("pyth-oracle", () => {
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Pyth as Program;
it("initialize", async () => {

View File

@ -4,7 +4,7 @@ import { SafetyChecks } from "../target/types/safety_checks";
describe("safety-checks", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.SafetyChecks as Program<SafetyChecks>;

View File

@ -2,7 +2,7 @@ const anchor = require("@project-serum/anchor");
const { assert } = require("chai");
describe("token", () => {
const provider = anchor.Provider.local();
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);
@ -118,7 +118,7 @@ async function createMint(provider, authority) {
const tx = new anchor.web3.Transaction();
tx.add(...instructions);
await provider.send(tx, [mint]);
await provider.sendAndConfirm(tx, [mint]);
return mint.publicKey;
}
@ -147,7 +147,7 @@ async function createTokenAccount(provider, mint, owner) {
tx.add(
...(await createTokenAccountInstrs(provider, vault.publicKey, mint, owner))
);
await provider.send(tx, [vault]);
await provider.sendAndConfirm(tx, [vault]);
return vault.publicKey;
}

View File

@ -11,7 +11,11 @@ const TAKER_FEE = 0.0022;
describe("swap", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const provider = anchor.AnchorProvider.env();
// hack so we don't have to update serum-common library
// to the new AnchorProvider class and Provider interface
provider.send = provider.sendAndConfirm;
anchor.setProvider(provider);
// Swap program client.
const program = anchor.workspace.Swap;

View File

@ -196,7 +196,7 @@ async function fundAccount({ provider, mints }) {
};
// Transfer lamports to market maker.
await provider.send(
await provider.sendAndConfirm(
(() => {
const tx = new Transaction();
tx.add(
@ -226,7 +226,7 @@ async function fundAccount({ provider, mints }) {
MARKET_MAKER.publicKey
);
await provider.send(
await provider.sendAndConfirm(
(() => {
const tx = new Transaction();
tx.add(
@ -291,7 +291,10 @@ async function setupMarket({
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
await provider.send(transaction, signers.concat(marketMaker.account));
await provider.sendAndConfirm(
transaction,
signers.concat(marketMaker.account)
);
}
for (let k = 0; k < bids.length; k += 1) {
@ -310,7 +313,10 @@ async function setupMarket({
feeDiscountPubkey: null,
selfTradeBehavior: "abortTransaction",
});
await provider.send(transaction, signers.concat(marketMaker.account));
await provider.sendAndConfirm(
transaction,
signers.concat(marketMaker.account)
);
}
return MARKET_A_USDC;

View File

@ -3,7 +3,7 @@ const splToken = require("@solana/spl-token");
const { assert } = require("chai");
describe("system_accounts", () => {
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(anchor.AnchorProvider.local());
const program = anchor.workspace.SystemAccounts;
const authority = program.provider.wallet.payer;
const wallet = anchor.web3.Keypair.generate();

View File

@ -3,7 +3,7 @@ const { assert } = require("chai");
describe("sysvars", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.local());
anchor.setProvider(anchor.AnchorProvider.local());
const program = anchor.workspace.Sysvars;
it("Is initialized!", async () => {

View File

@ -1,7 +1,7 @@
const anchor = require("@project-serum/anchor");
describe("tictactoe", () => {
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Tictactoe;
let dashboard = anchor.web3.Keypair.generate();
let game = anchor.web3.Keypair.generate();

View File

@ -2,7 +2,7 @@ import * as anchor from "@project-serum/anchor";
describe("typescript", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
it("Is initialized!", async () => {
// Add your test here.

View File

@ -5,7 +5,7 @@ import { ValidatorClone } from "../target/types/validator_clone";
describe("validator-clone", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.ValidatorClone as Program<ValidatorClone>;
const connection = program.provider.connection;

View File

@ -6,7 +6,7 @@ const BN = anchor.BN;
describe("zero-copy", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.ZeroCopy;
const programCpi = anchor.workspace.ZeroCpi;

View File

@ -3,7 +3,12 @@ import { isBrowser } from "./utils/common.js";
export { default as BN } from "bn.js";
export * as web3 from "@solana/web3.js";
export { default as Provider, getProvider, setProvider } from "./provider.js";
export {
default as Provider,
getProvider,
setProvider,
AnchorProvider,
} from "./provider.js";
export * from "./error.js";
export { Instruction } from "./coder/borsh/instruction.js";
export { Idl } from "./idl.js";

View File

@ -58,6 +58,13 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
// Signers default to the provider.
if (accountDesc.isSigner && !this._accounts[accountDescName]) {
// @ts-expect-error
if (this._provider.wallet === undefined) {
throw new Error(
"This function requires the Provider interface implementor to have a 'wallet' field."
);
}
// @ts-expect-error
this._accounts[accountDescName] = this._provider.wallet.publicKey;
continue;
}

View File

@ -287,7 +287,15 @@ export class AccountClient<
): Promise<TransactionInstruction> {
const size = this.size;
// @ts-expect-error
if (this._provider.wallet === undefined) {
throw new Error(
"This function requires the Provider interface implementor to have a 'wallet' field."
);
}
return SystemProgram.createAccount({
// @ts-expect-error
fromPubkey: this._provider.wallet.publicKey,
newAccountPubkey: signer.publicKey,
space: sizeOverride ?? size,

View File

@ -20,8 +20,17 @@ export default class RpcFactory {
const rpc: RpcFn<IDL, I> = async (...args) => {
const tx = txFn(...args);
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
if (provider.sendAndConfirm === undefined) {
throw new Error(
"This function requires 'Provider.sendAndConfirm' to be implemented."
);
}
try {
return await provider.send(tx, ctx.signers, ctx.options);
return await provider.sendAndConfirm(
tx,
ctx.signers ?? [],
ctx.options
);
} catch (err) {
throw translateError(err, idlErrors);
}

View File

@ -1,9 +1,6 @@
import {
PublicKey,
RpcResponseAndContext,
SimulatedTransactionResponse,
} from "@solana/web3.js";
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 { EventParser, Event } from "../event.js";
@ -30,21 +27,25 @@ export default class SimulateFactory {
const simulate: SimulateFn<IDL> = async (...args) => {
const tx = txFn(...args);
const [, ctx] = splitArgsAndCtx(idlIx, [...args]);
let resp:
| RpcResponseAndContext<SimulatedTransactionResponse>
| undefined = undefined;
let resp: SuccessfulTxSimulationResponse | undefined = undefined;
if (provider.simulate === undefined) {
throw new Error(
"This function requires 'Provider.simulate' to be implemented."
);
}
try {
resp = await provider!.simulate(tx, ctx.signers, ctx.options);
resp = await provider!.simulate(
tx,
ctx.signers,
ctx.options?.commitment
);
} catch (err) {
throw translateError(err, idlErrors);
}
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;
const logs = resp.logs;
if (!logs) {
throw new Error("Simulated logs not found");
}

View File

@ -245,8 +245,15 @@ function stateInstructionKeys<M extends IdlStateMethod>(
if (m.name === "new") {
// Ctor `new` method.
const [programSigner] = findProgramAddressSync([], programId);
// @ts-expect-error
if (provider.wallet === undefined) {
throw new Error(
"This function requires the Provider interface implementor to have a 'wallet' field."
);
}
return [
{
// @ts-expect-error
pubkey: provider.wallet.publicKey,
isWritable: false,
isSigner: true,

View File

@ -5,19 +5,49 @@ import {
Transaction,
TransactionSignature,
ConfirmOptions,
RpcResponseAndContext,
SimulatedTransactionResponse,
Commitment,
SendTransactionError,
SendOptions,
RpcResponseAndContext,
} from "@solana/web3.js";
import { bs58 } from "./utils/bytes/index.js";
import { isBrowser } from "./utils/common.js";
import {
simulateTransaction,
SuccessfulTxSimulationResponse,
} from "./utils/rpc.js";
export default interface Provider {
readonly connection: Connection;
send?(
tx: Transaction,
signers?: Signer[],
opts?: SendOptions
): Promise<TransactionSignature>;
sendAndConfirm?(
tx: Transaction,
signers?: Signer[],
opts?: ConfirmOptions
): Promise<TransactionSignature>;
sendAll?(
txWithSigners: { tx: Transaction; signers?: Signer[] }[],
opts?: ConfirmOptions
): Promise<Array<TransactionSignature>>;
simulate?(
tx: Transaction,
signers?: Signer[],
commitment?: Commitment,
includeAccounts?: boolean | PublicKey[]
): Promise<SuccessfulTxSimulationResponse>;
}
/**
* The network and wallet context used to send transactions paid for and signed
* by the provider.
*/
export default class Provider {
export class AnchorProvider implements Provider {
/**
* @param connection The cluster connection where the program is deployed.
* @param wallet The wallet used to pay for and sign all transactions.
@ -44,18 +74,18 @@ export default class Provider {
*
* (This api is for Node only.)
*/
static local(url?: string, opts?: ConfirmOptions): Provider {
static local(url?: string, opts?: ConfirmOptions): AnchorProvider {
if (isBrowser) {
throw new Error(`Provider local is not available on browser.`);
}
opts = opts ?? Provider.defaultOptions();
opts = opts ?? AnchorProvider.defaultOptions();
const connection = new Connection(
url ?? "http://localhost:8899",
opts.preflightCommitment
);
const NodeWallet = require("./nodewallet.js").default;
const wallet = NodeWallet.local();
return new Provider(connection, wallet, opts);
return new AnchorProvider(connection, wallet, opts);
}
/**
@ -64,7 +94,7 @@ export default class Provider {
*
* (This api is for Node only.)
*/
static env(): Provider {
static env(): AnchorProvider {
if (isBrowser) {
throw new Error(`Provider env is not available on browser.`);
}
@ -74,30 +104,26 @@ export default class Provider {
if (url === undefined) {
throw new Error("ANCHOR_PROVIDER_URL is not defined");
}
const options = Provider.defaultOptions();
const options = AnchorProvider.defaultOptions();
const connection = new Connection(url, options.commitment);
const NodeWallet = require("./nodewallet.js").default;
const wallet = NodeWallet.local();
return new Provider(connection, wallet, options);
return new AnchorProvider(connection, wallet, options);
}
/**
* Sends the given transaction, paid for and signed by the provider's wallet.
*
* @param tx The transaction to send.
* @param signers The set of signers in addition to the provider wallet that
* will sign the transaction.
* @param signers The signers of the transaction.
* @param opts Transaction confirmation options.
*/
async send(
async sendAndConfirm(
tx: Transaction,
signers?: Array<Signer | undefined>,
signers?: Signer[],
opts?: ConfirmOptions
): Promise<TransactionSignature> {
if (signers === undefined) {
signers = [];
}
if (opts === undefined) {
opts = this.opts;
}
@ -108,11 +134,9 @@ export default class Provider {
).blockhash;
tx = await this.wallet.signTransaction(tx);
signers
.filter((s): s is Signer => s !== undefined)
.forEach((kp) => {
tx.partialSign(kp);
});
(signers ?? []).forEach((kp) => {
tx.partialSign(kp);
});
const rawTx = tx.serialize();
@ -146,7 +170,7 @@ export default class Provider {
* Similar to `send`, but for an array of transactions and signers.
*/
async sendAll(
reqs: Array<SendTxRequest>,
txWithSigners: { tx: Transaction; signers?: Signer[] }[],
opts?: ConfirmOptions
): Promise<Array<TransactionSignature>> {
if (opts === undefined) {
@ -156,22 +180,16 @@ export default class Provider {
opts.preflightCommitment
);
let txs = reqs.map((r) => {
let txs = txWithSigners.map((r) => {
let tx = r.tx;
let signers = r.signers;
if (signers === undefined) {
signers = [];
}
let signers = r.signers ?? [];
tx.feePayer = this.wallet.publicKey;
tx.recentBlockhash = blockhash.blockhash;
signers
.filter((s): s is Signer => s !== undefined)
.forEach((kp) => {
tx.partialSign(kp);
});
signers.forEach((kp) => {
tx.partialSign(kp);
});
return tx;
});
@ -195,38 +213,45 @@ export default class Provider {
* Simulates the given transaction, returning emitted logs from execution.
*
* @param tx The transaction to send.
* @param signers The set of signers in addition to the provdier wallet that
* will sign the transaction.
* @param signers The signers of the transaction.
* @param opts Transaction confirmation options.
*/
async simulate(
tx: Transaction,
signers?: Array<Signer | undefined>,
opts: ConfirmOptions = this.opts
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
if (signers === undefined) {
signers = [];
}
signers?: Signer[],
commitment?: Commitment,
includeAccounts?: boolean | PublicKey[]
): Promise<SuccessfulTxSimulationResponse> {
tx.feePayer = this.wallet.publicKey;
tx.recentBlockhash = (
await this.connection.getRecentBlockhash(
opts.preflightCommitment ?? this.opts.preflightCommitment
await this.connection.getLatestBlockhash(
commitment ?? this.connection.commitment
)
).blockhash;
tx = await this.wallet.signTransaction(tx);
signers
.filter((s): s is Signer => s !== undefined)
.forEach((kp) => {
tx.partialSign(kp);
});
return await simulateTransaction(
const result = await simulateTransaction(
this.connection,
tx,
opts.commitment ?? this.opts.commitment ?? "processed"
signers,
commitment,
includeAccounts
);
if (result.value.err) {
throw new SimulateError(result.value);
}
return result.value;
}
}
class SimulateError extends Error {
constructor(
readonly simulationResponse: SimulatedTransactionResponse,
message?: string
) {
super(message);
}
}
@ -244,33 +269,6 @@ export interface Wallet {
publicKey: 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;
}
// Copy of Connection.sendAndConfirmRawTransaction that throws
// a better error if 'confirmTransaction` returns an error status
async function sendAndConfirmRawTransaction(
@ -322,7 +320,7 @@ export function setProvider(provider: Provider) {
*/
export function getProvider(): Provider {
if (_provider === null) {
return Provider.local();
return AnchorProvider.local();
}
return _provider;
}

View File

@ -9,10 +9,33 @@ import {
Transaction,
TransactionInstruction,
Commitment,
Signer,
RpcResponseAndContext,
SimulatedTransactionResponse,
SendTransactionError,
} from "@solana/web3.js";
import { chunks } from "../utils/common.js";
import { Address, translateAddress } from "../program/common.js";
import Provider, { getProvider } from "../provider.js";
import Provider, { getProvider, Wallet } from "../provider.js";
import {
type as pick,
number,
string,
array,
boolean,
literal,
record,
union,
optional,
nullable,
coerce,
instance,
create,
tuple,
unknown,
any,
Struct,
} from "superstruct";
/**
* Sends a transaction to a program with the given accounts and instruction
@ -38,7 +61,13 @@ export async function invoke(
})
);
return await provider.send(tx);
if (provider.sendAndConfirm === undefined) {
throw new Error(
"This function requires 'Provider.sendAndConfirm' to be implemented."
);
}
return await provider.sendAndConfirm(tx, []);
}
const GET_MULTIPLE_ACCOUNTS_LIMIT: number = 99;
@ -87,3 +116,141 @@ async function getMultipleAccountsCore(
};
});
}
// 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"
>;