@umbra-privacy/sdk 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +104 -25
  2. package/dist/{addresses-Brzgurv_.d.ts → addresses-B7HybtbJ.d.ts} +2 -1
  3. package/dist/{addresses-D_0YAS6B.d.cts → addresses-CTVY1oi7.d.cts} +2 -1
  4. package/dist/arcium-BXXlryfe.d.cts +20 -0
  5. package/dist/arcium-BXXlryfe.d.ts +20 -0
  6. package/dist/chunk-3LS5P32X.cjs +10892 -0
  7. package/dist/chunk-3LS5P32X.cjs.map +1 -0
  8. package/dist/chunk-4RHXVBNI.js +203 -0
  9. package/dist/chunk-4RHXVBNI.js.map +1 -0
  10. package/dist/chunk-4TZVXB5G.js +324 -0
  11. package/dist/chunk-4TZVXB5G.js.map +1 -0
  12. package/dist/chunk-5GUSMQ74.cjs +549 -0
  13. package/dist/chunk-5GUSMQ74.cjs.map +1 -0
  14. package/dist/chunk-5KPQXPQM.js +36 -0
  15. package/dist/chunk-5KPQXPQM.js.map +1 -0
  16. package/dist/chunk-AXD7LXYY.cjs +405 -0
  17. package/dist/chunk-AXD7LXYY.cjs.map +1 -0
  18. package/dist/{chunk-HOEXDXRC.cjs → chunk-BL6WXLPV.cjs} +32 -360
  19. package/dist/chunk-BL6WXLPV.cjs.map +1 -0
  20. package/dist/chunk-CFFLOE7D.cjs +598 -0
  21. package/dist/chunk-CFFLOE7D.cjs.map +1 -0
  22. package/dist/{chunk-BM7N6N7E.js → chunk-CFTW5WNG.js} +3 -325
  23. package/dist/chunk-CFTW5WNG.js.map +1 -0
  24. package/dist/chunk-DD2WCK4C.js +327 -0
  25. package/dist/chunk-DD2WCK4C.js.map +1 -0
  26. package/dist/chunk-DMPMQ74B.cjs +246 -0
  27. package/dist/chunk-DMPMQ74B.cjs.map +1 -0
  28. package/dist/{chunk-2Q75CQQJ.js → chunk-EEKF4553.js} +2 -2
  29. package/dist/chunk-EEKF4553.js.map +1 -0
  30. package/dist/chunk-ENVYYEM4.cjs +113 -0
  31. package/dist/chunk-ENVYYEM4.cjs.map +1 -0
  32. package/dist/chunk-FQX6ZYGJ.js +500 -0
  33. package/dist/chunk-FQX6ZYGJ.js.map +1 -0
  34. package/dist/chunk-FSK2ICMB.cjs +39 -0
  35. package/dist/chunk-FSK2ICMB.cjs.map +1 -0
  36. package/dist/chunk-FZYWLQAF.cjs +355 -0
  37. package/dist/chunk-FZYWLQAF.cjs.map +1 -0
  38. package/dist/chunk-GP26R377.js +436 -0
  39. package/dist/chunk-GP26R377.js.map +1 -0
  40. package/dist/chunk-HA5FLM63.js +393 -0
  41. package/dist/chunk-HA5FLM63.js.map +1 -0
  42. package/dist/chunk-INJ73LXQ.js +1107 -0
  43. package/dist/chunk-INJ73LXQ.js.map +1 -0
  44. package/dist/chunk-KMRROOME.js +10750 -0
  45. package/dist/chunk-KMRROOME.js.map +1 -0
  46. package/dist/{chunk-MDFSBU5W.cjs → chunk-LTCKPTZC.cjs} +2 -351
  47. package/dist/chunk-LTCKPTZC.cjs.map +1 -0
  48. package/dist/chunk-MKNCBUFA.js +564 -0
  49. package/dist/chunk-MKNCBUFA.js.map +1 -0
  50. package/dist/chunk-NKVMSABR.cjs +207 -0
  51. package/dist/chunk-NKVMSABR.cjs.map +1 -0
  52. package/dist/chunk-OFDWNWCL.js +70 -0
  53. package/dist/chunk-OFDWNWCL.js.map +1 -0
  54. package/dist/chunk-QJAUUYZU.cjs +331 -0
  55. package/dist/chunk-QJAUUYZU.cjs.map +1 -0
  56. package/dist/chunk-TLR7A64G.js +103 -0
  57. package/dist/chunk-TLR7A64G.js.map +1 -0
  58. package/dist/{chunk-MVKTV3FT.cjs → chunk-TQQZGNOI.cjs} +2 -2
  59. package/dist/chunk-TQQZGNOI.cjs.map +1 -0
  60. package/dist/chunk-UOFYS6M3.js +219 -0
  61. package/dist/chunk-UOFYS6M3.js.map +1 -0
  62. package/dist/chunk-UXMQI6B7.js +2406 -0
  63. package/dist/chunk-UXMQI6B7.js.map +1 -0
  64. package/dist/chunk-WN75ORDT.js +571 -0
  65. package/dist/chunk-WN75ORDT.js.map +1 -0
  66. package/dist/chunk-Y55PYKXH.cjs +595 -0
  67. package/dist/chunk-Y55PYKXH.cjs.map +1 -0
  68. package/dist/chunk-YEZBTYCP.cjs +77 -0
  69. package/dist/chunk-YEZBTYCP.cjs.map +1 -0
  70. package/dist/chunk-ZQOIYCGA.cjs +1126 -0
  71. package/dist/chunk-ZQOIYCGA.cjs.map +1 -0
  72. package/dist/chunk-ZY3TSHMJ.cjs +2665 -0
  73. package/dist/chunk-ZY3TSHMJ.cjs.map +1 -0
  74. package/dist/client-DkVBHMWb.d.cts +2613 -0
  75. package/dist/client-V4AF6Bz9.d.ts +2613 -0
  76. package/dist/common/pda/index.cjs +145 -0
  77. package/dist/common/pda/index.cjs.map +1 -0
  78. package/dist/common/pda/index.d.cts +1250 -0
  79. package/dist/common/pda/index.d.ts +1250 -0
  80. package/dist/common/pda/index.js +8 -0
  81. package/dist/common/pda/index.js.map +1 -0
  82. package/dist/constants/index.cjs +38 -164
  83. package/dist/constants/index.cjs.map +1 -1
  84. package/dist/constants/index.d.cts +8 -425
  85. package/dist/constants/index.d.ts +8 -425
  86. package/dist/constants/index.js +15 -124
  87. package/dist/constants/index.js.map +1 -1
  88. package/dist/crypto/index.cjs +583 -0
  89. package/dist/crypto/index.cjs.map +1 -0
  90. package/dist/crypto/index.d.cts +6731 -0
  91. package/dist/crypto/index.d.ts +6731 -0
  92. package/dist/crypto/index.js +14 -0
  93. package/dist/crypto/index.js.map +1 -0
  94. package/dist/{cryptography-BTGC72u-.d.ts → cryptography-BFSJcvi6.d.ts} +3 -2465
  95. package/dist/{cryptography-BTGC72u-.d.cts → cryptography-D6tPDh-Y.d.cts} +3 -2465
  96. package/dist/errors/index.cjs +64 -54
  97. package/dist/errors/index.d.cts +7 -797
  98. package/dist/errors/index.d.ts +7 -797
  99. package/dist/errors/index.js +3 -1
  100. package/dist/errors-B9EoPeWV.d.cts +593 -0
  101. package/dist/errors-B9EoPeWV.d.ts +593 -0
  102. package/dist/errors-DAIrstEL.d.cts +300 -0
  103. package/dist/errors-DPNMfyh0.d.ts +300 -0
  104. package/dist/index-BG0yjL7C.d.cts +6006 -0
  105. package/dist/index-ByynoyBO.d.ts +6006 -0
  106. package/dist/index.cjs +5133 -16116
  107. package/dist/index.cjs.map +1 -1
  108. package/dist/index.d.cts +1031 -7685
  109. package/dist/index.d.ts +1031 -7685
  110. package/dist/index.js +3228 -14905
  111. package/dist/index.js.map +1 -1
  112. package/dist/interfaces/index.d.cts +14 -6
  113. package/dist/interfaces/index.d.ts +14 -6
  114. package/dist/interfaces-43cReBcS.d.cts +3346 -0
  115. package/dist/interfaces-B8xKNl_6.d.ts +997 -0
  116. package/dist/interfaces-D2NO6kDD.d.cts +997 -0
  117. package/dist/interfaces-z_xYJlgV.d.ts +3346 -0
  118. package/dist/math/index.cjs +115 -0
  119. package/dist/math/index.cjs.map +1 -0
  120. package/dist/math/index.d.cts +1327 -0
  121. package/dist/math/index.d.ts +1327 -0
  122. package/dist/math/index.js +10 -0
  123. package/dist/math/index.js.map +1 -0
  124. package/dist/networks-RMd3abPE.d.ts +44 -0
  125. package/dist/networks-yAoO8peQ.d.cts +44 -0
  126. package/dist/relayer-NRRMSMNB.js +4 -0
  127. package/dist/relayer-NRRMSMNB.js.map +1 -0
  128. package/dist/relayer-RJHEIXJG.cjs +21 -0
  129. package/dist/relayer-RJHEIXJG.cjs.map +1 -0
  130. package/dist/solana/index.cjs +56 -0
  131. package/dist/solana/index.cjs.map +1 -0
  132. package/dist/solana/index.d.cts +105 -0
  133. package/dist/solana/index.d.ts +105 -0
  134. package/dist/solana/index.js +7 -0
  135. package/dist/solana/index.js.map +1 -0
  136. package/dist/{index-CLj_zWSD.d.ts → temporal-BbRaEPoO.d.ts} +1 -1
  137. package/dist/{index-CX6_pIRS.d.cts → temporal-oUj7iCaq.d.cts} +1 -1
  138. package/dist/transaction-forwarder-5mAMTjw6.d.ts +1155 -0
  139. package/dist/transaction-forwarder-C6gMUG7a.d.cts +1155 -0
  140. package/dist/types/index.cjs +232 -231
  141. package/dist/types/index.d.cts +15 -1485
  142. package/dist/types/index.d.ts +15 -1485
  143. package/dist/types/index.js +2 -1
  144. package/dist/types-BohhvPth.d.cts +87 -0
  145. package/dist/types-CW0oTT0j.d.ts +87 -0
  146. package/dist/types-C_V_CaKK.d.cts +2468 -0
  147. package/dist/types-C_V_CaKK.d.ts +2468 -0
  148. package/dist/types-Ca7frykr.d.ts +793 -0
  149. package/dist/types-CuKeoI19.d.cts +1296 -0
  150. package/dist/types-CxfTIpN9.d.ts +1052 -0
  151. package/dist/{types-n-sHFcgr.d.ts → types-D1jDUjfN.d.ts} +2 -2
  152. package/dist/types-DKEDUlH9.d.ts +1296 -0
  153. package/dist/types-EKuIfxTz.d.cts +1052 -0
  154. package/dist/{types-BBuELtY8.d.cts → types-IMGYmlv-.d.cts} +2 -2
  155. package/dist/types-PwNLi_2k.d.cts +793 -0
  156. package/dist/utils/index.cjs +823 -525
  157. package/dist/utils/index.d.cts +1711 -4021
  158. package/dist/utils/index.d.ts +1711 -4021
  159. package/dist/utils/index.js +9 -3
  160. package/dist/{versions-D9PqsEvj.d.cts → versions-BRlR36EA.d.cts} +1 -0
  161. package/dist/{versions-D9PqsEvj.d.ts → versions-BRlR36EA.d.ts} +1 -0
  162. package/package.json +79 -18
  163. package/dist/chunk-2Q75CQQJ.js.map +0 -1
  164. package/dist/chunk-BM7N6N7E.js.map +0 -1
  165. package/dist/chunk-GXKSUB2U.cjs +0 -4416
  166. package/dist/chunk-GXKSUB2U.cjs.map +0 -1
  167. package/dist/chunk-HOEXDXRC.cjs.map +0 -1
  168. package/dist/chunk-MDFSBU5W.cjs.map +0 -1
  169. package/dist/chunk-MQY7HDIA.js +0 -600
  170. package/dist/chunk-MQY7HDIA.js.map +0 -1
  171. package/dist/chunk-MVKTV3FT.cjs.map +0 -1
  172. package/dist/chunk-PG2J6V6Y.js +0 -4094
  173. package/dist/chunk-PG2J6V6Y.js.map +0 -1
  174. package/dist/chunk-VEGLTTYQ.cjs +0 -621
  175. package/dist/chunk-VEGLTTYQ.cjs.map +0 -1
  176. package/dist/chunk-WVHQ46DD.js +0 -758
  177. package/dist/chunk-WVHQ46DD.js.map +0 -1
  178. package/dist/index-B9pDY73x.d.ts +0 -12933
  179. package/dist/index-D33yo0qB.d.cts +0 -12933
  180. package/dist/networks-C-orpSFW.d.ts +0 -65
  181. package/dist/networks-FxYERGD1.d.cts +0 -65
