provider.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import {
  2. Connection,
  3. Signer,
  4. PublicKey,
  5. Transaction,
  6. TransactionSignature,
  7. ConfirmOptions,
  8. RpcResponseAndContext,
  9. SimulatedTransactionResponse,
  10. Commitment,
  11. SendTransactionError,
  12. } from "@solana/web3.js";
  13. import { bs58 } from "./utils/bytes/index.js";
  14. import { isBrowser } from "./utils/common.js";
  15. /**
  16. * The network and wallet context used to send transactions paid for and signed
  17. * by the provider.
  18. */
  19. export default class Provider {
  20. /**
  21. * @param connection The cluster connection where the program is deployed.
  22. * @param wallet The wallet used to pay for and sign all transactions.
  23. * @param opts Transaction confirmation options to use by default.
  24. */
  25. constructor(
  26. readonly connection: Connection,
  27. readonly wallet: Wallet,
  28. readonly opts: ConfirmOptions
  29. ) {}
  30. static defaultOptions(): ConfirmOptions {
  31. return {
  32. preflightCommitment: "processed",
  33. commitment: "processed",
  34. };
  35. }
  36. /**
  37. * Returns a `Provider` with a wallet read from the local filesystem.
  38. *
  39. * @param url The network cluster url.
  40. * @param opts The default transaction confirmation options.
  41. *
  42. * (This api is for Node only.)
  43. */
  44. static local(url?: string, opts?: ConfirmOptions): Provider {
  45. if (isBrowser) {
  46. throw new Error(`Provider local is not available on browser.`);
  47. }
  48. opts = opts ?? Provider.defaultOptions();
  49. const connection = new Connection(
  50. url ?? "http://localhost:8899",
  51. opts.preflightCommitment
  52. );
  53. const NodeWallet = require("./nodewallet.js").default;
  54. const wallet = NodeWallet.local();
  55. return new Provider(connection, wallet, opts);
  56. }
  57. /**
  58. * Returns a `Provider` read from the `ANCHOR_PROVIDER_URL` environment
  59. * variable
  60. *
  61. * (This api is for Node only.)
  62. */
  63. static env(): Provider {
  64. if (isBrowser) {
  65. throw new Error(`Provider env is not available on browser.`);
  66. }
  67. const process = require("process");
  68. const url = process.env.ANCHOR_PROVIDER_URL;
  69. if (url === undefined) {
  70. throw new Error("ANCHOR_PROVIDER_URL is not defined");
  71. }
  72. const options = Provider.defaultOptions();
  73. const connection = new Connection(url, options.commitment);
  74. const NodeWallet = require("./nodewallet.js").default;
  75. const wallet = NodeWallet.local();
  76. return new Provider(connection, wallet, options);
  77. }
  78. /**
  79. * Sends the given transaction, paid for and signed by the provider's wallet.
  80. *
  81. * @param tx The transaction to send.
  82. * @param signers The set of signers in addition to the provider wallet that
  83. * will sign the transaction.
  84. * @param opts Transaction confirmation options.
  85. */
  86. async send(
  87. tx: Transaction,
  88. signers?: Array<Signer | undefined>,
  89. opts?: ConfirmOptions
  90. ): Promise<TransactionSignature> {
  91. if (signers === undefined) {
  92. signers = [];
  93. }
  94. if (opts === undefined) {
  95. opts = this.opts;
  96. }
  97. tx.feePayer = this.wallet.publicKey;
  98. tx.recentBlockhash = (
  99. await this.connection.getRecentBlockhash(opts.preflightCommitment)
  100. ).blockhash;
  101. tx = await this.wallet.signTransaction(tx);
  102. signers
  103. .filter((s): s is Signer => s !== undefined)
  104. .forEach((kp) => {
  105. tx.partialSign(kp);
  106. });
  107. const rawTx = tx.serialize();
  108. try {
  109. return await sendAndConfirmRawTransaction(this.connection, rawTx, opts);
  110. } catch (err) {
  111. // thrown if the underlying 'confirmTransaction' encounters a failed tx
  112. // the 'confirmTransaction' error does not return logs so we make another rpc call to get them
  113. if (err instanceof ConfirmError) {
  114. // choose the shortest available commitment for 'getTransaction'
  115. // (the json RPC does not support any shorter than "confirmed" for 'getTransaction')
  116. // because that will see the tx sent with `sendAndConfirmRawTransaction` no matter which
  117. // commitment `sendAndConfirmRawTransaction` used
  118. const failedTx = await this.connection.getTransaction(
  119. bs58.encode(tx.signature!),
  120. { commitment: "confirmed" }
  121. );
  122. if (!failedTx) {
  123. throw err;
  124. } else {
  125. const logs = failedTx.meta?.logMessages;
  126. throw !logs ? err : new SendTransactionError(err.message, logs);
  127. }
  128. } else {
  129. throw err;
  130. }
  131. }
  132. }
  133. /**
  134. * Similar to `send`, but for an array of transactions and signers.
  135. */
  136. async sendAll(
  137. reqs: Array<SendTxRequest>,
  138. opts?: ConfirmOptions
  139. ): Promise<Array<TransactionSignature>> {
  140. if (opts === undefined) {
  141. opts = this.opts;
  142. }
  143. const blockhash = await this.connection.getRecentBlockhash(
  144. opts.preflightCommitment
  145. );
  146. let txs = reqs.map((r) => {
  147. let tx = r.tx;
  148. let signers = r.signers;
  149. if (signers === undefined) {
  150. signers = [];
  151. }
  152. tx.feePayer = this.wallet.publicKey;
  153. tx.recentBlockhash = blockhash.blockhash;
  154. signers
  155. .filter((s): s is Signer => s !== undefined)
  156. .forEach((kp) => {
  157. tx.partialSign(kp);
  158. });
  159. return tx;
  160. });
  161. const signedTxs = await this.wallet.signAllTransactions(txs);
  162. const sigs: TransactionSignature[] = [];
  163. for (let k = 0; k < txs.length; k += 1) {
  164. const tx = signedTxs[k];
  165. const rawTx = tx.serialize();
  166. sigs.push(
  167. await sendAndConfirmRawTransaction(this.connection, rawTx, opts)
  168. );
  169. }
  170. return sigs;
  171. }
  172. /**
  173. * Simulates the given transaction, returning emitted logs from execution.
  174. *
  175. * @param tx The transaction to send.
  176. * @param signers The set of signers in addition to the provdier wallet that
  177. * will sign the transaction.
  178. * @param opts Transaction confirmation options.
  179. */
  180. async simulate(
  181. tx: Transaction,
  182. signers?: Array<Signer | undefined>,
  183. opts: ConfirmOptions = this.opts
  184. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  185. if (signers === undefined) {
  186. signers = [];
  187. }
  188. tx.feePayer = this.wallet.publicKey;
  189. tx.recentBlockhash = (
  190. await this.connection.getRecentBlockhash(
  191. opts.preflightCommitment ?? this.opts.preflightCommitment
  192. )
  193. ).blockhash;
  194. tx = await this.wallet.signTransaction(tx);
  195. signers
  196. .filter((s): s is Signer => s !== undefined)
  197. .forEach((kp) => {
  198. tx.partialSign(kp);
  199. });
  200. return await simulateTransaction(
  201. this.connection,
  202. tx,
  203. opts.commitment ?? this.opts.commitment ?? "processed"
  204. );
  205. }
  206. }
  207. export type SendTxRequest = {
  208. tx: Transaction;
  209. signers: Array<Signer | undefined>;
  210. };
  211. /**
  212. * Wallet interface for objects that can be used to sign provider transactions.
  213. */
  214. export interface Wallet {
  215. signTransaction(tx: Transaction): Promise<Transaction>;
  216. signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
  217. publicKey: PublicKey;
  218. }
  219. // Copy of Connection.simulateTransaction that takes a commitment parameter.
  220. async function simulateTransaction(
  221. connection: Connection,
  222. transaction: Transaction,
  223. commitment: Commitment
  224. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  225. // @ts-ignore
  226. transaction.recentBlockhash = await connection._recentBlockhash(
  227. // @ts-ignore
  228. connection._disableBlockhashCaching
  229. );
  230. const signData = transaction.serializeMessage();
  231. // @ts-ignore
  232. const wireTransaction = transaction._serialize(signData);
  233. const encodedTransaction = wireTransaction.toString("base64");
  234. const config: any = { encoding: "base64", commitment };
  235. const args = [encodedTransaction, config];
  236. // @ts-ignore
  237. const res = await connection._rpcRequest("simulateTransaction", args);
  238. if (res.error) {
  239. throw new Error("failed to simulate transaction: " + res.error.message);
  240. }
  241. return res.result;
  242. }
  243. // Copy of Connection.sendAndConfirmRawTransaction that throws
  244. // a better error if 'confirmTransaction` returns an error status
  245. async function sendAndConfirmRawTransaction(
  246. connection: Connection,
  247. rawTransaction: Buffer,
  248. options?: ConfirmOptions
  249. ): Promise<TransactionSignature> {
  250. const sendOptions = options && {
  251. skipPreflight: options.skipPreflight,
  252. preflightCommitment: options.preflightCommitment || options.commitment,
  253. };
  254. const signature = await connection.sendRawTransaction(
  255. rawTransaction,
  256. sendOptions
  257. );
  258. const status = (
  259. await connection.confirmTransaction(
  260. signature,
  261. options && options.commitment
  262. )
  263. ).value;
  264. if (status.err) {
  265. throw new ConfirmError(
  266. `Raw transaction ${signature} failed (${JSON.stringify(status)})`
  267. );
  268. }
  269. return signature;
  270. }
  271. class ConfirmError extends Error {
  272. constructor(message?: string) {
  273. super(message);
  274. }
  275. }
  276. /**
  277. * Sets the default provider on the client.
  278. */
  279. export function setProvider(provider: Provider) {
  280. _provider = provider;
  281. }
  282. /**
  283. * Returns the default provider being used by the client.
  284. */
  285. export function getProvider(): Provider {
  286. if (_provider === null) {
  287. return Provider.local();
  288. }
  289. return _provider;
  290. }
  291. // Global provider used as the default when a provider is not given.
  292. let _provider: Provider | null = null;