provider.ts 8.7 KB

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