123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- import {
- Connection,
- Signer,
- PublicKey,
- Transaction,
- TransactionSignature,
- ConfirmOptions,
- 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;
- readonly publicKey: PublicKey;
- 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 class AnchorProvider implements Provider {
- readonly publicKey: PublicKey;
- /**
- * @param connection The cluster connection where the program is deployed.
- * @param wallet The wallet used to pay for and sign all transactions.
- * @param opts Transaction confirmation options to use by default.
- */
- constructor(
- readonly connection: Connection,
- readonly wallet: Wallet,
- readonly opts: ConfirmOptions
- ) {
- this.publicKey = wallet.publicKey;
- }
- static defaultOptions(): ConfirmOptions {
- return {
- preflightCommitment: "processed",
- commitment: "processed",
- };
- }
- /**
- * Returns a `Provider` with a wallet read from the local filesystem.
- *
- * @param url The network cluster url.
- * @param opts The default transaction confirmation options.
- *
- * (This api is for Node only.)
- */
- static local(url?: string, opts?: ConfirmOptions): AnchorProvider {
- if (isBrowser) {
- throw new Error(`Provider local is not available on browser.`);
- }
- 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 AnchorProvider(connection, wallet, opts);
- }
- /**
- * Returns a `Provider` read from the `ANCHOR_PROVIDER_URL` environment
- * variable
- *
- * (This api is for Node only.)
- */
- static env(): AnchorProvider {
- if (isBrowser) {
- throw new Error(`Provider env is not available on browser.`);
- }
- const process = require("process");
- const url = process.env.ANCHOR_PROVIDER_URL;
- if (url === undefined) {
- throw new Error("ANCHOR_PROVIDER_URL is not defined");
- }
- const options = AnchorProvider.defaultOptions();
- const connection = new Connection(url, options.commitment);
- const NodeWallet = require("./nodewallet.js").default;
- const wallet = NodeWallet.local();
- 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 signers of the transaction.
- * @param opts Transaction confirmation options.
- */
- async sendAndConfirm(
- tx: Transaction,
- signers?: Signer[],
- opts?: ConfirmOptions
- ): Promise<TransactionSignature> {
- if (opts === undefined) {
- opts = this.opts;
- }
- tx.feePayer = this.wallet.publicKey;
- tx.recentBlockhash = (
- await this.connection.getRecentBlockhash(opts.preflightCommitment)
- ).blockhash;
- tx = await this.wallet.signTransaction(tx);
- (signers ?? []).forEach((kp) => {
- tx.partialSign(kp);
- });
- const rawTx = tx.serialize();
- try {
- return await sendAndConfirmRawTransaction(this.connection, rawTx, opts);
- } catch (err) {
- // thrown if the underlying 'confirmTransaction' encounters a failed tx
- // the 'confirmTransaction' error does not return logs so we make another rpc call to get them
- if (err instanceof ConfirmError) {
- // choose the shortest available commitment for 'getTransaction'
- // (the json RPC does not support any shorter than "confirmed" for 'getTransaction')
- // because that will see the tx sent with `sendAndConfirmRawTransaction` no matter which
- // commitment `sendAndConfirmRawTransaction` used
- const failedTx = await this.connection.getTransaction(
- bs58.encode(tx.signature!),
- { commitment: "confirmed" }
- );
- if (!failedTx) {
- throw err;
- } else {
- const logs = failedTx.meta?.logMessages;
- throw !logs ? err : new SendTransactionError(err.message, logs);
- }
- } else {
- throw err;
- }
- }
- }
- /**
- * Similar to `send`, but for an array of transactions and signers.
- */
- async sendAll(
- txWithSigners: { tx: Transaction; signers?: Signer[] }[],
- opts?: ConfirmOptions
- ): Promise<Array<TransactionSignature>> {
- if (opts === undefined) {
- opts = this.opts;
- }
- const blockhash = await this.connection.getRecentBlockhash(
- opts.preflightCommitment
- );
- let txs = txWithSigners.map((r) => {
- let tx = r.tx;
- let signers = r.signers ?? [];
- tx.feePayer = this.wallet.publicKey;
- tx.recentBlockhash = blockhash.blockhash;
- signers.forEach((kp) => {
- tx.partialSign(kp);
- });
- return tx;
- });
- const signedTxs = await this.wallet.signAllTransactions(txs);
- const sigs: TransactionSignature[] = [];
- for (let k = 0; k < txs.length; k += 1) {
- const tx = signedTxs[k];
- const rawTx = tx.serialize();
- sigs.push(
- await sendAndConfirmRawTransaction(this.connection, rawTx, opts)
- );
- }
- return sigs;
- }
- /**
- * Simulates the given transaction, returning emitted logs from execution.
- *
- * @param tx The transaction to send.
- * @param signers The signers of the transaction.
- * @param opts Transaction confirmation options.
- */
- async simulate(
- tx: Transaction,
- signers?: Signer[],
- commitment?: Commitment,
- includeAccounts?: boolean | PublicKey[]
- ): Promise<SuccessfulTxSimulationResponse> {
- tx.feePayer = this.wallet.publicKey;
- tx.recentBlockhash = (
- await this.connection.getLatestBlockhash(
- commitment ?? this.connection.commitment
- )
- ).blockhash;
- tx = await this.wallet.signTransaction(tx);
- const result = await simulateTransaction(
- this.connection,
- tx,
- 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);
- }
- }
- export type SendTxRequest = {
- tx: Transaction;
- signers: Array<Signer | undefined>;
- };
- /**
- * Wallet interface for objects that can be used to sign provider transactions.
- */
- export interface Wallet {
- signTransaction(tx: Transaction): Promise<Transaction>;
- signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
- publicKey: PublicKey;
- }
- // Copy of Connection.sendAndConfirmRawTransaction that throws
- // a better error if 'confirmTransaction` returns an error status
- async function sendAndConfirmRawTransaction(
- connection: Connection,
- rawTransaction: Buffer,
- options?: ConfirmOptions
- ): Promise<TransactionSignature> {
- const sendOptions = options && {
- skipPreflight: options.skipPreflight,
- preflightCommitment: options.preflightCommitment || options.commitment,
- };
- const signature = await connection.sendRawTransaction(
- rawTransaction,
- sendOptions
- );
- const status = (
- await connection.confirmTransaction(
- signature,
- options && options.commitment
- )
- ).value;
- if (status.err) {
- throw new ConfirmError(
- `Raw transaction ${signature} failed (${JSON.stringify(status)})`
- );
- }
- return signature;
- }
- class ConfirmError extends Error {
- constructor(message?: string) {
- super(message);
- }
- }
- /**
- * Sets the default provider on the client.
- */
- export function setProvider(provider: Provider) {
- _provider = provider;
- }
- /**
- * Returns the default provider being used by the client.
- */
- export function getProvider(): Provider {
- if (_provider === null) {
- return AnchorProvider.local();
- }
- return _provider;
- }
- // Global provider used as the default when a provider is not given.
- let _provider: Provider | null = null;
|