provider.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import {
  2. Connection,
  3. Keypair,
  4. Signer,
  5. PublicKey,
  6. Transaction,
  7. TransactionSignature,
  8. ConfirmOptions,
  9. sendAndConfirmRawTransaction,
  10. RpcResponseAndContext,
  11. SimulatedTransactionResponse,
  12. Commitment,
  13. } from "@solana/web3.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: "recent",
  32. commitment: "recent",
  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. opts = opts ?? Provider.defaultOptions();
  45. const connection = new Connection(
  46. url ?? "http://localhost:8899",
  47. opts.preflightCommitment
  48. );
  49. const wallet = NodeWallet.local();
  50. return new Provider(connection, wallet, opts);
  51. }
  52. /**
  53. * Returns a `Provider` read from the `ANCHOR_PROVIDER_URL` envirnment
  54. * variable
  55. *
  56. * (This api is for Node only.)
  57. */
  58. static env(): Provider {
  59. const process = require("process");
  60. const url = process.env.ANCHOR_PROVIDER_URL;
  61. if (url === undefined) {
  62. throw new Error("ANCHOR_PROVIDER_URL is not defined");
  63. }
  64. const options = Provider.defaultOptions();
  65. const connection = new Connection(url, options.commitment);
  66. const wallet = NodeWallet.local();
  67. return new Provider(connection, wallet, options);
  68. }
  69. /**
  70. * Sends the given transaction, paid for and signed by the provider's wallet.
  71. *
  72. * @param tx The transaction to send.
  73. * @param signers The set of signers in addition to the provdier wallet that
  74. * will sign the transaction.
  75. * @param opts Transaction confirmation options.
  76. */
  77. async send(
  78. tx: Transaction,
  79. signers?: Array<Signer | undefined>,
  80. opts?: ConfirmOptions
  81. ): Promise<TransactionSignature> {
  82. if (signers === undefined) {
  83. signers = [];
  84. }
  85. if (opts === undefined) {
  86. opts = this.opts;
  87. }
  88. tx.feePayer = this.wallet.publicKey;
  89. tx.recentBlockhash = (
  90. await this.connection.getRecentBlockhash(opts.preflightCommitment)
  91. ).blockhash;
  92. await this.wallet.signTransaction(tx);
  93. signers
  94. .filter((s) => s !== undefined)
  95. .forEach((kp) => {
  96. tx.partialSign(kp);
  97. });
  98. const rawTx = tx.serialize();
  99. const txId = await sendAndConfirmRawTransaction(
  100. this.connection,
  101. rawTx,
  102. opts
  103. );
  104. return txId;
  105. }
  106. /**
  107. * Similar to `send`, but for an array of transactions and signers.
  108. */
  109. async sendAll(
  110. reqs: Array<SendTxRequest>,
  111. opts?: ConfirmOptions
  112. ): Promise<Array<TransactionSignature>> {
  113. if (opts === undefined) {
  114. opts = this.opts;
  115. }
  116. const blockhash = await this.connection.getRecentBlockhash(
  117. opts.preflightCommitment
  118. );
  119. let txs = reqs.map((r) => {
  120. let tx = r.tx;
  121. let signers = r.signers;
  122. if (signers === undefined) {
  123. signers = [];
  124. }
  125. tx.feePayer = this.wallet.publicKey;
  126. tx.recentBlockhash = blockhash.blockhash;
  127. signers
  128. .filter((s) => s !== undefined)
  129. .forEach((kp) => {
  130. tx.partialSign(kp);
  131. });
  132. return tx;
  133. });
  134. const signedTxs = await this.wallet.signAllTransactions(txs);
  135. const sigs = [];
  136. for (let k = 0; k < txs.length; k += 1) {
  137. const tx = signedTxs[k];
  138. const rawTx = tx.serialize();
  139. sigs.push(
  140. await sendAndConfirmRawTransaction(this.connection, rawTx, opts)
  141. );
  142. }
  143. return sigs;
  144. }
  145. /**
  146. * Simulates the given transaction, returning emitted logs from execution.
  147. *
  148. * @param tx The transaction to send.
  149. * @param signers The set of signers in addition to the provdier wallet that
  150. * will sign the transaction.
  151. * @param opts Transaction confirmation options.
  152. */
  153. async simulate(
  154. tx: Transaction,
  155. signers?: Array<Signer | undefined>,
  156. opts?: ConfirmOptions
  157. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  158. if (signers === undefined) {
  159. signers = [];
  160. }
  161. if (opts === undefined) {
  162. opts = this.opts;
  163. }
  164. tx.feePayer = this.wallet.publicKey;
  165. tx.recentBlockhash = (
  166. await this.connection.getRecentBlockhash(
  167. opts.preflightCommitment ?? this.opts.preflightCommitment
  168. )
  169. ).blockhash;
  170. await this.wallet.signTransaction(tx);
  171. signers
  172. .filter((s) => s !== undefined)
  173. .forEach((kp) => {
  174. tx.partialSign(kp);
  175. });
  176. return await simulateTransaction(
  177. this.connection,
  178. tx,
  179. opts.commitment ?? this.opts.commitment
  180. );
  181. }
  182. }
  183. export type SendTxRequest = {
  184. tx: Transaction;
  185. signers: Array<Signer | undefined>;
  186. };
  187. /**
  188. * Wallet interface for objects that can be used to sign provider transactions.
  189. */
  190. export interface Wallet {
  191. signTransaction(tx: Transaction): Promise<Transaction>;
  192. signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
  193. publicKey: PublicKey;
  194. }
  195. /**
  196. * Node only wallet.
  197. */
  198. export class NodeWallet implements Wallet {
  199. constructor(readonly payer: Keypair) {}
  200. static local(): NodeWallet {
  201. const payer = Keypair.fromSecretKey(
  202. Buffer.from(
  203. JSON.parse(
  204. require("fs").readFileSync(
  205. require("os").homedir() + "/.config/solana/id.json",
  206. {
  207. encoding: "utf-8",
  208. }
  209. )
  210. )
  211. )
  212. );
  213. return new NodeWallet(payer);
  214. }
  215. async signTransaction(tx: Transaction): Promise<Transaction> {
  216. tx.partialSign(this.payer);
  217. return tx;
  218. }
  219. async signAllTransactions(txs: Transaction[]): Promise<Transaction[]> {
  220. return txs.map((t) => {
  221. t.partialSign(this.payer);
  222. return t;
  223. });
  224. }
  225. get publicKey(): PublicKey {
  226. return this.payer.publicKey;
  227. }
  228. }
  229. // Copy of Connection.simulateTransaction that takes a commitment parameter.
  230. async function simulateTransaction(
  231. connection: Connection,
  232. transaction: Transaction,
  233. commitment: Commitment
  234. ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
  235. // @ts-ignore
  236. transaction.recentBlockhash = await connection._recentBlockhash(
  237. // @ts-ignore
  238. connection._disableBlockhashCaching
  239. );
  240. const signData = transaction.serializeMessage();
  241. // @ts-ignore
  242. const wireTransaction = transaction._serialize(signData);
  243. const encodedTransaction = wireTransaction.toString("base64");
  244. const config: any = { encoding: "base64", commitment };
  245. const args = [encodedTransaction, config];
  246. // @ts-ignore
  247. const res = await connection._rpcRequest("simulateTransaction", args);
  248. if (res.error) {
  249. throw new Error("failed to simulate transaction: " + res.error.message);
  250. }
  251. return res.result;
  252. }
  253. /**
  254. * Sets the default provider on the client.
  255. */
  256. export function setProvider(provider: Provider) {
  257. _provider = provider;
  258. }
  259. /**
  260. * Returns the default provider being used by the client.
  261. */
  262. export function getProvider(): Provider {
  263. if (_provider === null) {
  264. return Provider.local();
  265. }
  266. return _provider;
  267. }
  268. // Global provider used as the default when a provider is not given.
  269. let _provider: Provider | null = null;