provider.ts 8.7 KB

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