provider.ts 7.6 KB

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