@umbra-privacy/sdk 1.0.0 → 2.0.0
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.
- package/README.md +104 -25
- package/dist/{addresses-Brzgurv_.d.ts → addresses-B7HybtbJ.d.ts} +2 -1
- package/dist/{addresses-D_0YAS6B.d.cts → addresses-CTVY1oi7.d.cts} +2 -1
- package/dist/arcium-BXXlryfe.d.cts +20 -0
- package/dist/arcium-BXXlryfe.d.ts +20 -0
- package/dist/chunk-4RHXVBNI.js +203 -0
- package/dist/chunk-4RHXVBNI.js.map +1 -0
- package/dist/chunk-4TZVXB5G.js +324 -0
- package/dist/chunk-4TZVXB5G.js.map +1 -0
- package/dist/chunk-5GUSMQ74.cjs +549 -0
- package/dist/chunk-5GUSMQ74.cjs.map +1 -0
- package/dist/chunk-5KPQXPQM.js +36 -0
- package/dist/chunk-5KPQXPQM.js.map +1 -0
- package/dist/chunk-AXD7LXYY.cjs +405 -0
- package/dist/chunk-AXD7LXYY.cjs.map +1 -0
- package/dist/{chunk-HOEXDXRC.cjs → chunk-BL6WXLPV.cjs} +32 -360
- package/dist/chunk-BL6WXLPV.cjs.map +1 -0
- package/dist/chunk-CFFLOE7D.cjs +598 -0
- package/dist/chunk-CFFLOE7D.cjs.map +1 -0
- package/dist/{chunk-BM7N6N7E.js → chunk-CFTW5WNG.js} +3 -325
- package/dist/chunk-CFTW5WNG.js.map +1 -0
- package/dist/chunk-DD2WCK4C.js +327 -0
- package/dist/chunk-DD2WCK4C.js.map +1 -0
- package/dist/chunk-DMPMQ74B.cjs +246 -0
- package/dist/chunk-DMPMQ74B.cjs.map +1 -0
- package/dist/{chunk-2Q75CQQJ.js → chunk-EEKF4553.js} +2 -2
- package/dist/chunk-EEKF4553.js.map +1 -0
- package/dist/chunk-ENVYYEM4.cjs +113 -0
- package/dist/chunk-ENVYYEM4.cjs.map +1 -0
- package/dist/chunk-FQX6ZYGJ.js +500 -0
- package/dist/chunk-FQX6ZYGJ.js.map +1 -0
- package/dist/chunk-FSK2ICMB.cjs +39 -0
- package/dist/chunk-FSK2ICMB.cjs.map +1 -0
- package/dist/chunk-FZYWLQAF.cjs +355 -0
- package/dist/chunk-FZYWLQAF.cjs.map +1 -0
- package/dist/chunk-GP26R377.js +436 -0
- package/dist/chunk-GP26R377.js.map +1 -0
- package/dist/chunk-HA5FLM63.js +393 -0
- package/dist/chunk-HA5FLM63.js.map +1 -0
- package/dist/chunk-INJ73LXQ.js +1107 -0
- package/dist/chunk-INJ73LXQ.js.map +1 -0
- package/dist/chunk-JPDF7BIT.cjs +10892 -0
- package/dist/chunk-JPDF7BIT.cjs.map +1 -0
- package/dist/{chunk-MDFSBU5W.cjs → chunk-LTCKPTZC.cjs} +2 -351
- package/dist/chunk-LTCKPTZC.cjs.map +1 -0
- package/dist/chunk-MKNCBUFA.js +564 -0
- package/dist/chunk-MKNCBUFA.js.map +1 -0
- package/dist/chunk-NKVMSABR.cjs +207 -0
- package/dist/chunk-NKVMSABR.cjs.map +1 -0
- package/dist/chunk-OFDWNWCL.js +70 -0
- package/dist/chunk-OFDWNWCL.js.map +1 -0
- package/dist/chunk-QJAUUYZU.cjs +331 -0
- package/dist/chunk-QJAUUYZU.cjs.map +1 -0
- package/dist/chunk-RVUYPKKD.js +10750 -0
- package/dist/chunk-RVUYPKKD.js.map +1 -0
- package/dist/chunk-TLR7A64G.js +103 -0
- package/dist/chunk-TLR7A64G.js.map +1 -0
- package/dist/{chunk-MVKTV3FT.cjs → chunk-TQQZGNOI.cjs} +2 -2
- package/dist/chunk-TQQZGNOI.cjs.map +1 -0
- package/dist/chunk-UOFYS6M3.js +219 -0
- package/dist/chunk-UOFYS6M3.js.map +1 -0
- package/dist/chunk-UXMQI6B7.js +2406 -0
- package/dist/chunk-UXMQI6B7.js.map +1 -0
- package/dist/chunk-WN75ORDT.js +571 -0
- package/dist/chunk-WN75ORDT.js.map +1 -0
- package/dist/chunk-Y55PYKXH.cjs +595 -0
- package/dist/chunk-Y55PYKXH.cjs.map +1 -0
- package/dist/chunk-YEZBTYCP.cjs +77 -0
- package/dist/chunk-YEZBTYCP.cjs.map +1 -0
- package/dist/chunk-ZQOIYCGA.cjs +1126 -0
- package/dist/chunk-ZQOIYCGA.cjs.map +1 -0
- package/dist/chunk-ZY3TSHMJ.cjs +2665 -0
- package/dist/chunk-ZY3TSHMJ.cjs.map +1 -0
- package/dist/client-DkVBHMWb.d.cts +2613 -0
- package/dist/client-V4AF6Bz9.d.ts +2613 -0
- package/dist/common/pda/index.cjs +145 -0
- package/dist/common/pda/index.cjs.map +1 -0
- package/dist/common/pda/index.d.cts +1250 -0
- package/dist/common/pda/index.d.ts +1250 -0
- package/dist/common/pda/index.js +8 -0
- package/dist/common/pda/index.js.map +1 -0
- package/dist/constants/index.cjs +38 -164
- package/dist/constants/index.cjs.map +1 -1
- package/dist/constants/index.d.cts +8 -425
- package/dist/constants/index.d.ts +8 -425
- package/dist/constants/index.js +15 -124
- package/dist/constants/index.js.map +1 -1
- package/dist/crypto/index.cjs +583 -0
- package/dist/crypto/index.cjs.map +1 -0
- package/dist/crypto/index.d.cts +6731 -0
- package/dist/crypto/index.d.ts +6731 -0
- package/dist/crypto/index.js +14 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/{cryptography-BTGC72u-.d.ts → cryptography-BFSJcvi6.d.ts} +3 -2465
- package/dist/{cryptography-BTGC72u-.d.cts → cryptography-D6tPDh-Y.d.cts} +3 -2465
- package/dist/errors/index.cjs +64 -54
- package/dist/errors/index.d.cts +7 -797
- package/dist/errors/index.d.ts +7 -797
- package/dist/errors/index.js +3 -1
- package/dist/errors-B9EoPeWV.d.cts +593 -0
- package/dist/errors-B9EoPeWV.d.ts +593 -0
- package/dist/errors-DAIrstEL.d.cts +300 -0
- package/dist/errors-DPNMfyh0.d.ts +300 -0
- package/dist/index-BG0yjL7C.d.cts +6006 -0
- package/dist/index-ByynoyBO.d.ts +6006 -0
- package/dist/index.cjs +5126 -16118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1031 -7685
- package/dist/index.d.ts +1031 -7685
- package/dist/index.js +3219 -14905
- package/dist/index.js.map +1 -1
- package/dist/interfaces/index.d.cts +14 -6
- package/dist/interfaces/index.d.ts +14 -6
- package/dist/interfaces-43cReBcS.d.cts +3346 -0
- package/dist/interfaces-B8xKNl_6.d.ts +997 -0
- package/dist/interfaces-D2NO6kDD.d.cts +997 -0
- package/dist/interfaces-z_xYJlgV.d.ts +3346 -0
- package/dist/math/index.cjs +115 -0
- package/dist/math/index.cjs.map +1 -0
- package/dist/math/index.d.cts +1327 -0
- package/dist/math/index.d.ts +1327 -0
- package/dist/math/index.js +10 -0
- package/dist/math/index.js.map +1 -0
- package/dist/networks-RMd3abPE.d.ts +44 -0
- package/dist/networks-yAoO8peQ.d.cts +44 -0
- package/dist/relayer-NRRMSMNB.js +4 -0
- package/dist/relayer-NRRMSMNB.js.map +1 -0
- package/dist/relayer-RJHEIXJG.cjs +21 -0
- package/dist/relayer-RJHEIXJG.cjs.map +1 -0
- package/dist/solana/index.cjs +56 -0
- package/dist/solana/index.cjs.map +1 -0
- package/dist/solana/index.d.cts +105 -0
- package/dist/solana/index.d.ts +105 -0
- package/dist/solana/index.js +7 -0
- package/dist/solana/index.js.map +1 -0
- package/dist/{index-CLj_zWSD.d.ts → temporal-BbRaEPoO.d.ts} +1 -1
- package/dist/{index-CX6_pIRS.d.cts → temporal-oUj7iCaq.d.cts} +1 -1
- package/dist/transaction-forwarder-5mAMTjw6.d.ts +1155 -0
- package/dist/transaction-forwarder-C6gMUG7a.d.cts +1155 -0
- package/dist/types/index.cjs +232 -231
- package/dist/types/index.d.cts +15 -1485
- package/dist/types/index.d.ts +15 -1485
- package/dist/types/index.js +2 -1
- package/dist/types-BohhvPth.d.cts +87 -0
- package/dist/types-CW0oTT0j.d.ts +87 -0
- package/dist/types-C_V_CaKK.d.cts +2468 -0
- package/dist/types-C_V_CaKK.d.ts +2468 -0
- package/dist/types-Ca7frykr.d.ts +793 -0
- package/dist/types-CuKeoI19.d.cts +1296 -0
- package/dist/types-CxfTIpN9.d.ts +1052 -0
- package/dist/{types-n-sHFcgr.d.ts → types-D1jDUjfN.d.ts} +2 -2
- package/dist/types-DKEDUlH9.d.ts +1296 -0
- package/dist/types-EKuIfxTz.d.cts +1052 -0
- package/dist/{types-BBuELtY8.d.cts → types-IMGYmlv-.d.cts} +2 -2
- package/dist/types-PwNLi_2k.d.cts +793 -0
- package/dist/utils/index.cjs +823 -525
- package/dist/utils/index.d.cts +1711 -4021
- package/dist/utils/index.d.ts +1711 -4021
- package/dist/utils/index.js +9 -3
- package/dist/{versions-D9PqsEvj.d.cts → versions-BRlR36EA.d.cts} +1 -0
- package/dist/{versions-D9PqsEvj.d.ts → versions-BRlR36EA.d.ts} +1 -0
- package/package.json +79 -18
- package/dist/chunk-2Q75CQQJ.js.map +0 -1
- package/dist/chunk-BM7N6N7E.js.map +0 -1
- package/dist/chunk-GXKSUB2U.cjs +0 -4416
- package/dist/chunk-GXKSUB2U.cjs.map +0 -1
- package/dist/chunk-HOEXDXRC.cjs.map +0 -1
- package/dist/chunk-MDFSBU5W.cjs.map +0 -1
- package/dist/chunk-MQY7HDIA.js +0 -600
- package/dist/chunk-MQY7HDIA.js.map +0 -1
- package/dist/chunk-MVKTV3FT.cjs.map +0 -1
- package/dist/chunk-PG2J6V6Y.js +0 -4094
- package/dist/chunk-PG2J6V6Y.js.map +0 -1
- package/dist/chunk-VEGLTTYQ.cjs +0 -621
- package/dist/chunk-VEGLTTYQ.cjs.map +0 -1
- package/dist/chunk-WVHQ46DD.js +0 -758
- package/dist/chunk-WVHQ46DD.js.map +0 -1
- package/dist/index-B9pDY73x.d.ts +0 -12933
- package/dist/index-D33yo0qB.d.cts +0 -12933
- package/dist/networks-C-orpSFW.d.ts +0 -65
- 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 };
|