diff --git a/.gitmodules b/.gitmodules index 5bb41c1e..ec5d6737 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 417a04ef..7a4ca60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/tutorial/basic-0/client.js b/examples/tutorial/basic-0/client.js index ecdaddfc..77a7f08b 100644 --- a/examples/tutorial/basic-0/client.js +++ b/examples/tutorial/basic-0/client.js @@ -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 diff --git a/examples/tutorial/basic-0/tests/basic-0.js b/examples/tutorial/basic-0/tests/basic-0.js index ce65a034..e9be9609 100644 --- a/examples/tutorial/basic-0/tests/basic-0.js +++ b/examples/tutorial/basic-0/tests/basic-0.js @@ -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 diff --git a/examples/tutorial/basic-1/tests/basic-1.js b/examples/tutorial/basic-1/tests/basic-1.js index 66eac9c6..98e7ef5a 100644 --- a/examples/tutorial/basic-1/tests/basic-1.js +++ b/examples/tutorial/basic-1/tests/basic-1.js @@ -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); diff --git a/examples/tutorial/basic-2/tests/basic-2.js b/examples/tutorial/basic-2/tests/basic-2.js index eed5aa9d..8f638ee8 100644 --- a/examples/tutorial/basic-2/tests/basic-2.js +++ b/examples/tutorial/basic-2/tests/basic-2.js @@ -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); diff --git a/examples/tutorial/basic-3/tests/basic-3.js b/examples/tutorial/basic-3/tests/basic-3.js index fcebfe31..ad5a75d3 100644 --- a/examples/tutorial/basic-3/tests/basic-3.js +++ b/examples/tutorial/basic-3/tests/basic-3.js @@ -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); diff --git a/examples/tutorial/basic-4/tests/basic-4.js b/examples/tutorial/basic-4/tests/basic-4.js index 509519a1..fddc7514 100644 --- a/examples/tutorial/basic-4/tests/basic-4.js +++ b/examples/tutorial/basic-4/tests/basic-4.js @@ -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); diff --git a/tests/auction-house b/tests/auction-house index 967650c5..2d4d9583 160000 --- a/tests/auction-house +++ b/tests/auction-house @@ -1 +1 @@ -Subproject commit 967650c531ba0f23c88374875ccfcecb9b1a7800 +Subproject commit 2d4d9583467d697267b87b250af8c8bd360f3745 diff --git a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts index ec191342..16ed36af 100644 --- a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts +++ b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts @@ -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); diff --git a/tests/cashiers-check/tests/cashiers-check.js b/tests/cashiers-check/tests/cashiers-check.js index 9f0f8717..8ddcebe3 100644 --- a/tests/cashiers-check/tests/cashiers-check.js +++ b/tests/cashiers-check/tests/cashiers-check.js @@ -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; diff --git a/tests/cfo/deps/stake b/tests/cfo/deps/stake index 9c41642d..b68b9a6f 160000 --- a/tests/cfo/deps/stake +++ b/tests/cfo/deps/stake @@ -1 +1 @@ -Subproject commit 9c41642dffbb334e1e39c616cd6a645d91768d3e +Subproject commit b68b9a6fdea2c8befe95aa0f1fcca394579fc3bd diff --git a/tests/cfo/deps/swap b/tests/cfo/deps/swap index 3da36aaa..2ac6045c 160000 --- a/tests/cfo/deps/swap +++ b/tests/cfo/deps/swap @@ -1 +1 @@ -Subproject commit 3da36aaae7af6ce901d68c0280aac34817fe7fd8 +Subproject commit 2ac6045cf37436c4702fbe12678f3a6243feecb1 diff --git a/tests/cfo/scripts/fees.js b/tests/cfo/scripts/fees.js index 62249fcf..742877be 100755 --- a/tests/cfo/scripts/fees.js +++ b/tests/cfo/scripts/fees.js @@ -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; diff --git a/tests/cfo/scripts/list-market.js b/tests/cfo/scripts/list-market.js index 16658673..94c90769 100755 --- a/tests/cfo/scripts/list-market.js +++ b/tests/cfo/scripts/list-market.js @@ -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({ diff --git a/tests/cfo/scripts/trade-bot.js b/tests/cfo/scripts/trade-bot.js index 0b30c229..b7d2be59 100755 --- a/tests/cfo/scripts/trade-bot.js +++ b/tests/cfo/scripts/trade-bot.js @@ -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); } diff --git a/tests/cfo/tests/cfo.js b/tests/cfo/tests/cfo.js index cd018f80..307bc6c6 100644 --- a/tests/cfo/tests/cfo.js +++ b/tests/cfo/tests/cfo.js @@ -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; diff --git a/tests/cfo/tests/utils/index.js b/tests/cfo/tests/utils/index.js index 3988262a..63d21666 100644 --- a/tests/cfo/tests/utils/index.js +++ b/tests/cfo/tests/utils/index.js @@ -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); diff --git a/tests/cfo/tests/utils/stake.js b/tests/cfo/tests/utils/stake.js index 09054a13..1a9d0035 100644 --- a/tests/cfo/tests/utils/stake.js +++ b/tests/cfo/tests/utils/stake.js @@ -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; diff --git a/tests/chat/tests/chat.js b/tests/chat/tests/chat.js index 90eff17e..08c2b4a6 100644 --- a/tests/chat/tests/chat.js +++ b/tests/chat/tests/chat.js @@ -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; diff --git a/tests/composite/tests/composite.js b/tests/composite/tests/composite.js index 00e3eef2..e7c3a76b 100644 --- a/tests/composite/tests/composite.js +++ b/tests/composite/tests/composite.js @@ -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); diff --git a/tests/cpi-returns/tests/cpi-return.ts b/tests/cpi-returns/tests/cpi-return.ts index f5a10599..48bdc3a4 100644 --- a/tests/cpi-returns/tests/cpi-return.ts +++ b/tests/cpi-returns/tests/cpi-return.ts @@ -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; diff --git a/tests/custom-coder/tests/custom-coder.ts b/tests/custom-coder/tests/custom-coder.ts index 284a16a1..6777d673 100644 --- a/tests/custom-coder/tests/custom-coder.ts +++ b/tests/custom-coder/tests/custom-coder.ts @@ -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(); diff --git a/tests/declare-id/tests/declare-id.ts b/tests/declare-id/tests/declare-id.ts index 85e90dd9..4e0a206e 100644 --- a/tests/declare-id/tests/declare-id.ts +++ b/tests/declare-id/tests/declare-id.ts @@ -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; it("throws error!", async () => { diff --git a/tests/errors/tests/errors.ts b/tests/errors/tests/errors.ts index 25de4c82..ffd4a135 100644 --- a/tests/errors/tests/errors.ts +++ b/tests/errors/tests/errors.ts @@ -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); diff --git a/tests/escrow/tests/escrow.ts b/tests/escrow/tests/escrow.ts index 28921ea5..c12ae81c 100644 --- a/tests/escrow/tests/escrow.ts +++ b/tests/escrow/tests/escrow.ts @@ -8,7 +8,7 @@ import { Escrow } from "../target/types/escrow"; type EscrowAccount = IdlAccounts["escrowAccount"]; describe("escrow", () => { - const provider = anchor.Provider.env(); + const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.Escrow as Program; diff --git a/tests/events/tests/events.js b/tests/events/tests/events.js index d5f311db..348408cb 100644 --- a/tests/events/tests/events.js +++ b/tests/events/tests/events.js @@ -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 () => { diff --git a/tests/floats/tests/floats.ts b/tests/floats/tests/floats.ts index 4e5eeb91..ff0e0b7c 100644 --- a/tests/floats/tests/floats.ts +++ b/tests/floats/tests/floats.ts @@ -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; diff --git a/tests/ido-pool/tests/ido-pool.js b/tests/ido-pool/tests/ido-pool.js index 69a07bf2..03533c1a 100644 --- a/tests/ido-pool/tests/ido-pool.js +++ b/tests/ido-pool/tests/ido-pool.js @@ -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, diff --git a/tests/interface/tests/interface.js b/tests/interface/tests/interface.js index afdc910e..bbc3549e 100644 --- a/tests/interface/tests/interface.js +++ b/tests/interface/tests/interface.js @@ -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; diff --git a/tests/lockup/tests/lockup.js b/tests/lockup/tests/lockup.js index 8bffcecb..0aee2a3c 100644 --- a/tests/lockup/tests/lockup.js +++ b/tests/lockup/tests/lockup.js @@ -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); diff --git a/tests/misc/miscNonRentExempt.ts b/tests/misc/miscNonRentExempt.ts index 03bfa6d6..50b70c1f 100644 --- a/tests/misc/miscNonRentExempt.ts +++ b/tests/misc/miscNonRentExempt.ts @@ -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; it("init_if_needed checks rent_exemption if init is not needed", async () => { diff --git a/tests/misc/tests/misc.ts b/tests/misc/tests/misc.ts index f02d7f1a..2cbd0068 100644 --- a/tests/misc/tests/misc.ts +++ b/tests/misc/tests/misc.ts @@ -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; const misc2Program = anchor.workspace.Misc2 as Program; @@ -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 } diff --git a/tests/multiple-suites/tests/another-suite/another-suite.ts b/tests/multiple-suites/tests/another-suite/another-suite.ts index 44f32266..0ab19987 100644 --- a/tests/multiple-suites/tests/another-suite/another-suite.ts +++ b/tests/multiple-suites/tests/another-suite/another-suite.ts @@ -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; diff --git a/tests/multiple-suites/tests/fourth-and-fifth-suite/fifth-suite/fifthSuite.ts b/tests/multiple-suites/tests/fourth-and-fifth-suite/fifth-suite/fifthSuite.ts index 0d7dbfa1..43e781a5 100644 --- a/tests/multiple-suites/tests/fourth-and-fifth-suite/fifth-suite/fifthSuite.ts +++ b/tests/multiple-suites/tests/fourth-and-fifth-suite/fifth-suite/fifthSuite.ts @@ -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; diff --git a/tests/multiple-suites/tests/fourth-and-fifth-suite/forth-suite/forth-suite.ts b/tests/multiple-suites/tests/fourth-and-fifth-suite/forth-suite/forth-suite.ts index eb46e9d4..5f14aebe 100644 --- a/tests/multiple-suites/tests/fourth-and-fifth-suite/forth-suite/forth-suite.ts +++ b/tests/multiple-suites/tests/fourth-and-fifth-suite/forth-suite/forth-suite.ts @@ -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; diff --git a/tests/multiple-suites/tests/multiple-suites/multiple-suites.ts b/tests/multiple-suites/tests/multiple-suites/multiple-suites.ts index 2171a0e3..3cdf84ed 100644 --- a/tests/multiple-suites/tests/multiple-suites/multiple-suites.ts +++ b/tests/multiple-suites/tests/multiple-suites/multiple-suites.ts @@ -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; diff --git a/tests/multiple-suites/tests/third-suite/sub-dir-one/subDirOne.ts b/tests/multiple-suites/tests/third-suite/sub-dir-one/subDirOne.ts index dccfac1f..93cf1c0a 100644 --- a/tests/multiple-suites/tests/third-suite/sub-dir-one/subDirOne.ts +++ b/tests/multiple-suites/tests/third-suite/sub-dir-one/subDirOne.ts @@ -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; diff --git a/tests/multiple-suites/tests/third-suite/sub-dir-two/subDirTwo.ts b/tests/multiple-suites/tests/third-suite/sub-dir-two/subDirTwo.ts index c1e563cd..62be69fc 100644 --- a/tests/multiple-suites/tests/third-suite/sub-dir-two/subDirTwo.ts +++ b/tests/multiple-suites/tests/third-suite/sub-dir-two/subDirTwo.ts @@ -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; diff --git a/tests/multisig/tests/multisig.js b/tests/multisig/tests/multisig.js index d2ea3a64..1a700025 100644 --- a/tests/multisig/tests/multisig.js +++ b/tests/multisig/tests/multisig.js @@ -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; diff --git a/tests/pda-derivation/tests/typescript.spec.ts b/tests/pda-derivation/tests/typescript.spec.ts index 6d30939b..490b49ab 100644 --- a/tests/pda-derivation/tests/typescript.spec.ts +++ b/tests/pda-derivation/tests/typescript.spec.ts @@ -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; const base = Keypair.generate(); diff --git a/tests/pyth/tests/pyth.spec.ts b/tests/pyth/tests/pyth.spec.ts index 6baab838..579f68c6 100644 --- a/tests/pyth/tests/pyth.spec.ts +++ b/tests/pyth/tests/pyth.spec.ts @@ -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 () => { diff --git a/tests/safety-checks/tests/safety-checks.ts b/tests/safety-checks/tests/safety-checks.ts index 2eed33f1..ba4bc4c5 100644 --- a/tests/safety-checks/tests/safety-checks.ts +++ b/tests/safety-checks/tests/safety-checks.ts @@ -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; diff --git a/tests/spl/token-proxy/tests/token-proxy.js b/tests/spl/token-proxy/tests/token-proxy.js index 9d31cc4f..503c976f 100644 --- a/tests/spl/token-proxy/tests/token-proxy.js +++ b/tests/spl/token-proxy/tests/token-proxy.js @@ -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; } diff --git a/tests/swap/tests/swap.js b/tests/swap/tests/swap.js index ef74c575..21dd0778 100644 --- a/tests/swap/tests/swap.js +++ b/tests/swap/tests/swap.js @@ -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; diff --git a/tests/swap/tests/utils/index.js b/tests/swap/tests/utils/index.js index ac31831b..37503051 100644 --- a/tests/swap/tests/utils/index.js +++ b/tests/swap/tests/utils/index.js @@ -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; diff --git a/tests/system-accounts/tests/system-accounts.js b/tests/system-accounts/tests/system-accounts.js index 3b1b12c7..5cd2f60a 100644 --- a/tests/system-accounts/tests/system-accounts.js +++ b/tests/system-accounts/tests/system-accounts.js @@ -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(); diff --git a/tests/sysvars/tests/sysvars.js b/tests/sysvars/tests/sysvars.js index f20042ac..c69afa97 100644 --- a/tests/sysvars/tests/sysvars.js +++ b/tests/sysvars/tests/sysvars.js @@ -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 () => { diff --git a/tests/tictactoe/tests/tictactoe.js b/tests/tictactoe/tests/tictactoe.js index 268886f2..5c4147a3 100644 --- a/tests/tictactoe/tests/tictactoe.js +++ b/tests/tictactoe/tests/tictactoe.js @@ -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(); diff --git a/tests/typescript/tests/typescript.spec.ts b/tests/typescript/tests/typescript.spec.ts index eb93acba..d4c34f50 100644 --- a/tests/typescript/tests/typescript.spec.ts +++ b/tests/typescript/tests/typescript.spec.ts @@ -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. diff --git a/tests/validator-clone/tests/validator-clone.ts b/tests/validator-clone/tests/validator-clone.ts index 2d8b83ab..92b2fc1f 100644 --- a/tests/validator-clone/tests/validator-clone.ts +++ b/tests/validator-clone/tests/validator-clone.ts @@ -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; const connection = program.provider.connection; diff --git a/tests/zero-copy/tests/zero-copy.js b/tests/zero-copy/tests/zero-copy.js index 2f5052cc..f4e9f121 100644 --- a/tests/zero-copy/tests/zero-copy.js +++ b/tests/zero-copy/tests/zero-copy.js @@ -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; diff --git a/ts/src/index.ts b/ts/src/index.ts index 818ad60d..30ecb761 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -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"; diff --git a/ts/src/program/accounts-resolver.ts b/ts/src/program/accounts-resolver.ts index b97cd04d..a6968084 100644 --- a/ts/src/program/accounts-resolver.ts +++ b/ts/src/program/accounts-resolver.ts @@ -58,6 +58,13 @@ export class AccountsResolver> { // 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; } diff --git a/ts/src/program/namespace/account.ts b/ts/src/program/namespace/account.ts index 5563900c..cbaf04f0 100644 --- a/ts/src/program/namespace/account.ts +++ b/ts/src/program/namespace/account.ts @@ -287,7 +287,15 @@ export class AccountClient< ): Promise { 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, diff --git a/ts/src/program/namespace/rpc.ts b/ts/src/program/namespace/rpc.ts index de2ae383..f61c81bc 100644 --- a/ts/src/program/namespace/rpc.ts +++ b/ts/src/program/namespace/rpc.ts @@ -20,8 +20,17 @@ export default class RpcFactory { const rpc: RpcFn = 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); } diff --git a/ts/src/program/namespace/simulate.ts b/ts/src/program/namespace/simulate.ts index e97d2363..1c653f50 100644 --- a/ts/src/program/namespace/simulate.ts +++ b/ts/src/program/namespace/simulate.ts @@ -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 = async (...args) => { const tx = txFn(...args); const [, ctx] = splitArgsAndCtx(idlIx, [...args]); - let resp: - | RpcResponseAndContext - | 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"); } diff --git a/ts/src/program/namespace/state.ts b/ts/src/program/namespace/state.ts index 2e3bc6f4..3d7430e2 100644 --- a/ts/src/program/namespace/state.ts +++ b/ts/src/program/namespace/state.ts @@ -245,8 +245,15 @@ function stateInstructionKeys( 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, diff --git a/ts/src/provider.ts b/ts/src/provider.ts index cdc70221..ed499720 100644 --- a/ts/src/provider.ts +++ b/ts/src/provider.ts @@ -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; + sendAndConfirm?( + tx: Transaction, + signers?: Signer[], + opts?: ConfirmOptions + ): Promise; + sendAll?( + txWithSigners: { tx: Transaction; signers?: Signer[] }[], + opts?: ConfirmOptions + ): Promise>; + simulate?( + tx: Transaction, + signers?: Signer[], + commitment?: Commitment, + includeAccounts?: boolean | PublicKey[] + ): Promise; +} /** * 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, + signers?: Signer[], opts?: ConfirmOptions ): Promise { - 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, + txWithSigners: { tx: Transaction; signers?: Signer[] }[], opts?: ConfirmOptions ): Promise> { 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, - opts: ConfirmOptions = this.opts - ): Promise> { - if (signers === undefined) { - signers = []; - } - + signers?: Signer[], + commitment?: Commitment, + includeAccounts?: boolean | PublicKey[] + ): Promise { 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> { - // @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; } diff --git a/ts/src/utils/rpc.ts b/ts/src/utils/rpc.ts index 827a8f8d..46f8defb 100644 --- a/ts/src/utils/rpc.ts +++ b/ts/src/utils/rpc.ts @@ -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, + commitment?: Commitment, + includeAccounts?: boolean | Array +): Promise> { + 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(schema: Struct) { + 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(result: Struct) { + 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(value: Struct) { + 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" +>;