provider.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import {
  2. Connection,
  3. Signer,
  4. PublicKey,
  5. Transaction,
  6. TransactionSignature,
  7. ConfirmOptions,
  8. sendAndConfirmRawTransaction,
  9. RpcResponseAndContext,
  10. SimulatedTransactionResponse,
  11. Commitment,
  12. } from "@solana/web3.js";
  13. import { isBrowser } from "./utils/common.js";
  14. /**
  15. * The network and wallet context used to send transactions paid for and signed
  16. * by the provider.
  17. */
  18. export default class Provider {
  19. /**
  20. * @param connection The cluster connection where the program is deployed.
  21. * @param wallet The wallet used to pay for and sign all transactions.
  22. * @param opts Transaction confirmation options to use by default.
  23. */
  24. constructor(
  25. readonly connection: Connection,
  26. readonly wallet: Wallet,
  27. readonly opts: ConfirmOptions
  28. ) {}
  29. static defaultOptions(): ConfirmOptions {
  30. return {
  31. preflightCommitment: "processed",
  32. commitment: "processed",
  33. };
  34. }
  35. /**
  36. * Returns a `Provider` with a wallet read from the local filesystem.
  37. *
  38. * @param url The network cluster url.
  39. * @param opts The default transaction confirmation options.
  40. *
  41. * (This api is for Node only.)
  42. */
  43. static local(url?: string, opts?: ConfirmOptions): Provider {
  44. if (isBrowser) {
  45. throw new Error(`Provider local is not available on browser.`);
  46. }
  47. opts = opts ?? Provider.defaultOptions();
  48. const connection = new Connection(
  49. url ?? "http://localhost:8899",
  50. opts.preflightCommitment
  51. );
  52. const NodeWallet = require("./nodewallet.js").default;
  53. const wallet = NodeWallet.local();
  54. return new Provider(connection, wallet, opts);
  55. }
  56. /**
  57. * Returns a `Provider` read from the `ANCHOR_PROVIDER_URL` environment
  58. * variable
  59. *
  60. * (This api is for Node only.)
  61. */
  62. static env(): Provider {
  63. if (isBrowser) {
  64. throw new Error(`Provider env is not available on browser.`);
  65. }
  66. const process = require("process");
  67. const url = process.env.ANCHOR_PROVIDER_URL;
  68. if (url === undefined) {
  69. throw new Error("ANCHOR_PROVIDER_URL is not defined");
  70. }
  71. const options = Provider.defaultOptions();
  72. const connection = new Connection(url, options.commitment);
  73. const NodeWallet = require("./nodewallet.js").default;
  74. const wallet = NodeWallet.local();
  75. return new Provider(connection, wallet, options);
  76. }
  77. /**
  78. * Sends the given transaction, paid for and signed by the provider's wallet.
  79. *
  80. * @param tx The transaction to send.
  81. * @param signers The set of signers in addition to the provider wallet that
  82. * will sign the transaction.
  83. * @param opts Transaction confirmation options.
  84. */
  85. async send(
  86. tx: Transaction,
  87. signers?: Array<Signer | undefined>,
  88. opts?: ConfirmOptions
  89. ): Promise<TransactionSignature> {
  90. if (signers === undefined) {
  91. signers = [];
  92. }
  93. if (opts === undefined) {
  94. opts = this.opts;
  95. }
  96. tx.feePayer = this.wallet.publicKey;
  97. tx.recentBlockhash = (
  98. await this.connection.getRecentBlockhash(opts.preflightCommitment)
  99. ).blockhash;
  100. await this.wallet.signTransaction(tx);
  101. signers
  102. .filter((s): s is Signer => s !== undefined)
  103. .forEach((kp) => {
  104. tx.partialSign(kp);
  105. });
  106. const rawTx = tx.serialize();
  107. const txId = await sendAndConfirmRawTransaction(
  108. this.connection,
  109. rawTx,
  110. opts
  111. );
  112. return txId;
  113. }
  114. /**
  115. * Similar to `send`, but for an array of transactions and signers.
  116. */
  117. async sendAll(
  118. reqs: Array<SendTxRequest>,
  119. opts?: ConfirmOptions
  120. ): Promise<Array<TransactionSignature>> {
  121. if (opts === undefined) {
  122. opts = this.opts;
  123. }
  124. const blockhash = await this.connection.getRecentBlockhash(
  125. opts.preflightCommitment
  126. );
  127. let txs = reqs.map((r) => {
  128. let tx = r.tx;
  129. let signers = r.signers;
  130. if (signers === undefined) {
  131. signers = [];
  132. }
  133. tx.feePayer = this.wallet.publicKey;
  134. tx.recentBlockhash = blockhash.blockhash;
  135. signers
  136. .filter((s): s is Signer => s !== undefined)
  137. .forEach((kp) => {
  138. tx.partialSign(kp);
  139. });
  140. return tx;
  141. });
  142. const signedTxs = await this.wallet.signAllTransactions(txs);
  143. const sigs: TransactionSignature[] = [];
  144. for (let k = 0; k < txs.length; k += 1) {
  145. const tx = signedTxs[k];
  146. const rawTx = tx.serialize();
  147. sigs.push(
  148. await sendAndConfirmRawTransaction(this.connection, rawTx, opts)
  149. );
  150. }
  151. return sigs;
  152. }
  153. /**
  154. * Simulates the given transaction, returning emitted logs from execution.
  155. *
  156. * @param tx The transaction to send.
  157. * @param signers The set of signers in addition to the provdier wallet that
  158. * will sign the transaction.
  159. * @param opts Transaction confirmation options.
  160. */
  161. async simulate(
  162. tx: Transaction,
  163. signers?: Array<Signer | undefined>,
  164. opts: ConfirmOptions = this.opts
  165. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  166. if (signers === undefined) {
  167. signers = [];
  168. }
  169. tx.feePayer = this.wallet.publicKey;
  170. tx.recentBlockhash = (
  171. await this.connection.getRecentBlockhash(
  172. opts.preflightCommitment ?? this.opts.preflightCommitment
  173. )
  174. ).blockhash;
  175. await this.wallet.signTransaction(tx);
  176. signers
  177. .filter((s): s is Signer => s !== undefined)
  178. .forEach((kp) => {
  179. tx.partialSign(kp);
  180. });
  181. return await simulateTransaction(
  182. this.connection,
  183. tx,
  184. opts.commitment ?? this.opts.commitment ?? "processed"
  185. );
  186. }
  187. }
  188. export type SendTxRequest = {
  189. tx: Transaction;
  190. signers: Array<Signer | undefined>;
  191. };
  192. /**
  193. * Wallet interface for objects that can be used to sign provider transactions.
  194. */
  195. export interface Wallet {
  196. signTransaction(tx: Transaction): Promise<Transaction>;
  197. signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
  198. publicKey: PublicKey;
  199. }
  200. // Copy of Connection.simulateTransaction that takes a commitment parameter.
  201. async function simulateTransaction(
  202. connection: Connection,
  203. transaction: Transaction,
  204. commitment: Commitment
  205. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  206. // @ts-ignore
  207. transaction.recentBlockhash = await connection._recentBlockhash(
  208. // @ts-ignore
  209. connection._disableBlockhashCaching
  210. );
  211. const signData = transaction.serializeMessage();
  212. // @ts-ignore
  213. const wireTransaction = transaction._serialize(signData);
  214. const encodedTransaction = wireTransaction.toString("base64");
  215. const config: any = { encoding: "base64", commitment };
  216. const args = [encodedTransaction, config];
  217. // @ts-ignore
  218. const res = await connection._rpcRequest("simulateTransaction", args);
  219. if (res.error) {
  220. throw new Error("failed to simulate transaction: " + res.error.message);
  221. }
  222. return res.result;
  223. }
  224. /**
  225. * Sets the default provider on the client.
  226. */
  227. export function setProvider(provider: Provider) {
  228. _provider = provider;
  229. }
  230. /**
  231. * Returns the default provider being used by the client.
  232. */
  233. export function getProvider(): Provider {
  234. if (_provider === null) {
  235. return Provider.local();
  236. }
  237. return _provider;
  238. }
  239. // Global provider used as the default when a provider is not given.
  240. let _provider: Provider | null = null;