@@ -0,0 +1,1155 @@
1
+ import { A as AccountInfoProviderFunction, C as CreateSolanaRpcFunction, G as GetLatestBlockhash, a as GetEpochInfo, I as IUmbraSigner, b as CreateSolanaRpcSubscriptionsFunction, S as SendAndConfirmTransactionFactoryFunction, T as TransactionForwarder } from './client-V4AF6Bz9.js';
2
+ import { KeyPairSigner, Commitment } from '@solana/kit';
3
+ import { Wallet, WalletAccount } from '@wallet-standard/core';
4
+ import { T as TransactionSignature } from './types-CxfTIpN9.js';
5
+
6
+ /**
7
+ * RPC Account Info Provider.
8
+ *
9
+ * This module provides the factory function {@link getRpcAccountInfoProvider}
10
+ * that creates an {@link AccountInfoProviderFunction} for fetching raw, encoded
11
+ * Solana account data via the JSON-RPC API.
12
+ *
13
+ * ## Overview
14
+ *
15
+ * The provider wraps Solana's `getMultipleAccounts` RPC method, exposed through
16
+ * `@solana/kit`'s `fetchEncodedAccounts` helper. It returns account data as
17
+ * `MaybeEncodedAccount` values — discriminated unions that indicate whether
18
+ * each queried account exists on-chain.
19
+ *
20
+ * ## How It Works
21
+ *
22
+ * 1. The factory accepts an RPC URL and returns a **function** (not an object).
23
+ * 2. The function accepts an array of Solana addresses and returns a
24
+ * `Map<Address, MaybeEncodedAccount>`.
25
+ * 3. Input addresses are **deduplicated** before the RPC call to avoid wasting
26
+ * bandwidth on redundant `getMultipleAccounts` slots.
27
+ * 4. The RPC client is created **lazily** on first invocation — no connection
28
+ * is established until the function is actually called.
29
+ * 5. Subsequent calls reuse the same RPC client instance (singleton per factory).
30
+ *
31
+ * ## Usage in the SDK
32
+ *
33
+ * This provider is wired into the {@link IUmbraClient} at construction time via
34
+ * `getUmbraClient()`. Service factories (deposit, claim, withdraw, etc.) receive
35
+ * it through their dependency injection and use it to:
36
+ *
37
+ * - Fetch `EncryptedUserAccount` and `EncryptedTokenAccount` PDAs to determine
38
+ * instruction variant selection (e.g., new vs. existing account).
39
+ * - Fetch `ProtocolConfig` to validate program state.
40
+ * - Fetch `FeeSchedule` and `FeeVault` accounts for fee calculation.
41
+ * - Fetch mint accounts for Token-2022 transfer fee configuration.
42
+ *
43
+ * ## Decoding Accounts
44
+ *
45
+ * The returned `MaybeEncodedAccount` values carry raw base64-decoded bytes.
46
+ * Pass them to Codama-generated decoders to obtain typed program account structs:
47
+ *
48
+ * ```typescript
49
+ * import { decodeEncryptedUserAccount } from "@umbra-privacy/umbra-codama";
50
+ *
51
+ * const accountMap = await fetchAccounts([userPda]);
52
+ * const maybeAccount = accountMap.get(userPda);
53
+ * if (maybeAccount?.exists) {
54
+ * const decoded = decodeEncryptedUserAccount(maybeAccount);
55
+ * console.log("User commitment:", decoded.data.userCommitment);
56
+ * }
57
+ * ```
58
+ *
59
+ * ## Error Handling
60
+ *
61
+ * RPC errors (network failures, rate limiting, malformed responses) propagate
62
+ * as rejected promises. The provider does **not** implement retry logic —
63
+ * callers are responsible for wrapping calls in their own retry/backoff
64
+ * strategy if needed.
65
+ *
66
+ * ## Testing
67
+ *
68
+ * The `AccountInfoProviderFunction` type is a plain async function, making it
69
+ * trivial to mock in tests without any RPC dependency:
70
+ *
71
+ * ```typescript
72
+ * const mockProvider: AccountInfoProviderFunction = async (addresses) => {
73
+ * const map = new Map<Address, MaybeEncodedAccount>();
74
+ * for (const addr of addresses) {
75
+ * map.set(addr, { exists: false });
76
+ * }
77
+ * return map;
78
+ * };
79
+ * ```
80
+ *
81
+ * @see {@link AccountInfoProviderFunction} for the returned function type
82
+ * @see {@link getRpcAccountInfoProvider} for the factory function
83
+ *
84
+ * @packageDocumentation
85
+ * @since 2.0.0
86
+ * @module solana/account-info-provider
87
+ */
88
+
89
+ /**
90
+ * Configuration for the RPC-based account info provider.
91
+ *
92
+ * @since 2.0.0
93
+ * @public
94
+ */
95
+ interface RpcBasedAccountInfoProviderConfig {
96
+ /**
97
+ * The URL of the Solana RPC endpoint.
98
+ *
99
+ * @remarks
100
+ * Must be an HTTP or HTTPS URL pointing to a Solana JSON-RPC server.
101
+ * The provider uses `getMultipleAccounts` under the hood via `@solana/kit`'s
102
+ * `fetchEncodedAccounts` helper.
103
+ *
104
+ * Both public RPC endpoints (e.g., `https://api.mainnet-beta.solana.com`)
105
+ * and private/paid endpoints (e.g., Helius, QuickNode, Triton) are supported.
106
+ * Private endpoints are recommended for production use to avoid rate limiting.
107
+ *
108
+ * @example `"https://api.mainnet-beta.solana.com"`
109
+ * @example `"https://mainnet.helius-rpc.com/?api-key=YOUR_KEY"`
110
+ */
111
+ readonly rpcUrl: string;
112
+ }
113
+ /**
114
+ * Creates an {@link AccountInfoProviderFunction} that fetches account data
115
+ * from a Solana RPC endpoint.
116
+ *
117
+ * @remarks
118
+ * The returned function is the **primary account-fetching interface** used
119
+ * throughout the Umbra SDK. It is designed as a plain async function (not a
120
+ * class or object with methods) to support the SDK's functional dependency
121
+ * injection pattern and make mocking trivial.
122
+ *
123
+ * ## Lifecycle
124
+ *
125
+ * ```
126
+ * getRpcAccountInfoProvider({ rpcUrl })
127
+ * └─► Returns: async (addresses: Address[]) => Map<Address, MaybeEncodedAccount>
128
+ * │
129
+ * ├─ First call: creates RPC client (lazy singleton)
130
+ * ├─ Deduplicates input addresses
131
+ * ├─ Calls getMultipleAccounts via fetchEncodedAccounts
132
+ * └─ Returns Map<Address, MaybeEncodedAccount>
133
+ * ```
134
+ *
135
+ * ## Commitment Level
136
+ *
137
+ * Each call can specify a commitment level via `options.commitment`.
138
+ * Defaults to `"confirmed"` when not specified. The commitment controls
139
+ * how finalized the account data must be before it is returned.
140
+ *
141
+ * ## Deduplication
142
+ *
143
+ * Duplicate entries in the `addresses` array are removed before the RPC call.
144
+ * Each unique address appears exactly once in the `getMultipleAccounts` request.
145
+ * The returned `Map` contains one entry per unique input address, in first-seen
146
+ * order.
147
+ *
148
+ * ## Empty Input
149
+ *
150
+ * Passing an empty array short-circuits immediately and returns an empty `Map`
151
+ * without making any RPC call.
152
+ *
153
+ * ## Error Propagation
154
+ *
155
+ * RPC errors (network failures, endpoint unreachable, rate limiting, malformed
156
+ * responses) are **not caught** and propagate as rejected promises. Callers
157
+ * should implement retry logic for production use. Common retryable errors:
158
+ *
159
+ * - HTTP 429 (Too Many Requests) — back off and retry
160
+ * - HTTP 502/503/504 (Server Error) — transient, retry with backoff
161
+ * - Network timeout — retry with increased timeout
162
+ *
163
+ * ## Solana RPC Limits
164
+ *
165
+ * `getMultipleAccounts` supports up to **100 addresses** per call. If more
166
+ * than 100 unique addresses are passed, `@solana/kit` will automatically
167
+ * batch them into multiple RPC calls. However, this increases latency and
168
+ * the risk of partial failure.
169
+ *
170
+ * @param config - Configuration containing the RPC endpoint URL.
171
+ * @returns An {@link AccountInfoProviderFunction}: an async function that
172
+ * accepts a readonly array of {@link Address} values and resolves to a
173
+ * `Map<Address, MaybeEncodedAccount>`. Each map entry corresponds to one
174
+ * unique input address, with `{ exists: true, data, ... }` for on-chain
175
+ * accounts or `{ exists: false }` for missing accounts.
176
+ *
177
+ * @throws The returned function may throw (or reject) with:
178
+ * - Network errors if the RPC endpoint is unreachable
179
+ * - HTTP errors if the endpoint rate-limits or returns an error status
180
+ * - JSON-RPC errors if the request parameters are invalid
181
+ *
182
+ * @example
183
+ * Basic usage — fetching two accounts:
184
+ * ```typescript
185
+ * import { getRpcAccountInfoProvider } from "@umbra-privacy/sdk";
186
+ * import { address } from "@solana/kit";
187
+ *
188
+ * const fetchAccounts = getRpcAccountInfoProvider({
189
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
190
+ * });
191
+ *
192
+ * const addresses = [
193
+ * address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
194
+ * address("11111111111111111111111111111111"),
195
+ * ];
196
+ *
197
+ * const accountMap = await fetchAccounts(addresses);
198
+ *
199
+ * for (const [addr, account] of accountMap) {
200
+ * if (account.exists) {
201
+ * console.log(`${addr}: ${account.data.length} bytes`);
202
+ * } else {
203
+ * console.log(`${addr}: does not exist`);
204
+ * }
205
+ * }
206
+ * ```
207
+ *
208
+ * @example
209
+ * Decoding a fetched account with a Codama decoder:
210
+ * ```typescript
211
+ * import { getRpcAccountInfoProvider } from "@umbra-privacy/sdk";
212
+ * import { decodeEncryptedUserAccount } from "@umbra-privacy/umbra-codama";
213
+ *
214
+ * const fetchAccounts = getRpcAccountInfoProvider({
215
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
216
+ * });
217
+ *
218
+ * const accountMap = await fetchAccounts([userAccountPda]);
219
+ * const maybeAccount = accountMap.get(userAccountPda);
220
+ *
221
+ * if (maybeAccount?.exists) {
222
+ * const decoded = decodeEncryptedUserAccount(maybeAccount);
223
+ * console.log("User commitment registered:", decoded.data.isUserCommitmentRegistered);
224
+ * } else {
225
+ * console.log("User account does not exist — registration required");
226
+ * }
227
+ * ```
228
+ *
229
+ * @example
230
+ * Handling duplicate addresses (deduplicated automatically):
231
+ * ```typescript
232
+ * const myAddress = address("MyAccount1111111111111111111111111111111111");
233
+ * const accountMap = await fetchAccounts([myAddress, myAddress, myAddress]);
234
+ * // Only one RPC slot used; the Map has one entry.
235
+ * console.log(accountMap.size); // 1
236
+ * ```
237
+ *
238
+ * @example
239
+ * Using as a mock in tests (no RPC dependency):
240
+ * ```typescript
241
+ * import type { AccountInfoProviderFunction } from "@umbra-privacy/sdk/solana";
242
+ *
243
+ * const mockFetchAccounts: AccountInfoProviderFunction = async (addresses) => {
244
+ * const map = new Map();
245
+ * for (const addr of addresses) {
246
+ * map.set(addr, { exists: false }); // All accounts "missing"
247
+ * }
248
+ * return map;
249
+ * };
250
+ *
251
+ * const client = await getUmbraClient(
252
+ * { signer, network: "devnet", rpcUrl: "http://localhost:8899" },
253
+ * { accountInfoProvider: mockFetchAccounts },
254
+ * );
255
+ * ```
256
+ *
257
+ * @see {@link AccountInfoProviderFunction} for the returned function type
258
+ * @see {@link RpcBasedAccountInfoProviderConfig} for the configuration interface
259
+ * @since 2.0.0
260
+ * @public
261
+ */
262
+ declare function getRpcAccountInfoProvider(config: RpcBasedAccountInfoProviderConfig): AccountInfoProviderFunction;
263
+
264
+ /**
265
+ * RPC-Based Blockhash Provider Implementation.
266
+ *
267
+ * This module provides a factory function for creating a blockhash provider
268
+ * that fetches the latest blockhash from a Solana RPC endpoint. Blockhashes are
269
+ * a core component of Solana transaction lifetime management: every transaction
270
+ * must include a recent blockhash that bounds the window during which it is valid.
271
+ *
272
+ * @remarks
273
+ * The provider created by {@link getRpcBlockhashProvider} is stateless and
274
+ * performs no caching — every invocation of the returned function issues a fresh
275
+ * `getLatestBlockhash` RPC call using the `"confirmed"` commitment level (the
276
+ * default used by `@solana/kit`). Callers that need caching should wrap the
277
+ * returned function in their own caching layer.
278
+ *
279
+ * The returned {@link GetLatestBlockhash} function is designed to be injected into
280
+ * transaction-building pipelines, enabling straightforward mocking in tests by
281
+ * substituting a static implementation.
282
+ *
283
+ * @see {@link GetLatestBlockhash} for the function type returned by the factory
284
+ * @see {@link LatestBlockhashResult} for the shape of the fetched data
285
+ *
286
+ * @packageDocumentation
287
+ * @since 2.0.0
288
+ * @module implementation/solana/blockhash-provider
289
+ */
290
+
291
+ /**
292
+ * Configuration for the RPC-based blockhash provider.
293
+ *
294
+ * @public
295
+ */
296
+ interface RpcBasedBlockhashProviderConfig {
297
+ /**
298
+ * The URL of the Solana RPC endpoint.
299
+ *
300
+ * @remarks
301
+ * Should be an HTTP or HTTPS URL. WebSocket endpoints are used separately
302
+ * by the transaction forwarder, not by this provider.
303
+ *
304
+ * @example `"https://api.mainnet-beta.solana.com"`
305
+ * @example `"https://my-rpc.example.com"`
306
+ */
307
+ readonly rpcUrl: string;
308
+ }
309
+ /**
310
+ * Optional dependencies for the RPC-based blockhash provider.
311
+ *
312
+ * @remarks
313
+ * Providing custom dependencies is primarily useful for testing, where a mock
314
+ * RPC factory can be injected to avoid real network calls. In production, omit
315
+ * this parameter to use the default `createSolanaRpc` from `@solana/kit`.
316
+ *
317
+ * @public
318
+ */
319
+ interface RpcBasedBlockhashProviderDeps {
320
+ /**
321
+ * Optional custom Solana RPC client factory.
322
+ *
323
+ * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.
324
+ * Override this in tests to inject a mock RPC client.
325
+ *
326
+ * @defaultValue `createSolanaRpc` from `@solana/kit`
327
+ */
328
+ readonly createRpc?: CreateSolanaRpcFunction;
329
+ }
330
+ /**
331
+ * Creates a blockhash provider that fetches the latest blockhash from a Solana
332
+ * RPC endpoint.
333
+ *
334
+ * The factory constructs a single RPC client from the given URL and closes over
335
+ * it. Every call to the returned {@link GetLatestBlockhash} function invokes
336
+ * `rpc.getLatestBlockhash().send()` and extracts `blockhash` and
337
+ * `lastValidBlockHeight` from the response's `value` field.
338
+ *
339
+ * @remarks
340
+ * **Commitment** — Each call accepts an optional `{ commitment }` parameter
341
+ * that defaults to `"confirmed"`. Pass `"finalized"` for maximum safety.
342
+ *
343
+ * **No caching** — each invocation of the returned function makes an RPC call.
344
+ * If the same blockhash will be used across many operations in a short window,
345
+ * callers should fetch it once and reuse the result rather than calling the
346
+ * provider repeatedly.
347
+ *
348
+ *
349
+ * **Error propagation** — RPC errors (network failures, endpoint unreachable,
350
+ * rate limiting) are not caught and will propagate as rejections from the
351
+ * returned promise. Callers are responsible for implementing retry logic if
352
+ * needed.
353
+ *
354
+ * @param config - Configuration containing the RPC endpoint URL.
355
+ * @param deps - Optional dependencies. Pass a custom `createRpc` for testing.
356
+ * @returns A {@link GetLatestBlockhash} function. Each call to this function
357
+ * issues one `getLatestBlockhash` RPC request and resolves to a
358
+ * {@link LatestBlockhashResult}.
359
+ *
360
+ * @throws The returned function may throw (or reject) if the RPC endpoint is
361
+ * unreachable, returns an unexpected response structure, or rate-limits the
362
+ * caller.
363
+ *
364
+ * @example
365
+ * Basic usage:
366
+ * ```typescript
367
+ * import { getRpcBlockhashProvider } from "./blockhash-provider";
368
+ *
369
+ * const getLatestBlockhash = getRpcBlockhashProvider({
370
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
371
+ * });
372
+ *
373
+ * const { blockhash, lastValidBlockHeight } = await getLatestBlockhash();
374
+ * console.log(`Current blockhash: ${blockhash}`);
375
+ * console.log(`Valid until block height: ${lastValidBlockHeight}`);
376
+ * ```
377
+ *
378
+ * @example
379
+ * Using in a transaction-building pipeline:
380
+ * ```typescript
381
+ * import { getRpcBlockhashProvider } from "./blockhash-provider";
382
+ * import {
383
+ * createTransactionMessage,
384
+ * setTransactionMessageLifetimeUsingBlockhash,
385
+ * pipe,
386
+ * } from "@solana/kit";
387
+ *
388
+ * const getLatestBlockhash = getRpcBlockhashProvider({
389
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
390
+ * });
391
+ *
392
+ * async function buildTx() {
393
+ * const blockhashResult = await getLatestBlockhash();
394
+ * return pipe(
395
+ * createTransactionMessage({ version: 0 }),
396
+ * (m) => setTransactionMessageLifetimeUsingBlockhash(blockhashResult, m),
397
+ * );
398
+ * }
399
+ * ```
400
+ *
401
+ * @example
402
+ * Injecting a mock RPC factory in tests:
403
+ * ```typescript
404
+ * import { getRpcBlockhashProvider } from "./blockhash-provider";
405
+ *
406
+ * const mockCreateRpc = (_url: string) => ({
407
+ * getLatestBlockhash: () => ({
408
+ * send: async () => ({
409
+ * value: {
410
+ * blockhash: "FakeBlockhash1111111111111111111111111111111",
411
+ * lastValidBlockHeight: 9999n,
412
+ * },
413
+ * }),
414
+ * }),
415
+ * });
416
+ *
417
+ * const getLatestBlockhash = getRpcBlockhashProvider(
418
+ * { rpcUrl: "http://localhost:8899" },
419
+ * { createRpc: mockCreateRpc as any },
420
+ * );
421
+ *
422
+ * const result = await getLatestBlockhash();
423
+ * // result.blockhash === "FakeBlockhash1111111111111111111111111111111"
424
+ * ```
425
+ *
426
+ * @see {@link GetLatestBlockhash} for the returned function type
427
+ * @see {@link LatestBlockhashResult} for the return value shape
428
+ * @public
429
+ */
430
+ declare function getRpcBlockhashProvider(config: RpcBasedBlockhashProviderConfig, deps?: RpcBasedBlockhashProviderDeps): GetLatestBlockhash;
431
+
432
+ /**
433
+ * RPC-Based Epoch Info Provider Implementation.
434
+ *
435
+ * This module provides a factory function for creating an epoch info provider
436
+ * that fetches the current epoch information from a Solana RPC endpoint. Epoch
437
+ * data is used throughout the Umbra SDK primarily to resolve Token-2022 transfer
438
+ * fee schedules, which can change between epochs.
439
+ *
440
+ * @remarks
441
+ * On Solana mainnet, one epoch spans approximately 432,000 slots (~2–3 days).
442
+ * The Token-2022 program uses the epoch number to determine which of two
443
+ * consecutive fee schedules (older vs. newer) is active for a given mint.
444
+ * Fetching the epoch before constructing a deposit or transfer ensures the SDK
445
+ * uses the correct fee when computing the amount that will actually land in the
446
+ * destination account.
447
+ *
448
+ * The provider created by {@link getRpcEpochInfoProvider} is stateless and
449
+ * performs no caching — every invocation of the returned function issues a fresh
450
+ * `getEpochInfo` RPC call. Because epochs change slowly (every ~2–3 days on
451
+ * mainnet), short-term caching at the call site is safe and can reduce RPC load.
452
+ *
453
+ * @see {@link GetEpochInfo} for the function type returned by the factory
454
+ * @see {@link EpochInfoResult} for the shape of the fetched data
455
+ *
456
+ * @packageDocumentation
457
+ * @since 2.0.0
458
+ * @module implementation/solana/epoch-info-provider
459
+ */
460
+
461
+ /**
462
+ * Configuration for the RPC-based epoch info provider.
463
+ *
464
+ * @public
465
+ */
466
+ interface RpcBasedEpochInfoProviderConfig {
467
+ /**
468
+ * The URL of the Solana RPC endpoint.
469
+ *
470
+ * @remarks
471
+ * Should be an HTTP or HTTPS URL pointing to a Solana JSON-RPC server.
472
+ *
473
+ * @example `"https://api.mainnet-beta.solana.com"`
474
+ * @example `"https://api.devnet.solana.com"`
475
+ */
476
+ readonly rpcUrl: string;
477
+ }
478
+ /**
479
+ * Optional dependencies for the RPC-based epoch info provider.
480
+ *
481
+ * @remarks
482
+ * Providing custom dependencies is primarily useful for testing, where a mock
483
+ * RPC factory can be injected to return controlled epoch data without real
484
+ * network calls. In production, omit this parameter to use the default
485
+ * `createSolanaRpc` from `@solana/kit`.
486
+ *
487
+ * @public
488
+ */
489
+ interface RpcBasedEpochInfoProviderDeps {
490
+ /**
491
+ * Optional custom Solana RPC client factory.
492
+ *
493
+ * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.
494
+ * Override in tests to inject a mock RPC client with predetermined responses.
495
+ *
496
+ * @defaultValue `createSolanaRpc` from `@solana/kit`
497
+ */
498
+ readonly createRpc?: CreateSolanaRpcFunction;
499
+ }
500
+ /**
501
+ * Creates an epoch info provider that fetches the current epoch information
502
+ * from a Solana RPC endpoint.
503
+ *
504
+ * The factory constructs a single RPC client from the given URL and closes over
505
+ * it. Every call to the returned {@link GetEpochInfo} function invokes
506
+ * `rpc.getEpochInfo().send()` and maps the response fields into an
507
+ * {@link EpochInfoResult}. The `transactionCount` field is conditionally
508
+ * included only when the RPC node returns a non-null value.
509
+ *
510
+ * @remarks
511
+ * **Commitment** — Each call accepts an optional `{ commitment }` parameter
512
+ * that defaults to `"confirmed"`. Pass `"finalized"` for maximum safety.
513
+ *
514
+ * **No caching** — each invocation of the returned function issues one RPC
515
+ * call. Epochs change approximately every 2–3 days on mainnet, so callers in
516
+ * hot paths may safely cache the result for seconds or even minutes without
517
+ * risk of using a stale epoch.
518
+ *
519
+ *
520
+ * **Token-2022 transfer fees** — The primary consumer of this provider in the
521
+ * SDK is the transfer-fee calculation logic. Token-2022 mints may define two
522
+ * consecutive fee schedules. The active schedule is the one whose configured
523
+ * epoch is less than or equal to `EpochInfoResult.epoch`. Always fetch epoch
524
+ * info fresh when computing deposit amounts to avoid off-by-one errors at epoch
525
+ * boundaries.
526
+ *
527
+ * **Error propagation** — RPC errors are not caught and will propagate as
528
+ * rejections from the returned promise. Callers are responsible for implementing
529
+ * retry logic if needed.
530
+ *
531
+ * @param config - Configuration containing the RPC endpoint URL.
532
+ * @param deps - Optional dependencies. Pass a custom `createRpc` for testing.
533
+ * @returns A {@link GetEpochInfo} function. Each call to this function issues
534
+ * one `getEpochInfo` RPC request and resolves to an {@link EpochInfoResult}.
535
+ *
536
+ * @throws The returned function may throw (or reject) if the RPC endpoint is
537
+ * unreachable, returns an unexpected response, or rate-limits the caller.
538
+ *
539
+ * @example
540
+ * Basic usage:
541
+ * ```typescript
542
+ * import { getRpcEpochInfoProvider } from "./epoch-info-provider";
543
+ *
544
+ * const getEpochInfo = getRpcEpochInfoProvider({
545
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
546
+ * });
547
+ *
548
+ * const epochInfo = await getEpochInfo();
549
+ * console.log(`Current epoch: ${epochInfo.epoch}`);
550
+ * console.log(`Slot ${epochInfo.slotIndex} of ${epochInfo.slotsInEpoch}`);
551
+ * ```
552
+ *
553
+ * @example
554
+ * Using with Token-2022 transfer fee calculation:
555
+ * ```typescript
556
+ * import { getRpcEpochInfoProvider } from "./epoch-info-provider";
557
+ * import { calculateTransferFee } from "@umbra-privacy/sdk";
558
+ *
559
+ * const getEpochInfo = getRpcEpochInfoProvider({
560
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
561
+ * });
562
+ *
563
+ * const epochInfo = await getEpochInfo();
564
+ * const fee = calculateTransferFee(feeConfig, epochInfo.epoch, depositAmount);
565
+ * // Use (depositAmount + fee) as the transfer amount so the recipient
566
+ * // lands the exact depositAmount after the protocol deducts the fee.
567
+ * ```
568
+ *
569
+ * @example
570
+ * Injecting a mock RPC factory in tests:
571
+ * ```typescript
572
+ * import { getRpcEpochInfoProvider } from "./epoch-info-provider";
573
+ *
574
+ * const mockCreateRpc = (_url: string) => ({
575
+ * getEpochInfo: () => ({
576
+ * send: async () => ({
577
+ * epoch: 500n,
578
+ * slotIndex: 100n,
579
+ * slotsInEpoch: 432000n,
580
+ * absoluteSlot: 216100000n,
581
+ * blockHeight: 215800000n,
582
+ * transactionCount: null,
583
+ * }),
584
+ * }),
585
+ * });
586
+ *
587
+ * const getEpochInfo = getRpcEpochInfoProvider(
588
+ * { rpcUrl: "http://localhost:8899" },
589
+ * { createRpc: mockCreateRpc as any },
590
+ * );
591
+ *
592
+ * const info = await getEpochInfo();
593
+ * // info.epoch === 500n, info.transactionCount === undefined
594
+ * ```
595
+ *
596
+ * @see {@link GetEpochInfo} for the returned function type
597
+ * @see {@link EpochInfoResult} for the return value shape
598
+ * @public
599
+ */
600
+ declare function getRpcEpochInfoProvider(config: RpcBasedEpochInfoProviderConfig, deps?: RpcBasedEpochInfoProviderDeps): GetEpochInfo;
601
+
602
+ /**
603
+ * Signer Helper Functions
604
+ *
605
+ * Factory functions and adapters for creating `IUmbraSigner` instances from
606
+ * common Solana wallet primitives. Covers in-memory keypairs (for testing and
607
+ * scripting), `@solana/kit` `KeyPairSigner` instances, and browser wallets
608
+ * that implement the Wallet Standard (`@wallet-standard/core`).
609
+ *
610
+ * @since 2.0.0
611
+ * @module solana/signers
612
+ * @packageDocumentation
613
+ */
614
+
615
+ /**
616
+ * Creates a new random in-memory keypair signer.
617
+ *
618
+ * Generates a fresh Ed25519 keypair using the platform's CSPRNG. The private key
619
+ * is non-extractable by default (Web Crypto API constraint). Use this for tests,
620
+ * CI pipelines, and scripts where the wallet lifecycle is managed by your code.
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * import { createInMemorySigner, getUmbraClient } from "@umbra-privacy/sdk";
625
+ *
626
+ * const signer = await createInMemorySigner();
627
+ * const client = await getUmbraClient({ signer, network: "devnet", rpcUrl, rpcSubscriptionsUrl });
628
+ * ```
629
+ *
630
+ * @remarks
631
+ * The keypair exists only in memory. It is lost when the process exits or the page
632
+ * refreshes. Only use this for testing or scripts where you control the key lifecycle.
633
+ */
634
+ declare function createInMemorySigner(): Promise<IUmbraSigner>;
635
+ /**
636
+ * Creates a signer from raw private key bytes.
637
+ *
638
+ * Accepts either 64-byte combined key material (private key + public key) or a
639
+ * 32-byte seed — `@solana/kit` handles both formats via `createKeyPairSignerFromBytes`.
640
+ *
641
+ * @param bytes - Raw private key bytes (64-byte keypair or 32-byte seed).
642
+ *
643
+ * @example
644
+ * ```typescript
645
+ * import { createSignerFromPrivateKeyBytes, getUmbraClient } from "@umbra-privacy/sdk";
646
+ * import { readFileSync } from "fs";
647
+ *
648
+ * // Load from a Solana CLI JSON key file (array of numbers)
649
+ * const keyFile = JSON.parse(readFileSync("/path/to/keypair.json", "utf8"));
650
+ * const signer = await createSignerFromPrivateKeyBytes(new Uint8Array(keyFile));
651
+ * ```
652
+ */
653
+ declare function createSignerFromPrivateKeyBytes(bytes: Uint8Array): Promise<IUmbraSigner>;
654
+ /**
655
+ * Adapts an existing `@solana/kit` `KeyPairSigner` to `IUmbraSigner`.
656
+ *
657
+ * Use this when you already have a `KeyPairSigner` (e.g., from `generateKeyPairSigner`
658
+ * or `createKeyPairSignerFromBytes`) and want to pass it to `getUmbraClient`.
659
+ *
660
+ * @param kps - A `KeyPairSigner` from `@solana/kit`.
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * import { generateKeyPairSigner } from "@solana/kit";
665
+ * import { createSignerFromKeyPair, getUmbraClient } from "@umbra-privacy/sdk";
666
+ *
667
+ * const kps = await generateKeyPairSigner();
668
+ * const signer = createSignerFromKeyPair(kps);
669
+ * const client = await getUmbraClient({ signer, network: "devnet", rpcUrl, rpcSubscriptionsUrl });
670
+ * ```
671
+ */
672
+ declare function createSignerFromKeyPair(kps: KeyPairSigner): IUmbraSigner;
673
+ /**
674
+ * Creates an `IUmbraSigner` from a Wallet Standard `WalletAccount`.
675
+ *
676
+ * This is the primary adapter for browser wallets (Phantom, Backpack, Solflare, etc.)
677
+ * that implement the Wallet Standard. Obtain `wallet` and `account` via
678
+ * `@wallet-standard/react`'s `useWallets()` hook or `@solana/react`.
679
+ *
680
+ * Both `"solana:signTransaction"` and `"solana:signMessage"` must be present in
681
+ * `wallet.features` — an error is thrown immediately if either is missing.
682
+ *
683
+ * @param wallet - The Wallet Standard `Wallet` object (provides feature implementations).
684
+ * @param account - The specific `WalletAccount` to sign with.
685
+ *
686
+ * @example
687
+ * ```typescript
688
+ * import { useWallet } from "@solana/react"; // or @wallet-standard/react
689
+ * import { createSignerFromWalletAccount, getUmbraClient } from "@umbra-privacy/sdk";
690
+ *
691
+ * function useUmbraClient(rpcUrl: string, rpcSubscriptionsUrl: string) {
692
+ * const { wallet, account } = useWallet();
693
+ * const signer = createSignerFromWalletAccount(wallet, account);
694
+ * return await getUmbraClient({ signer, network: "mainnet-beta", rpcUrl, rpcSubscriptionsUrl });
695
+ * }
696
+ * ```
697
+ */
698
+ declare function createSignerFromWalletAccount(wallet: Wallet, account: WalletAccount): IUmbraSigner;
699
+
700
+ /**
701
+ * RPC-Based Transaction Forwarder Implementations.
702
+ *
703
+ * This module provides two factory functions that produce {@link TransactionForwarder}
704
+ * implementations with different confirmation strategies:
705
+ *
706
+ * - **{@link getWebsocketTransactionForwarder}** — Uses
707
+ * `sendAndConfirmTransaction` from `@solana/kit`, which subscribes to
708
+ * transaction signature notifications via WebSocket for low-latency
709
+ * confirmation. Requires access to a WebSocket RPC endpoint.
710
+ *
711
+ * - **{@link getPollingTransactionForwarder}** — Sends transactions via
712
+ * HTTP and polls `getSignatureStatuses` on a configurable interval to detect
713
+ * confirmation. Suitable for environments that block WebSocket connections
714
+ * (e.g., certain CI systems, restricted networks). Supports configurable
715
+ * timeouts, retry counts, and a per-transaction confirmation callback.
716
+ *
717
+ * Both factories implement the {@link TransactionForwarder} interface, exposing
718
+ * `forwardSequentially` (ordered, one-at-a-time) and `forwardInParallel`
719
+ * (concurrent submission) methods.
720
+ *
721
+ * @remarks
722
+ * **Choosing a strategy:**
723
+ * - Use the WebSocket forwarder in browser and server environments where
724
+ * WebSocket access is available — it produces lower confirmation latency by
725
+ * receiving push notifications instead of polling.
726
+ * - Use the polling forwarder in environments without WebSocket support or when
727
+ * you need fine-grained control over timeout and retry behavior.
728
+ *
729
+ * **Error handling:**
730
+ * - Both forwarders propagate RPC errors as rejections. Callers are responsible
731
+ * for wrapping the returned promises in their own retry/error-boundary logic.
732
+ * - The polling forwarder includes internal send-level retries with exponential
733
+ * backoff; the WebSocket forwarder relies on `@solana/kit`'s built-in
734
+ * confirmation timeout.
735
+ * - Confirmation timeout in the polling forwarder throws a plain `Error` with
736
+ * a human-readable timeout message.
737
+ *
738
+ * **Wire format:**
739
+ * - Both forwarders serialize transactions using `getBase64EncodedWireTransaction`
740
+ * from `@solana/kit` and submit them via `sendTransaction` with
741
+ * `encoding: "base64"`.
742
+ *
743
+ * @packageDocumentation
744
+ * @since 2.0.0
745
+ * @module implementation/solana/transaction-forwarder
746
+ */
747
+
748
+ /**
749
+ * Configuration for the websocket-based transaction forwarder.
750
+ *
751
+ * @public
752
+ */
753
+ interface WebsocketBasedTransactionForwarderConfig {
754
+ /**
755
+ * The HTTP or HTTPS URL of the Solana JSON-RPC endpoint.
756
+ *
757
+ * @remarks
758
+ * Used for `sendTransaction` calls and the `sendAndConfirmTransaction`
759
+ * factory. Must be an HTTP/HTTPS URL, not a WebSocket URL.
760
+ *
761
+ * @example `"https://api.mainnet-beta.solana.com"`
762
+ */
763
+ readonly rpcUrl: string;
764
+ /**
765
+ * The WebSocket URL of the Solana RPC subscriptions endpoint.
766
+ *
767
+ * @remarks
768
+ * Used to subscribe to signature notifications for real-time confirmation
769
+ * detection. Must be a WebSocket URL (`ws://` or `wss://`).
770
+ *
771
+ * @example `"wss://api.mainnet-beta.solana.com"`
772
+ */
773
+ readonly rpcSubscriptionsUrl: string;
774
+ }
775
+ /**
776
+ * Optional dependencies for the websocket-based transaction forwarder.
777
+ *
778
+ * @remarks
779
+ * All three dependency slots default to the corresponding `@solana/kit`
780
+ * implementations. Override any or all of them in tests to avoid real network
781
+ * I/O.
782
+ *
783
+ * @public
784
+ */
785
+ interface WebsocketBasedTransactionForwarderDeps {
786
+ /**
787
+ * Optional custom Solana RPC client factory.
788
+ *
789
+ * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.
790
+ *
791
+ * @defaultValue `createSolanaRpc` from `@solana/kit`
792
+ */
793
+ readonly createRpc?: CreateSolanaRpcFunction;
794
+ /**
795
+ * Optional custom Solana RPC subscriptions client factory.
796
+ *
797
+ * If not provided, uses the default `createSolanaRpcSubscriptions` from
798
+ * `@solana/kit`.
799
+ *
800
+ * @defaultValue `createSolanaRpcSubscriptions` from `@solana/kit`
801
+ */
802
+ readonly createRpcSubscriptions?: CreateSolanaRpcSubscriptionsFunction;
803
+ /**
804
+ * Optional custom `sendAndConfirmTransaction` factory.
805
+ *
806
+ * If not provided, uses the default `sendAndConfirmTransactionFactory` from
807
+ * `@solana/kit`.
808
+ *
809
+ * @defaultValue `sendAndConfirmTransactionFactory` from `@solana/kit`
810
+ */
811
+ readonly sendAndConfirmTransactionFactory?: SendAndConfirmTransactionFactoryFunction;
812
+ }
813
+ /**
814
+ * Common options shared by both forwarder strategies for a forwarding operation.
815
+ *
816
+ * @public
817
+ */
818
+ interface TransactionForwardOptions {
819
+ /**
820
+ * The commitment level to use for transaction confirmation.
821
+ *
822
+ * @remarks
823
+ * - `"processed"` — confirmed by the local RPC node (weakest, fastest)
824
+ * - `"confirmed"` — confirmed by a supermajority of the cluster (recommended)
825
+ * - `"finalized"` — confirmed by a full supermajority and rooted (slowest, strongest)
826
+ *
827
+ * @defaultValue `"confirmed"`
828
+ */
829
+ readonly commitment?: Commitment;
830
+ }
831
+ /**
832
+ * Configuration for the polling-based transaction forwarder.
833
+ *
834
+ * @public
835
+ */
836
+ interface PollingBasedTransactionForwarderConfig {
837
+ /**
838
+ * The HTTP or HTTPS URL of the Solana JSON-RPC endpoint.
839
+ *
840
+ * @example `"https://api.mainnet-beta.solana.com"`
841
+ */
842
+ readonly rpcUrl: string;
843
+ }
844
+ /**
845
+ * Optional dependencies for the polling-based transaction forwarder.
846
+ *
847
+ * @remarks
848
+ * Override `createRpc` in tests to inject a mock RPC client with controlled
849
+ * responses.
850
+ *
851
+ * @public
852
+ */
853
+ interface PollingBasedTransactionForwarderDeps {
854
+ /**
855
+ * Optional custom Solana RPC client factory.
856
+ *
857
+ * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.
858
+ *
859
+ * @defaultValue `createSolanaRpc` from `@solana/kit`
860
+ */
861
+ readonly createRpc?: CreateSolanaRpcFunction;
862
+ }
863
+ /**
864
+ * Options specific to the polling-based transaction forwarder, extending the
865
+ * base {@link TransactionForwardOptions}.
866
+ *
867
+ * @public
868
+ */
869
+ interface PollingTransactionForwardOptions extends TransactionForwardOptions {
870
+ /**
871
+ * Maximum time in milliseconds to wait for a transaction to reach the
872
+ * desired commitment level before throwing a timeout error.
873
+ *
874
+ * @remarks
875
+ * The polling loop checks elapsed time against this value. If `timeoutMs` is
876
+ * exceeded, the forwarder throws `Error("Transaction confirmation timed out
877
+ * after Xms")`. The transaction may still land on-chain after the timeout —
878
+ * callers should query `getSignatureStatus` if they need to determine the
879
+ * final outcome.
880
+ *
881
+ * @defaultValue `60000` (60 seconds)
882
+ */
883
+ readonly timeoutMs?: number;
884
+ /**
885
+ * Time in milliseconds between successive `getSignatureStatuses` polling
886
+ * attempts.
887
+ *
888
+ * @remarks
889
+ * Lower values reduce confirmation latency at the cost of more RPC calls.
890
+ * On mainnet, slots are ~400ms and blocks ~500ms, so polling more frequently
891
+ * than every 500ms typically yields no benefit.
892
+ *
893
+ * @defaultValue `1000` (1 second)
894
+ */
895
+ readonly pollingIntervalMs?: number;
896
+ /**
897
+ * Maximum number of `sendTransaction` attempts before giving up.
898
+ *
899
+ * @remarks
900
+ * Each retry is preceded by an exponential backoff delay:
901
+ * - Attempt 1 → no delay before send
902
+ * - Retry 1 → 1 second delay
903
+ * - Retry 2 → 2 seconds delay
904
+ * - ...
905
+ *
906
+ * Only `sendTransaction` failures trigger retries. Confirmation timeouts
907
+ * are not retried — they throw immediately after `timeoutMs`.
908
+ *
909
+ * @defaultValue `3`
910
+ */
911
+ readonly maxRetries?: number;
912
+ /**
913
+ * Callback invoked after each transaction is confirmed in sequential mode.
914
+ *
915
+ * @remarks
916
+ * Only called by `forwardSequentially` — not by `forwardInParallel`.
917
+ * Useful for updating progress indicators in a UI while a multi-transaction
918
+ * sequence is in flight.
919
+ *
920
+ * The callback is `await`-ed before the next transaction is submitted, so
921
+ * async UI updates are safe.
922
+ */
923
+ readonly onTransactionConfirmed?: OnTransactionConfirmed;
924
+ }
925
+ /**
926
+ * Metadata passed to the {@link OnTransactionConfirmed} callback after each
927
+ * transaction is confirmed in a sequential forwarding sequence.
928
+ *
929
+ * @public
930
+ */
931
+ interface TransactionConfirmedInfo {
932
+ /**
933
+ * The base58-encoded signature of the confirmed transaction.
934
+ */
935
+ readonly signature: TransactionSignature;
936
+ /**
937
+ * The zero-based index of this transaction within the input array.
938
+ *
939
+ * @remarks
940
+ * Use `index + 1` and `totalCount` to display `"Transaction X of Y confirmed"`.
941
+ */
942
+ readonly index: number;
943
+ /**
944
+ * The total number of transactions being forwarded in this sequence.
945
+ */
946
+ readonly totalCount: number;
947
+ }
948
+ /**
949
+ * Async callback function type invoked after each transaction is confirmed
950
+ * during sequential forwarding.
951
+ *
952
+ * @remarks
953
+ * The callback is `await`-ed before the next transaction in the sequence is
954
+ * submitted. This guarantees that UI updates or other async side effects
955
+ * complete before the next round-trip begins.
956
+ *
957
+ * @param info - Metadata about the confirmed transaction.
958
+ * @returns A `Promise<void>`. The returned promise is awaited before proceeding.
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * const onTransactionConfirmed: OnTransactionConfirmed = async ({ signature, index, totalCount }) => {
963
+ * setProgress(Math.round(((index + 1) / totalCount) * 100));
964
+ * console.log(`[${index + 1}/${totalCount}] confirmed: ${signature}`);
965
+ * };
966
+ * ```
967
+ *
968
+ * @see {@link PollingTransactionForwardOptions.onTransactionConfirmed}
969
+ * @public
970
+ */
971
+ type OnTransactionConfirmed = (info: TransactionConfirmedInfo) => Promise<void>;
972
+ /**
973
+ * Creates a {@link TransactionForwarder} that uses WebSocket subscriptions for
974
+ * transaction confirmation.
975
+ *
976
+ * The forwarder establishes an RPC client and a subscriptions client from the
977
+ * provided URLs, then uses `@solana/kit`'s `sendAndConfirmTransactionFactory`
978
+ * to produce a `sendAndConfirmTransaction` function. This function:
979
+ *
980
+ * 1. Serializes the transaction to base64 wire format using
981
+ * `getBase64EncodedWireTransaction`.
982
+ * 2. Submits it via `rpc.sendTransaction` to obtain the signature.
983
+ * 3. Waits for confirmation by subscribing to the signature via WebSocket
984
+ * through `sendAndConfirm`.
985
+ *
986
+ * @remarks
987
+ * **WebSocket requirement** — This forwarder requires a WebSocket-capable RPC
988
+ * endpoint. If the environment blocks WebSocket connections (e.g., some CI
989
+ * runners, corporate proxies), use {@link getPollingTransactionForwarder}
990
+ * instead.
991
+ *
992
+ * **Sequential behavior** — `forwardSequentially` submits and confirms
993
+ * transactions one at a time in order. If any transaction fails, the method
994
+ * rejects and subsequent transactions are not submitted.
995
+ *
996
+ * **Parallel behavior** — `forwardInParallel` maps all transactions through
997
+ * `sendAndConfirmTransaction` and races them with `Promise.all`. Individual
998
+ * failures propagate immediately through the `Promise.all` rejection.
999
+ *
1000
+ * **Commitment** — Defaults to `"confirmed"`. Pass `{ commitment: "finalized" }`
1001
+ * in options for stronger guarantees at the cost of additional latency.
1002
+ *
1003
+ * **Error propagation** — RPC errors and confirmation failures are not caught
1004
+ * internally; they propagate as rejected promises.
1005
+ *
1006
+ * @param config - Configuration containing the HTTP RPC URL and the WebSocket
1007
+ * subscriptions URL.
1008
+ * @param deps - Optional dependency overrides for testing. All three slots
1009
+ * default to `@solana/kit` implementations.
1010
+ * @returns A {@link TransactionForwarder} with `forwardSequentially` and
1011
+ * `forwardInParallel` methods.
1012
+ *
1013
+ * @throws The returned methods may reject if the RPC endpoint is unreachable,
1014
+ * the WebSocket subscription cannot be established, or the transaction is
1015
+ * rejected on-chain.
1016
+ *
1017
+ * @example
1018
+ * Basic sequential forwarding:
1019
+ * ```typescript
1020
+ * import { getWebsocketTransactionForwarder } from "./transaction-forwarder";
1021
+ *
1022
+ * const forwarder = getWebsocketTransactionForwarder({
1023
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
1024
+ * rpcSubscriptionsUrl: "wss://api.mainnet-beta.solana.com",
1025
+ * });
1026
+ *
1027
+ * const [sig1, sig2] = await forwarder.forwardSequentially([initTx, depositTx], {
1028
+ * commitment: "confirmed",
1029
+ * });
1030
+ * console.log("Init:", sig1);
1031
+ * console.log("Deposit:", sig2);
1032
+ * ```
1033
+ *
1034
+ * @example
1035
+ * Parallel forwarding of independent transactions:
1036
+ * ```typescript
1037
+ * const signatures = await forwarder.forwardInParallel(transferTxs, {
1038
+ * commitment: "confirmed",
1039
+ * });
1040
+ * ```
1041
+ *
1042
+ * @example
1043
+ * Injecting mock dependencies in tests:
1044
+ * ```typescript
1045
+ * const forwarder = getWebsocketTransactionForwarder(
1046
+ * { rpcUrl: "http://localhost:8899", rpcSubscriptionsUrl: "ws://localhost:8900" },
1047
+ * {
1048
+ * createRpc: mockCreateRpc,
1049
+ * createRpcSubscriptions: mockCreateRpcSubscriptions,
1050
+ * sendAndConfirmTransactionFactory: mockSendAndConfirmFactory,
1051
+ * },
1052
+ * );
1053
+ * ```
1054
+ *
1055
+ * @see {@link getPollingTransactionForwarder} for the polling alternative
1056
+ * @see {@link TransactionForwarder} for the returned interface
1057
+ * @public
1058
+ */
1059
+ declare function getWebsocketTransactionForwarder(config: WebsocketBasedTransactionForwarderConfig, deps?: WebsocketBasedTransactionForwarderDeps): TransactionForwarder;
1060
+ /**
1061
+ * Creates a {@link TransactionForwarder} that uses periodic polling of
1062
+ * `getSignatureStatuses` for transaction confirmation.
1063
+ *
1064
+ * The polling forwarder works as follows:
1065
+ * 1. Serialize the transaction and submit via `rpc.sendTransaction`.
1066
+ * 2. Begin polling `rpc.getSignatureStatuses` at `pollingIntervalMs` intervals.
1067
+ * 3. Compare `confirmationStatus` against the requested commitment level.
1068
+ * 4. Resolve when the status matches, or throw after `timeoutMs` elapses.
1069
+ *
1070
+ * Send failures are retried up to `maxRetries` times with exponential backoff.
1071
+ *
1072
+ * @remarks
1073
+ * **Environment compatibility** — This forwarder requires only HTTP/HTTPS
1074
+ * access and is suitable for environments that block WebSocket connections.
1075
+ *
1076
+ * **Commitment mapping** — The confirmation check maps `confirmationStatus`
1077
+ * from the RPC response as follows:
1078
+ * - `"processed"` commitment: any non-null `confirmationStatus` satisfies it.
1079
+ * - `"confirmed"` commitment: `confirmationStatus` must be `"confirmed"` or
1080
+ * `"finalized"`.
1081
+ * - `"finalized"` commitment: `confirmationStatus` must be `"finalized"`.
1082
+ *
1083
+ * **On-chain errors** — If `status.err` is non-null, the forwarder immediately
1084
+ * throws `Error("Transaction failed: <err JSON>")`. This distinguishes an
1085
+ * on-chain execution failure from a network/timeout failure.
1086
+ *
1087
+ * **Sequential callback** — The `onTransactionConfirmed` callback in
1088
+ * {@link PollingTransactionForwardOptions} is invoked (and awaited) after each
1089
+ * transaction in a `forwardSequentially` call. It is NOT invoked during
1090
+ * `forwardInParallel`.
1091
+ *
1092
+ * **Parallel behavior** — `forwardInParallel` first sends all transactions
1093
+ * (with retries), then waits for all confirmations concurrently via
1094
+ * `Promise.all`.
1095
+ *
1096
+ * **Retry backoff** — Send retries use linear backoff: attempt N waits
1097
+ * `N * 1000ms` before retrying (attempt 1: 1s, attempt 2: 2s, etc.).
1098
+ *
1099
+ * @param config - Configuration containing the HTTP RPC endpoint URL.
1100
+ * @param deps - Optional dependency overrides for testing.
1101
+ * @returns A {@link TransactionForwarder} with `forwardSequentially` and
1102
+ * `forwardInParallel` methods. Both methods accept
1103
+ * {@link PollingTransactionForwardOptions} for polling-specific settings.
1104
+ *
1105
+ * @throws The returned methods may reject if:
1106
+ * - The transaction cannot be sent after `maxRetries` attempts.
1107
+ * - The confirmation timeout (`timeoutMs`) is exceeded.
1108
+ * - The transaction is rejected on-chain (`status.err !== null`).
1109
+ *
1110
+ * @example
1111
+ * Basic usage with progress callback:
1112
+ * ```typescript
1113
+ * import { getPollingTransactionForwarder } from "./transaction-forwarder";
1114
+ *
1115
+ * const forwarder = getPollingTransactionForwarder({
1116
+ * rpcUrl: "https://api.mainnet-beta.solana.com",
1117
+ * });
1118
+ *
1119
+ * const signatures = await forwarder.forwardSequentially(transactions, {
1120
+ * commitment: "confirmed",
1121
+ * timeoutMs: 30_000,
1122
+ * pollingIntervalMs: 500,
1123
+ * maxRetries: 5,
1124
+ * onTransactionConfirmed: async ({ signature, index, totalCount }) => {
1125
+ * console.log(`[${index + 1}/${totalCount}] confirmed: ${signature}`);
1126
+ * },
1127
+ * });
1128
+ * ```
1129
+ *
1130
+ * @example
1131
+ * Parallel forwarding without a callback:
1132
+ * ```typescript
1133
+ * const signatures = await forwarder.forwardInParallel(transferTxs, {
1134
+ * commitment: "confirmed",
1135
+ * timeoutMs: 60_000,
1136
+ * });
1137
+ * ```
1138
+ *
1139
+ * @example
1140
+ * Injecting a mock RPC for tests:
1141
+ * ```typescript
1142
+ * const forwarder = getPollingTransactionForwarder(
1143
+ * { rpcUrl: "http://localhost:8899" },
1144
+ * { createRpc: mockCreateRpc },
1145
+ * );
1146
+ * ```
1147
+ *
1148
+ * @see {@link getWebsocketTransactionForwarder} for the WebSocket alternative
1149
+ * @see {@link TransactionForwarder} for the returned interface
1150
+ * @see {@link PollingTransactionForwardOptions} for the full options reference
1151
+ * @public
1152
+ */
1153
+ declare function getPollingTransactionForwarder(config: PollingBasedTransactionForwarderConfig, deps?: PollingBasedTransactionForwarderDeps): TransactionForwarder;
1154
+
1155
+ export { type OnTransactionConfirmed as O, type PollingBasedTransactionForwarderConfig as P, type RpcBasedAccountInfoProviderConfig as R, type TransactionConfirmedInfo as T, type WebsocketBasedTransactionForwarderConfig as W, type PollingBasedTransactionForwarderDeps as a, type PollingTransactionForwardOptions as b, type RpcBasedBlockhashProviderConfig as c, type RpcBasedBlockhashProviderDeps as d, type RpcBasedEpochInfoProviderConfig as e, type RpcBasedEpochInfoProviderDeps as f, type TransactionForwardOptions as g, type WebsocketBasedTransactionForwarderDeps as h, createInMemorySigner as i, createSignerFromKeyPair as j, createSignerFromPrivateKeyBytes as k, createSignerFromWalletAccount as l, getPollingTransactionForwarder as m, getRpcAccountInfoProvider as n, getRpcBlockhashProvider as o, getRpcEpochInfoProvider as p, getWebsocketTransactionForwarder as q };