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,
|
|
|
|
sendAndConfirmRawTransaction,
|
2021-05-08 21:31:55 -07:00
|
|
|
RpcResponseAndContext,
|
|
|
|
SimulatedTransactionResponse,
|
|
|
|
Commitment,
|
2021-01-27 19:31:15 -08:00
|
|
|
} from "@solana/web3.js";
|
2021-11-28 08:02:58 -08:00
|
|
|
import { isBrowser } from "./utils/common.js";
|
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.
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
export default class Provider {
|
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
|
|
|
|
) {}
|
|
|
|
|
|
|
|
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.)
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
static local(url?: string, opts?: ConfirmOptions): Provider {
|
2021-12-07 12:16:46 -08:00
|
|
|
if (isBrowser) {
|
|
|
|
throw new Error(`Provider local is not available on browser.`);
|
|
|
|
}
|
2021-05-19 13:26:09 -07:00
|
|
|
opts = opts ?? Provider.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();
|
|
|
|
return new Provider(connection, wallet, opts);
|
|
|
|
}
|
|
|
|
|
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.)
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
static env(): Provider {
|
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");
|
|
|
|
}
|
|
|
|
const options = Provider.defaultOptions();
|
|
|
|
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();
|
|
|
|
|
|
|
|
return new Provider(connection, wallet, options);
|
|
|
|
}
|
|
|
|
|
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.
|
2021-09-22 10:22:53 -07:00
|
|
|
* @param signers The set of signers in addition to the provider wallet that
|
2021-05-10 13:12:20 -07:00
|
|
|
* will sign the transaction.
|
|
|
|
* @param opts Transaction confirmation options.
|
|
|
|
*/
|
2021-01-27 19:31:15 -08:00
|
|
|
async send(
|
|
|
|
tx: Transaction,
|
2021-05-20 02:26:32 -07:00
|
|
|
signers?: Array<Signer | undefined>,
|
2021-01-27 19:31:15 -08:00
|
|
|
opts?: ConfirmOptions
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
if (signers === undefined) {
|
|
|
|
signers = [];
|
|
|
|
}
|
|
|
|
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);
|
2021-05-27 13:03:08 -07:00
|
|
|
signers
|
2021-09-16 13:42:11 -07:00
|
|
|
.filter((s): s is Signer => s !== undefined)
|
2021-05-27 13:03:08 -07:00
|
|
|
.forEach((kp) => {
|
|
|
|
tx.partialSign(kp);
|
|
|
|
});
|
2021-01-27 19:31:15 -08:00
|
|
|
|
|
|
|
const rawTx = tx.serialize();
|
|
|
|
|
|
|
|
const txId = await sendAndConfirmRawTransaction(
|
|
|
|
this.connection,
|
|
|
|
rawTx,
|
|
|
|
opts
|
|
|
|
);
|
|
|
|
|
|
|
|
return txId;
|
|
|
|
}
|
|
|
|
|
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(
|
|
|
|
reqs: Array<SendTxRequest>,
|
|
|
|
opts?: ConfirmOptions
|
|
|
|
): Promise<Array<TransactionSignature>> {
|
|
|
|
if (opts === undefined) {
|
|
|
|
opts = this.opts;
|
|
|
|
}
|
|
|
|
const blockhash = await this.connection.getRecentBlockhash(
|
|
|
|
opts.preflightCommitment
|
|
|
|
);
|
|
|
|
|
|
|
|
let txs = reqs.map((r) => {
|
|
|
|
let tx = r.tx;
|
|
|
|
let signers = r.signers;
|
|
|
|
|
|
|
|
if (signers === undefined) {
|
|
|
|
signers = [];
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
signers
|
2021-09-16 13:42:11 -07:00
|
|
|
.filter((s): s is Signer => s !== undefined)
|
2021-05-27 13:03:08 -07:00
|
|
|
.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.
|
|
|
|
* @param signers The set of signers in addition to the provdier wallet that
|
|
|
|
* will sign the transaction.
|
|
|
|
* @param opts Transaction confirmation options.
|
|
|
|
*/
|
2021-05-08 21:31:55 -07:00
|
|
|
async simulate(
|
|
|
|
tx: Transaction,
|
2021-05-20 02:26:32 -07:00
|
|
|
signers?: Array<Signer | undefined>,
|
2021-09-16 13:42:11 -07:00
|
|
|
opts: ConfirmOptions = this.opts
|
2021-05-08 21:31:55 -07:00
|
|
|
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
|
|
|
if (signers === undefined) {
|
|
|
|
signers = [];
|
|
|
|
}
|
|
|
|
|
2021-05-27 13:03:08 -07:00
|
|
|
tx.feePayer = this.wallet.publicKey;
|
2021-05-08 21:31:55 -07:00
|
|
|
tx.recentBlockhash = (
|
2021-05-15 15:21:16 -07:00
|
|
|
await this.connection.getRecentBlockhash(
|
|
|
|
opts.preflightCommitment ?? this.opts.preflightCommitment
|
|
|
|
)
|
2021-05-08 21:31:55 -07:00
|
|
|
).blockhash;
|
|
|
|
|
2022-03-20 11:11:41 -07:00
|
|
|
tx = await this.wallet.signTransaction(tx);
|
2021-05-27 13:03:08 -07:00
|
|
|
signers
|
2021-09-16 13:42:11 -07:00
|
|
|
.filter((s): s is Signer => s !== undefined)
|
2021-05-27 13:03:08 -07:00
|
|
|
.forEach((kp) => {
|
|
|
|
tx.partialSign(kp);
|
|
|
|
});
|
|
|
|
|
2021-05-15 15:21:16 -07:00
|
|
|
return await simulateTransaction(
|
|
|
|
this.connection,
|
|
|
|
tx,
|
2021-12-20 01:51:02 -08:00
|
|
|
opts.commitment ?? this.opts.commitment ?? "processed"
|
2021-05-15 15:21:16 -07:00
|
|
|
);
|
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;
|
|
|
|
}
|
|
|
|
|
2021-05-08 21:31:55 -07:00
|
|
|
// 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;
|
|
|
|
}
|
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) {
|
|
|
|
return Provider.local();
|
|
|
|
}
|
|
|
|
return _provider;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global provider used as the default when a provider is not given.
|
|
|
|
let _provider: Provider | null = null;
|