2021-01-27 19:31:15 -08:00
|
|
|
import {
|
|
|
|
Connection,
|
2021-05-20 02:26:32 -07:00
|
|
|
Signer,
|
2021-01-27 19:31:15 -08:00
|
|
|
PublicKey,
|
|
|
|
Transaction,
|
|
|
|
TransactionSignature,
|
|
|
|
ConfirmOptions,
|
2021-05-08 21:31:55 -07:00
|
|
|
SimulatedTransactionResponse,
|
|
|
|
Commitment,
|
2022-03-20 17:29:12 -07:00
|
|
|
SendTransactionError,
|
2022-04-11 12:48:58 -07:00
|
|
|
SendOptions,
|
2021-01-27 19:31:15 -08:00
|
|
|
} from "@solana/web3.js";
|
2022-03-20 17:29:12 -07:00
|
|
|
import { bs58 } from "./utils/bytes/index.js";
|
2021-11-28 08:02:58 -08:00
|
|
|
import { isBrowser } from "./utils/common.js";
|
2022-04-11 12:48:58 -07:00
|
|
|
import {
|
|
|
|
simulateTransaction,
|
|
|
|
SuccessfulTxSimulationResponse,
|
|
|
|
} from "./utils/rpc.js";
|
|
|
|
|
|
|
|
export default interface Provider {
|
|
|
|
readonly connection: Connection;
|
2022-05-03 07:45:42 -07:00
|
|
|
readonly publicKey?: PublicKey;
|
2022-04-11 12:48:58 -07:00
|
|
|
|
|
|
|
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>;
|
|
|
|
}
|
2021-01-27 19:31:15 -08:00
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* The network and wallet context used to send transactions paid for and signed
|
|
|
|
* by the provider.
|
|
|
|
*/
|
2022-04-11 12:48:58 -07:00
|
|
|
export class AnchorProvider implements Provider {
|
2022-04-28 15:23:20 -07:00
|
|
|
readonly publicKey: PublicKey;
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
constructor(
|
|
|
|
readonly connection: Connection,
|
|
|
|
readonly wallet: Wallet,
|
|
|
|
readonly opts: ConfirmOptions
|
2022-04-28 15:23:20 -07:00
|
|
|
) {
|
|
|
|
this.publicKey = wallet.publicKey;
|
|
|
|
}
|
2021-01-27 19:31:15 -08:00
|
|
|
|
|
|
|
static defaultOptions(): ConfirmOptions {
|
|
|
|
return {
|
2021-12-20 01:51:02 -08:00
|
|
|
preflightCommitment: "processed",
|
|
|
|
commitment: "processed",
|
2021-01-27 19:31:15 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* 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.)
|
|
|
|
*/
|
2022-04-11 12:48:58 -07:00
|
|
|
static local(url?: string, opts?: ConfirmOptions): AnchorProvider {
|
2021-12-07 12:16:46 -08:00
|
|
|
if (isBrowser) {
|
|
|
|
throw new Error(`Provider local is not available on browser.`);
|
|
|
|
}
|
2022-04-11 12:48:58 -07:00
|
|
|
opts = opts ?? AnchorProvider.defaultOptions();
|
2021-01-27 19:31:15 -08:00
|
|
|
const connection = new Connection(
|
2021-05-19 13:26:09 -07:00
|
|
|
url ?? "http://localhost:8899",
|
2021-01-27 19:31:15 -08:00
|
|
|
opts.preflightCommitment
|
|
|
|
);
|
2021-12-07 12:16:46 -08:00
|
|
|
const NodeWallet = require("./nodewallet.js").default;
|
2021-01-27 19:31:15 -08:00
|
|
|
const wallet = NodeWallet.local();
|
2022-04-11 12:48:58 -07:00
|
|
|
return new AnchorProvider(connection, wallet, opts);
|
2021-01-27 19:31:15 -08:00
|
|
|
}
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
2021-06-28 13:31:41 -07:00
|
|
|
* Returns a `Provider` read from the `ANCHOR_PROVIDER_URL` environment
|
2021-05-10 13:12:20 -07:00
|
|
|
* variable
|
|
|
|
*
|
|
|
|
* (This api is for Node only.)
|
|
|
|
*/
|
2022-04-11 12:48:58 -07:00
|
|
|
static env(): AnchorProvider {
|
2021-09-16 13:42:11 -07:00
|
|
|
if (isBrowser) {
|
|
|
|
throw new Error(`Provider env is not available on browser.`);
|
|
|
|
}
|
2021-06-28 13:31:41 -07:00
|
|
|
|
2021-01-27 19:31:15 -08:00
|
|
|
const process = require("process");
|
|
|
|
const url = process.env.ANCHOR_PROVIDER_URL;
|
|
|
|
if (url === undefined) {
|
|
|
|
throw new Error("ANCHOR_PROVIDER_URL is not defined");
|
|
|
|
}
|
2022-04-11 12:48:58 -07:00
|
|
|
const options = AnchorProvider.defaultOptions();
|
2021-01-27 19:31:15 -08:00
|
|
|
const connection = new Connection(url, options.commitment);
|
2021-12-07 12:16:46 -08:00
|
|
|
const NodeWallet = require("./nodewallet.js").default;
|
2021-01-27 19:31:15 -08:00
|
|
|
const wallet = NodeWallet.local();
|
|
|
|
|
2022-04-11 12:48:58 -07:00
|
|
|
return new AnchorProvider(connection, wallet, options);
|
2021-01-27 19:31:15 -08:00
|
|
|
}
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
2021-06-06 10:53:38 -07:00
|
|
|
* Sends the given transaction, paid for and signed by the provider's wallet.
|
2021-05-10 13:12:20 -07:00
|
|
|
*
|
|
|
|
* @param tx The transaction to send.
|
2022-04-11 12:48:58 -07:00
|
|
|
* @param signers The signers of the transaction.
|
2021-05-10 13:12:20 -07:00
|
|
|
* @param opts Transaction confirmation options.
|
|
|
|
*/
|
2022-04-11 12:48:58 -07:00
|
|
|
async sendAndConfirm(
|
2021-01-27 19:31:15 -08:00
|
|
|
tx: Transaction,
|
2022-04-11 12:48:58 -07:00
|
|
|
signers?: Signer[],
|
2021-01-27 19:31:15 -08:00
|
|
|
opts?: ConfirmOptions
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
if (opts === undefined) {
|
|
|
|
opts = this.opts;
|
|
|
|
}
|
|
|
|
|
2021-05-27 13:03:08 -07:00
|
|
|
tx.feePayer = this.wallet.publicKey;
|
2021-01-27 19:31:15 -08:00
|
|
|
tx.recentBlockhash = (
|
|
|
|
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
|
|
|
).blockhash;
|
|
|
|
|
2022-03-20 11:11:41 -07:00
|
|
|
tx = await this.wallet.signTransaction(tx);
|
2022-04-11 12:48:58 -07:00
|
|
|
(signers ?? []).forEach((kp) => {
|
|
|
|
tx.partialSign(kp);
|
|
|
|
});
|
2021-01-27 19:31:15 -08:00
|
|
|
|
|
|
|
const rawTx = tx.serialize();
|
|
|
|
|
2022-03-20 17:29:12 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2021-01-27 19:31:15 -08:00
|
|
|
}
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* Similar to `send`, but for an array of transactions and signers.
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
async sendAll(
|
2022-04-11 12:48:58 -07:00
|
|
|
txWithSigners: { tx: Transaction; signers?: Signer[] }[],
|
2021-01-27 19:31:15 -08:00
|
|
|
opts?: ConfirmOptions
|
|
|
|
): Promise<Array<TransactionSignature>> {
|
|
|
|
if (opts === undefined) {
|
|
|
|
opts = this.opts;
|
|
|
|
}
|
|
|
|
const blockhash = await this.connection.getRecentBlockhash(
|
|
|
|
opts.preflightCommitment
|
|
|
|
);
|
|
|
|
|
2022-04-11 12:48:58 -07:00
|
|
|
let txs = txWithSigners.map((r) => {
|
2021-01-27 19:31:15 -08:00
|
|
|
let tx = r.tx;
|
2022-04-11 12:48:58 -07:00
|
|
|
let signers = r.signers ?? [];
|
2021-01-27 19:31:15 -08:00
|
|
|
|
2021-05-27 13:03:08 -07:00
|
|
|
tx.feePayer = this.wallet.publicKey;
|
2021-01-27 19:31:15 -08:00
|
|
|
tx.recentBlockhash = blockhash.blockhash;
|
2021-05-27 13:03:08 -07:00
|
|
|
|
2022-04-11 12:48:58 -07:00
|
|
|
signers.forEach((kp) => {
|
|
|
|
tx.partialSign(kp);
|
|
|
|
});
|
2021-01-27 19:31:15 -08:00
|
|
|
|
|
|
|
return tx;
|
|
|
|
});
|
|
|
|
|
|
|
|
const signedTxs = await this.wallet.signAllTransactions(txs);
|
|
|
|
|
2021-09-16 13:42:11 -07:00
|
|
|
const sigs: TransactionSignature[] = [];
|
2021-01-27 19:31:15 -08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2021-05-08 21:31:55 -07:00
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* Simulates the given transaction, returning emitted logs from execution.
|
|
|
|
*
|
|
|
|
* @param tx The transaction to send.
|
2022-04-11 12:48:58 -07:00
|
|
|
* @param signers The signers of the transaction.
|
2021-05-10 13:12:20 -07:00
|
|
|
* @param opts Transaction confirmation options.
|
|
|
|
*/
|
2021-05-08 21:31:55 -07:00
|
|
|
async simulate(
|
|
|
|
tx: Transaction,
|
2022-04-11 12:48:58 -07:00
|
|
|
signers?: Signer[],
|
|
|
|
commitment?: Commitment,
|
|
|
|
includeAccounts?: boolean | PublicKey[]
|
|
|
|
): Promise<SuccessfulTxSimulationResponse> {
|
2021-05-27 13:03:08 -07:00
|
|
|
tx.feePayer = this.wallet.publicKey;
|
2021-05-08 21:31:55 -07:00
|
|
|
tx.recentBlockhash = (
|
2022-04-11 12:48:58 -07:00
|
|
|
await this.connection.getLatestBlockhash(
|
|
|
|
commitment ?? this.connection.commitment
|
2021-05-15 15:21:16 -07:00
|
|
|
)
|
2021-05-08 21:31:55 -07:00
|
|
|
).blockhash;
|
|
|
|
|
2022-07-14 01:23:44 -07:00
|
|
|
// Don't ask the wallet to sign
|
|
|
|
//tx = await this.wallet.signTransaction(tx);
|
|
|
|
|
2022-04-11 12:48:58 -07:00
|
|
|
const result = await simulateTransaction(
|
2021-05-15 15:21:16 -07:00
|
|
|
this.connection,
|
|
|
|
tx,
|
2022-04-11 12:48:58 -07:00
|
|
|
signers,
|
|
|
|
commitment,
|
|
|
|
includeAccounts
|
2021-05-15 15:21:16 -07:00
|
|
|
);
|
2022-04-11 12:48:58 -07:00
|
|
|
|
|
|
|
if (result.value.err) {
|
|
|
|
throw new SimulateError(result.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class SimulateError extends Error {
|
|
|
|
constructor(
|
|
|
|
readonly simulationResponse: SimulatedTransactionResponse,
|
|
|
|
message?: string
|
|
|
|
) {
|
|
|
|
super(message);
|
2021-05-08 21:31:55 -07:00
|
|
|
}
|
2021-01-27 19:31:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export type SendTxRequest = {
|
|
|
|
tx: Transaction;
|
2021-05-20 02:26:32 -07:00
|
|
|
signers: Array<Signer | undefined>;
|
2021-01-27 19:31:15 -08:00
|
|
|
};
|
|
|
|
|
2021-05-10 13:12:20 -07:00
|
|
|
/**
|
|
|
|
* Wallet interface for objects that can be used to sign provider transactions.
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
export interface Wallet {
|
|
|
|
signTransaction(tx: Transaction): Promise<Transaction>;
|
|
|
|
signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
|
|
|
|
publicKey: PublicKey;
|
|
|
|
}
|
|
|
|
|
2022-03-20 17:29:12 -07:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-27 11:29:46 -07:00
|
|
|
/**
|
|
|
|
* 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) {
|
2022-04-11 12:48:58 -07:00
|
|
|
return AnchorProvider.local();
|
2021-05-27 11:29:46 -07:00
|
|
|
}
|
|
|
|
return _provider;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global provider used as the default when a provider is not given.
|
|
|
|
let _provider: Provider | null = null;
|