@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.
- 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-3LS5P32X.cjs +10892 -0
- package/dist/chunk-3LS5P32X.cjs.map +1 -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-KMRROOME.js +10750 -0
- package/dist/chunk-KMRROOME.js.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-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 +5133 -16116
- 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 +3228 -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,393 @@
|
|
|
1
|
+
import { sleep } from './chunk-TLR7A64G.js';
|
|
2
|
+
import { assertTransactionSignature } from './chunk-DD2WCK4C.js';
|
|
3
|
+
import { __name } from './chunk-7QVYU63E.js';
|
|
4
|
+
import { fetchEncodedAccounts, createSolanaRpc, signBytes, getTransactionEncoder, getTransactionDecoder, generateKeyPairSigner, createKeyPairSignerFromBytes, sendAndConfirmTransactionFactory, createSolanaRpcSubscriptions, getBase64EncodedWireTransaction, getSignatureFromTransaction, signature } from '@solana/kit';
|
|
5
|
+
import { SolanaSignTransaction, SolanaSignMessage } from '@solana/wallet-standard-features';
|
|
6
|
+
|
|
7
|
+
function getRpcAccountInfoProvider(config) {
|
|
8
|
+
const { rpcUrl } = config;
|
|
9
|
+
let rpcClient = null;
|
|
10
|
+
function getRpc() {
|
|
11
|
+
rpcClient ??= createSolanaRpc(rpcUrl);
|
|
12
|
+
return rpcClient;
|
|
13
|
+
}
|
|
14
|
+
__name(getRpc, "getRpc");
|
|
15
|
+
return async (addresses, options) => {
|
|
16
|
+
const commitment = options?.commitment ?? "confirmed";
|
|
17
|
+
if (addresses.length === 0) {
|
|
18
|
+
return /* @__PURE__ */ new Map();
|
|
19
|
+
}
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
const uniqueAddresses = [];
|
|
22
|
+
for (const addr of addresses) {
|
|
23
|
+
const key = addr.toString();
|
|
24
|
+
if (!seen.has(key)) {
|
|
25
|
+
seen.add(key);
|
|
26
|
+
uniqueAddresses.push(addr);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const rpc = getRpc();
|
|
30
|
+
const accounts = await fetchEncodedAccounts(rpc, uniqueAddresses, { commitment });
|
|
31
|
+
const result = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const [index, uniqueAddress] of uniqueAddresses.entries()) {
|
|
33
|
+
result.set(uniqueAddress, accounts[index]);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
__name(getRpcAccountInfoProvider, "getRpcAccountInfoProvider");
|
|
39
|
+
function getRpcBlockhashProvider(config, deps) {
|
|
40
|
+
const { rpcUrl } = config;
|
|
41
|
+
const { createRpc = createSolanaRpc } = deps ?? {};
|
|
42
|
+
const rpc = createRpc(rpcUrl);
|
|
43
|
+
return async (options) => {
|
|
44
|
+
const commitment = options?.commitment ?? "confirmed";
|
|
45
|
+
const response = await rpc.getLatestBlockhash({ commitment }).send();
|
|
46
|
+
return {
|
|
47
|
+
blockhash: response.value.blockhash,
|
|
48
|
+
lastValidBlockHeight: response.value.lastValidBlockHeight
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
__name(getRpcBlockhashProvider, "getRpcBlockhashProvider");
|
|
53
|
+
function getRpcEpochInfoProvider(config, deps) {
|
|
54
|
+
const { rpcUrl } = config;
|
|
55
|
+
const { createRpc = createSolanaRpc } = deps ?? {};
|
|
56
|
+
const rpc = createRpc(rpcUrl);
|
|
57
|
+
return async (options) => {
|
|
58
|
+
const commitment = options?.commitment ?? "confirmed";
|
|
59
|
+
const response = await rpc.getEpochInfo({ commitment }).send();
|
|
60
|
+
return {
|
|
61
|
+
epoch: response.epoch,
|
|
62
|
+
slotIndex: response.slotIndex,
|
|
63
|
+
slotsInEpoch: response.slotsInEpoch,
|
|
64
|
+
absoluteSlot: response.absoluteSlot,
|
|
65
|
+
blockHeight: response.blockHeight,
|
|
66
|
+
...response.transactionCount !== null && {
|
|
67
|
+
transactionCount: response.transactionCount
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
__name(getRpcEpochInfoProvider, "getRpcEpochInfoProvider");
|
|
73
|
+
function convertSolanaKitKeypairSignerToIUmbraSigner(kps) {
|
|
74
|
+
return {
|
|
75
|
+
address: kps.address,
|
|
76
|
+
async signTransaction(transaction) {
|
|
77
|
+
const kitTx = transaction;
|
|
78
|
+
const [sigDict] = await kps.signTransactions([kitTx]);
|
|
79
|
+
return {
|
|
80
|
+
...transaction,
|
|
81
|
+
signatures: { ...transaction.signatures, ...sigDict }
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
async signTransactions(transactions) {
|
|
85
|
+
const kitTxs = transactions;
|
|
86
|
+
const sigDicts = await kps.signTransactions(kitTxs);
|
|
87
|
+
return transactions.map((tx, index) => ({
|
|
88
|
+
...tx,
|
|
89
|
+
signatures: { ...tx.signatures, ...sigDicts[index] }
|
|
90
|
+
}));
|
|
91
|
+
},
|
|
92
|
+
async signMessage(message) {
|
|
93
|
+
const signature = await signBytes(kps.keyPair.privateKey, message);
|
|
94
|
+
return { message, signature, signer: kps.address };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
__name(convertSolanaKitKeypairSignerToIUmbraSigner, "convertSolanaKitKeypairSignerToIUmbraSigner");
|
|
99
|
+
function convertWalletStandardAccountToIUmbraSigner(wallet, account) {
|
|
100
|
+
const walletFeatures = wallet.features;
|
|
101
|
+
const signTxFeature = walletFeatures[SolanaSignTransaction];
|
|
102
|
+
const signMessageFeature = walletFeatures[SolanaSignMessage];
|
|
103
|
+
if (signTxFeature === void 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Wallet "${wallet.name}" does not support the "${SolanaSignTransaction}" feature`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (signMessageFeature === void 0) {
|
|
109
|
+
throw new Error(`Wallet "${wallet.name}" does not support the "${SolanaSignMessage}" feature`);
|
|
110
|
+
}
|
|
111
|
+
const encoder = getTransactionEncoder();
|
|
112
|
+
const decoder = getTransactionDecoder();
|
|
113
|
+
return {
|
|
114
|
+
address: account.address,
|
|
115
|
+
async signTransaction(transaction) {
|
|
116
|
+
const wireBytes = encoder.encode(transaction);
|
|
117
|
+
const [output] = await signTxFeature.signTransaction({ account, transaction: wireBytes });
|
|
118
|
+
const decoded = decoder.decode(output.signedTransaction);
|
|
119
|
+
return {
|
|
120
|
+
...transaction,
|
|
121
|
+
signatures: { ...transaction.signatures, ...decoded.signatures }
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
async signTransactions(transactions) {
|
|
125
|
+
const inputs = transactions.map((tx) => ({
|
|
126
|
+
account,
|
|
127
|
+
transaction: encoder.encode(tx)
|
|
128
|
+
}));
|
|
129
|
+
const outputs = await signTxFeature.signTransaction(...inputs);
|
|
130
|
+
return transactions.map((tx, index) => {
|
|
131
|
+
const decoded = decoder.decode(outputs[index].signedTransaction);
|
|
132
|
+
return {
|
|
133
|
+
...tx,
|
|
134
|
+
signatures: { ...tx.signatures, ...decoded.signatures }
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
async signMessage(message) {
|
|
139
|
+
const [output] = await signMessageFeature.signMessage({ account, message });
|
|
140
|
+
return {
|
|
141
|
+
message,
|
|
142
|
+
// Cast required: Wallet Standard returns its own signature type,
|
|
143
|
+
// we need @solana/kit's SignatureBytes. Both are 64-byte Uint8Array.
|
|
144
|
+
signature: output.signature,
|
|
145
|
+
signer: account.address
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
__name(convertWalletStandardAccountToIUmbraSigner, "convertWalletStandardAccountToIUmbraSigner");
|
|
151
|
+
async function createInMemorySigner() {
|
|
152
|
+
const kps = await generateKeyPairSigner();
|
|
153
|
+
return convertSolanaKitKeypairSignerToIUmbraSigner(kps);
|
|
154
|
+
}
|
|
155
|
+
__name(createInMemorySigner, "createInMemorySigner");
|
|
156
|
+
async function createSignerFromPrivateKeyBytes(bytes) {
|
|
157
|
+
const kps = await createKeyPairSignerFromBytes(bytes);
|
|
158
|
+
return convertSolanaKitKeypairSignerToIUmbraSigner(kps);
|
|
159
|
+
}
|
|
160
|
+
__name(createSignerFromPrivateKeyBytes, "createSignerFromPrivateKeyBytes");
|
|
161
|
+
function createSignerFromKeyPair(kps) {
|
|
162
|
+
return convertSolanaKitKeypairSignerToIUmbraSigner(kps);
|
|
163
|
+
}
|
|
164
|
+
__name(createSignerFromKeyPair, "createSignerFromKeyPair");
|
|
165
|
+
function createSignerFromWalletAccount(wallet, account) {
|
|
166
|
+
return convertWalletStandardAccountToIUmbraSigner(wallet, account);
|
|
167
|
+
}
|
|
168
|
+
__name(createSignerFromWalletAccount, "createSignerFromWalletAccount");
|
|
169
|
+
function assertKitCompatibleTransaction(transaction) {
|
|
170
|
+
if (transaction.messageBytes === void 0) {
|
|
171
|
+
throw new Error("Transaction is missing messageBytes — not a valid signed transaction");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
__name(assertKitCompatibleTransaction, "assertKitCompatibleTransaction");
|
|
175
|
+
function toTransactionSignature(signature) {
|
|
176
|
+
const sigString = signature.toString();
|
|
177
|
+
assertTransactionSignature(sigString);
|
|
178
|
+
return sigString;
|
|
179
|
+
}
|
|
180
|
+
__name(toTransactionSignature, "toTransactionSignature");
|
|
181
|
+
function getWebsocketTransactionForwarder(config, deps) {
|
|
182
|
+
const { rpcUrl, rpcSubscriptionsUrl } = config;
|
|
183
|
+
const {
|
|
184
|
+
createRpc = createSolanaRpc,
|
|
185
|
+
createRpcSubscriptions = createSolanaRpcSubscriptions,
|
|
186
|
+
sendAndConfirmTransactionFactory: sendAndConfirmTransactionFactory$1 = sendAndConfirmTransactionFactory
|
|
187
|
+
} = deps ?? {};
|
|
188
|
+
const rpc = createRpc(rpcUrl);
|
|
189
|
+
const rpcSubscriptions = createRpcSubscriptions(rpcSubscriptionsUrl);
|
|
190
|
+
const sendAndConfirm = sendAndConfirmTransactionFactory$1({
|
|
191
|
+
rpc,
|
|
192
|
+
rpcSubscriptions
|
|
193
|
+
});
|
|
194
|
+
async function sendAndConfirmTransaction(transaction, options) {
|
|
195
|
+
const commitment = options.commitment ?? "confirmed";
|
|
196
|
+
assertKitCompatibleTransaction(transaction);
|
|
197
|
+
await sendAndConfirm(transaction, {
|
|
198
|
+
commitment
|
|
199
|
+
});
|
|
200
|
+
const signature = getSignatureFromTransaction(transaction);
|
|
201
|
+
return toTransactionSignature(signature);
|
|
202
|
+
}
|
|
203
|
+
__name(sendAndConfirmTransaction, "sendAndConfirmTransaction");
|
|
204
|
+
return {
|
|
205
|
+
/**
|
|
206
|
+
* Forwards transactions one at a time, waiting for each to reach the
|
|
207
|
+
* requested commitment level before submitting the next.
|
|
208
|
+
*
|
|
209
|
+
* @param transactions - Ordered array of signed transactions.
|
|
210
|
+
* @param options - Forward options (commitment). Defaults to `"confirmed"`.
|
|
211
|
+
* @returns Array of confirmed signatures in input order.
|
|
212
|
+
*/
|
|
213
|
+
forwardSequentially: /* @__PURE__ */ __name(async (transactions, options = {}) => {
|
|
214
|
+
const signatures = [];
|
|
215
|
+
for (const transaction of transactions) {
|
|
216
|
+
const signature = await sendAndConfirmTransaction(transaction, options);
|
|
217
|
+
signatures.push(signature);
|
|
218
|
+
}
|
|
219
|
+
return signatures;
|
|
220
|
+
}, "forwardSequentially"),
|
|
221
|
+
/**
|
|
222
|
+
* Forwards all transactions simultaneously via `Promise.all`.
|
|
223
|
+
*
|
|
224
|
+
* Each transaction is passed to `sendAndConfirmTransaction` concurrently.
|
|
225
|
+
* Results are returned in input order regardless of confirmation order.
|
|
226
|
+
*
|
|
227
|
+
* @param transactions - Array of signed transactions to submit in parallel.
|
|
228
|
+
* @param options - Forward options (commitment). Defaults to `"confirmed"`.
|
|
229
|
+
* @returns Array of confirmed signatures in input order.
|
|
230
|
+
*/
|
|
231
|
+
forwardInParallel: /* @__PURE__ */ __name(async (transactions, options = {}) => {
|
|
232
|
+
const signaturePromises = transactions.map(
|
|
233
|
+
async (tx) => sendAndConfirmTransaction(tx, options)
|
|
234
|
+
);
|
|
235
|
+
return Promise.all(signaturePromises);
|
|
236
|
+
}, "forwardInParallel"),
|
|
237
|
+
fireAndForget: /* @__PURE__ */ __name(async (transaction) => {
|
|
238
|
+
const wireTransaction = getBase64EncodedWireTransaction(transaction);
|
|
239
|
+
const response = await rpc.sendTransaction(wireTransaction, {
|
|
240
|
+
encoding: "base64",
|
|
241
|
+
skipPreflight: true
|
|
242
|
+
}).send();
|
|
243
|
+
return toTransactionSignature(response);
|
|
244
|
+
}, "fireAndForget")
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
__name(getWebsocketTransactionForwarder, "getWebsocketTransactionForwarder");
|
|
248
|
+
function getPollingTransactionForwarder(config, deps) {
|
|
249
|
+
const { rpcUrl } = config;
|
|
250
|
+
const { createRpc = createSolanaRpc } = deps ?? {};
|
|
251
|
+
const rpc = createRpc(rpcUrl);
|
|
252
|
+
async function sendTransactionOnly(transaction, commitment) {
|
|
253
|
+
const wireTransaction = getBase64EncodedWireTransaction(transaction);
|
|
254
|
+
const response = await rpc.sendTransaction(wireTransaction, {
|
|
255
|
+
encoding: "base64",
|
|
256
|
+
preflightCommitment: commitment
|
|
257
|
+
}).send();
|
|
258
|
+
return toTransactionSignature(response);
|
|
259
|
+
}
|
|
260
|
+
__name(sendTransactionOnly, "sendTransactionOnly");
|
|
261
|
+
async function waitForConfirmation(signature$1, commitment, timeoutMs, pollingIntervalMs) {
|
|
262
|
+
const startTime = Date.now();
|
|
263
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
264
|
+
const response = await rpc.getSignatureStatuses([signature(signature$1)], {
|
|
265
|
+
searchTransactionHistory: false
|
|
266
|
+
}).send();
|
|
267
|
+
const status = response.value[0];
|
|
268
|
+
if (status !== null) {
|
|
269
|
+
const confirmedCommitments = ["confirmed", "finalized"];
|
|
270
|
+
const finalizedCommitments = ["finalized"];
|
|
271
|
+
const confirmationStatus = status.confirmationStatus;
|
|
272
|
+
if (commitment === "processed" && confirmationStatus !== null) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (commitment === "confirmed" && confirmationStatus !== null && confirmedCommitments.includes(confirmationStatus)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (commitment === "finalized" && confirmationStatus !== null && finalizedCommitments.includes(confirmationStatus)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (status.err !== null) {
|
|
282
|
+
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
await sleep(pollingIntervalMs);
|
|
286
|
+
}
|
|
287
|
+
throw new Error(`Transaction confirmation timed out after ${String(timeoutMs)}ms`);
|
|
288
|
+
}
|
|
289
|
+
__name(waitForConfirmation, "waitForConfirmation");
|
|
290
|
+
async function sendWithRetries(transaction, commitment, maxRetries) {
|
|
291
|
+
let lastError;
|
|
292
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
293
|
+
try {
|
|
294
|
+
return await sendTransactionOnly(transaction, commitment);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
297
|
+
if (attempt < maxRetries - 1) {
|
|
298
|
+
await sleep(1e3 * (attempt + 1));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
throw lastError ?? new Error("Failed to send transaction after retries");
|
|
303
|
+
}
|
|
304
|
+
__name(sendWithRetries, "sendWithRetries");
|
|
305
|
+
return {
|
|
306
|
+
/**
|
|
307
|
+
* Forwards transactions sequentially using polling-based confirmation.
|
|
308
|
+
*
|
|
309
|
+
* @remarks
|
|
310
|
+
* For each transaction in order:
|
|
311
|
+
* 1. Sends with retries (up to `maxRetries`).
|
|
312
|
+
* 2. Polls until confirmed (up to `timeoutMs`).
|
|
313
|
+
* 3. Invokes `onTransactionConfirmed` if provided (awaited).
|
|
314
|
+
*
|
|
315
|
+
* Aborts on first failure without submitting subsequent transactions.
|
|
316
|
+
*
|
|
317
|
+
* @param transactions - Ordered array of signed transactions.
|
|
318
|
+
* @param options - Polling-specific options. All fields have defaults.
|
|
319
|
+
* @returns Array of confirmed signatures in input order.
|
|
320
|
+
*/
|
|
321
|
+
forwardSequentially: /* @__PURE__ */ __name(async (transactions, options = {}) => {
|
|
322
|
+
const {
|
|
323
|
+
commitment = "confirmed",
|
|
324
|
+
timeoutMs = 6e4,
|
|
325
|
+
pollingIntervalMs = 1e3,
|
|
326
|
+
maxRetries = 3,
|
|
327
|
+
onTransactionConfirmed
|
|
328
|
+
} = options;
|
|
329
|
+
const signatures = [];
|
|
330
|
+
const totalCount = transactions.length;
|
|
331
|
+
for (const [index, transaction] of transactions.entries()) {
|
|
332
|
+
const signature = await sendWithRetries(transaction, commitment, maxRetries);
|
|
333
|
+
await waitForConfirmation(signature, commitment, timeoutMs, pollingIntervalMs);
|
|
334
|
+
signatures.push(signature);
|
|
335
|
+
if (onTransactionConfirmed !== void 0) {
|
|
336
|
+
await onTransactionConfirmed({
|
|
337
|
+
signature,
|
|
338
|
+
index,
|
|
339
|
+
totalCount
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return signatures;
|
|
344
|
+
}, "forwardSequentially"),
|
|
345
|
+
/**
|
|
346
|
+
* Forwards all transactions in parallel using polling-based confirmation.
|
|
347
|
+
*
|
|
348
|
+
* @remarks
|
|
349
|
+
* All transactions are sent concurrently (with retries), then all
|
|
350
|
+
* confirmations are awaited concurrently. The `onTransactionConfirmed`
|
|
351
|
+
* callback is NOT invoked during parallel forwarding.
|
|
352
|
+
*
|
|
353
|
+
* @param transactions - Array of signed transactions to submit in parallel.
|
|
354
|
+
* @param options - Polling-specific options. All fields have defaults.
|
|
355
|
+
* @returns Array of confirmed signatures in input order.
|
|
356
|
+
*/
|
|
357
|
+
forwardInParallel: /* @__PURE__ */ __name(async (transactions, options = {}) => {
|
|
358
|
+
const {
|
|
359
|
+
commitment = "confirmed",
|
|
360
|
+
timeoutMs = 6e4,
|
|
361
|
+
pollingIntervalMs = 1e3,
|
|
362
|
+
maxRetries = 3
|
|
363
|
+
} = options;
|
|
364
|
+
const sendPromises = transactions.map(
|
|
365
|
+
async (tx) => sendWithRetries(tx, commitment, maxRetries)
|
|
366
|
+
);
|
|
367
|
+
const signatures = await Promise.all(sendPromises);
|
|
368
|
+
const confirmationPromises = signatures.map(
|
|
369
|
+
async (sig) => waitForConfirmation(sig, commitment, timeoutMs, pollingIntervalMs)
|
|
370
|
+
);
|
|
371
|
+
await Promise.all(confirmationPromises);
|
|
372
|
+
return signatures;
|
|
373
|
+
}, "forwardInParallel"),
|
|
374
|
+
fireAndForget: /* @__PURE__ */ __name(async (transaction) => {
|
|
375
|
+
return sendTransactionOnly(transaction, "confirmed");
|
|
376
|
+
}, "fireAndForget")
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
__name(getPollingTransactionForwarder, "getPollingTransactionForwarder");
|
|
380
|
+
|
|
381
|
+
// src/solana/alt-utils.ts
|
|
382
|
+
function buildAltAddressesRecord(altEntry) {
|
|
383
|
+
return { [altEntry.altAddress]: [...altEntry.addresses] };
|
|
384
|
+
}
|
|
385
|
+
__name(buildAltAddressesRecord, "buildAltAddressesRecord");
|
|
386
|
+
function lookupAltEntry(addressLookupTables, clusterOffset, instructionName) {
|
|
387
|
+
return addressLookupTables[clusterOffset]?.[instructionName];
|
|
388
|
+
}
|
|
389
|
+
__name(lookupAltEntry, "lookupAltEntry");
|
|
390
|
+
|
|
391
|
+
export { buildAltAddressesRecord, createInMemorySigner, createSignerFromKeyPair, createSignerFromPrivateKeyBytes, createSignerFromWalletAccount, getPollingTransactionForwarder, getRpcAccountInfoProvider, getRpcBlockhashProvider, getRpcEpochInfoProvider, getWebsocketTransactionForwarder, lookupAltEntry };
|
|
392
|
+
//# sourceMappingURL=chunk-HA5FLM63.js.map
|
|
393
|
+
//# sourceMappingURL=chunk-HA5FLM63.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/solana/account-info-provider.ts","../src/solana/blockhash-provider.ts","../src/solana/epoch-info-provider.ts","../src/solana/signers.ts","../src/solana/transaction-forwarder.ts","../src/solana/alt-utils.ts"],"names":["defaultCreateSolanaRpc","kitGenerateKeyPairSigner","defaultCreateSolanaRpcSubscriptions","sendAndConfirmTransactionFactory","defaultSendAndConfirmTransactionFactory","signature","toKitSignature"],"mappings":"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","file":"chunk-HA5FLM63.js","sourcesContent":["/**\n * RPC Account Info Provider.\n *\n * This module provides the factory function {@link getRpcAccountInfoProvider}\n * that creates an {@link AccountInfoProviderFunction} for fetching raw, encoded\n * Solana account data via the JSON-RPC API.\n *\n * ## Overview\n *\n * The provider wraps Solana's `getMultipleAccounts` RPC method, exposed through\n * `@solana/kit`'s `fetchEncodedAccounts` helper. It returns account data as\n * `MaybeEncodedAccount` values — discriminated unions that indicate whether\n * each queried account exists on-chain.\n *\n * ## How It Works\n *\n * 1. The factory accepts an RPC URL and returns a **function** (not an object).\n * 2. The function accepts an array of Solana addresses and returns a\n * `Map<Address, MaybeEncodedAccount>`.\n * 3. Input addresses are **deduplicated** before the RPC call to avoid wasting\n * bandwidth on redundant `getMultipleAccounts` slots.\n * 4. The RPC client is created **lazily** on first invocation — no connection\n * is established until the function is actually called.\n * 5. Subsequent calls reuse the same RPC client instance (singleton per factory).\n *\n * ## Usage in the SDK\n *\n * This provider is wired into the {@link IUmbraClient} at construction time via\n * `getUmbraClient()`. Service factories (deposit, claim, withdraw, etc.) receive\n * it through their dependency injection and use it to:\n *\n * - Fetch `EncryptedUserAccount` and `EncryptedTokenAccount` PDAs to determine\n * instruction variant selection (e.g., new vs. existing account).\n * - Fetch `ProtocolConfig` to validate program state.\n * - Fetch `FeeSchedule` and `FeeVault` accounts for fee calculation.\n * - Fetch mint accounts for Token-2022 transfer fee configuration.\n *\n * ## Decoding Accounts\n *\n * The returned `MaybeEncodedAccount` values carry raw base64-decoded bytes.\n * Pass them to Codama-generated decoders to obtain typed program account structs:\n *\n * ```typescript\n * import { decodeEncryptedUserAccount } from \"@umbra-privacy/umbra-codama\";\n *\n * const accountMap = await fetchAccounts([userPda]);\n * const maybeAccount = accountMap.get(userPda);\n * if (maybeAccount?.exists) {\n * const decoded = decodeEncryptedUserAccount(maybeAccount);\n * console.log(\"User commitment:\", decoded.data.userCommitment);\n * }\n * ```\n *\n * ## Error Handling\n *\n * RPC errors (network failures, rate limiting, malformed responses) propagate\n * as rejected promises. The provider does **not** implement retry logic —\n * callers are responsible for wrapping calls in their own retry/backoff\n * strategy if needed.\n *\n * ## Testing\n *\n * The `AccountInfoProviderFunction` type is a plain async function, making it\n * trivial to mock in tests without any RPC dependency:\n *\n * ```typescript\n * const mockProvider: AccountInfoProviderFunction = async (addresses) => {\n * const map = new Map<Address, MaybeEncodedAccount>();\n * for (const addr of addresses) {\n * map.set(addr, { exists: false });\n * }\n * return map;\n * };\n * ```\n *\n * @see {@link AccountInfoProviderFunction} for the returned function type\n * @see {@link getRpcAccountInfoProvider} for the factory function\n *\n * @packageDocumentation\n * @since 2.0.0\n * @module solana/account-info-provider\n */\n\nimport {\n createSolanaRpc,\n fetchEncodedAccounts,\n type Address,\n type Commitment,\n type MaybeEncodedAccount,\n} from \"@solana/kit\";\n\nimport type { AccountInfoProviderFunction } from \"./interfaces\";\n\n/* =============================================================================\n * TYPES\n * ============================================================================= */\n\n/**\n * Type alias for the RPC client instance produced by `createSolanaRpc`.\n *\n * Captured as a local alias so the lazy-singleton pattern in\n * {@link getRpcAccountInfoProvider} can reference the correct type without\n * repeating the generic `ReturnType` expression.\n *\n * @internal\n */\ntype RpcClient = ReturnType<typeof createSolanaRpc>;\n\n/**\n * Configuration for the RPC-based account info provider.\n *\n * @since 2.0.0\n * @public\n */\nexport interface RpcBasedAccountInfoProviderConfig {\n /**\n * The URL of the Solana RPC endpoint.\n *\n * @remarks\n * Must be an HTTP or HTTPS URL pointing to a Solana JSON-RPC server.\n * The provider uses `getMultipleAccounts` under the hood via `@solana/kit`'s\n * `fetchEncodedAccounts` helper.\n *\n * Both public RPC endpoints (e.g., `https://api.mainnet-beta.solana.com`)\n * and private/paid endpoints (e.g., Helius, QuickNode, Triton) are supported.\n * Private endpoints are recommended for production use to avoid rate limiting.\n *\n * @example `\"https://api.mainnet-beta.solana.com\"`\n * @example `\"https://mainnet.helius-rpc.com/?api-key=YOUR_KEY\"`\n */\n readonly rpcUrl: string;\n}\n\n/* =============================================================================\n * FACTORY FUNCTION\n * ============================================================================= */\n\n/**\n * Creates an {@link AccountInfoProviderFunction} that fetches account data\n * from a Solana RPC endpoint.\n *\n * @remarks\n * The returned function is the **primary account-fetching interface** used\n * throughout the Umbra SDK. It is designed as a plain async function (not a\n * class or object with methods) to support the SDK's functional dependency\n * injection pattern and make mocking trivial.\n *\n * ## Lifecycle\n *\n * ```\n * getRpcAccountInfoProvider({ rpcUrl })\n * └─► Returns: async (addresses: Address[]) => Map<Address, MaybeEncodedAccount>\n * │\n * ├─ First call: creates RPC client (lazy singleton)\n * ├─ Deduplicates input addresses\n * ├─ Calls getMultipleAccounts via fetchEncodedAccounts\n * └─ Returns Map<Address, MaybeEncodedAccount>\n * ```\n *\n * ## Commitment Level\n *\n * Each call can specify a commitment level via `options.commitment`.\n * Defaults to `\"confirmed\"` when not specified. The commitment controls\n * how finalized the account data must be before it is returned.\n *\n * ## Deduplication\n *\n * Duplicate entries in the `addresses` array are removed before the RPC call.\n * Each unique address appears exactly once in the `getMultipleAccounts` request.\n * The returned `Map` contains one entry per unique input address, in first-seen\n * order.\n *\n * ## Empty Input\n *\n * Passing an empty array short-circuits immediately and returns an empty `Map`\n * without making any RPC call.\n *\n * ## Error Propagation\n *\n * RPC errors (network failures, endpoint unreachable, rate limiting, malformed\n * responses) are **not caught** and propagate as rejected promises. Callers\n * should implement retry logic for production use. Common retryable errors:\n *\n * - HTTP 429 (Too Many Requests) — back off and retry\n * - HTTP 502/503/504 (Server Error) — transient, retry with backoff\n * - Network timeout — retry with increased timeout\n *\n * ## Solana RPC Limits\n *\n * `getMultipleAccounts` supports up to **100 addresses** per call. If more\n * than 100 unique addresses are passed, `@solana/kit` will automatically\n * batch them into multiple RPC calls. However, this increases latency and\n * the risk of partial failure.\n *\n * @param config - Configuration containing the RPC endpoint URL.\n * @returns An {@link AccountInfoProviderFunction}: an async function that\n * accepts a readonly array of {@link Address} values and resolves to a\n * `Map<Address, MaybeEncodedAccount>`. Each map entry corresponds to one\n * unique input address, with `{ exists: true, data, ... }` for on-chain\n * accounts or `{ exists: false }` for missing accounts.\n *\n * @throws The returned function may throw (or reject) with:\n * - Network errors if the RPC endpoint is unreachable\n * - HTTP errors if the endpoint rate-limits or returns an error status\n * - JSON-RPC errors if the request parameters are invalid\n *\n * @example\n * Basic usage — fetching two accounts:\n * ```typescript\n * import { getRpcAccountInfoProvider } from \"@umbra-privacy/sdk\";\n * import { address } from \"@solana/kit\";\n *\n * const fetchAccounts = getRpcAccountInfoProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const addresses = [\n * address(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\"),\n * address(\"11111111111111111111111111111111\"),\n * ];\n *\n * const accountMap = await fetchAccounts(addresses);\n *\n * for (const [addr, account] of accountMap) {\n * if (account.exists) {\n * console.log(`${addr}: ${account.data.length} bytes`);\n * } else {\n * console.log(`${addr}: does not exist`);\n * }\n * }\n * ```\n *\n * @example\n * Decoding a fetched account with a Codama decoder:\n * ```typescript\n * import { getRpcAccountInfoProvider } from \"@umbra-privacy/sdk\";\n * import { decodeEncryptedUserAccount } from \"@umbra-privacy/umbra-codama\";\n *\n * const fetchAccounts = getRpcAccountInfoProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const accountMap = await fetchAccounts([userAccountPda]);\n * const maybeAccount = accountMap.get(userAccountPda);\n *\n * if (maybeAccount?.exists) {\n * const decoded = decodeEncryptedUserAccount(maybeAccount);\n * console.log(\"User commitment registered:\", decoded.data.isUserCommitmentRegistered);\n * } else {\n * console.log(\"User account does not exist — registration required\");\n * }\n * ```\n *\n * @example\n * Handling duplicate addresses (deduplicated automatically):\n * ```typescript\n * const myAddress = address(\"MyAccount1111111111111111111111111111111111\");\n * const accountMap = await fetchAccounts([myAddress, myAddress, myAddress]);\n * // Only one RPC slot used; the Map has one entry.\n * console.log(accountMap.size); // 1\n * ```\n *\n * @example\n * Using as a mock in tests (no RPC dependency):\n * ```typescript\n * import type { AccountInfoProviderFunction } from \"@umbra-privacy/sdk/solana\";\n *\n * const mockFetchAccounts: AccountInfoProviderFunction = async (addresses) => {\n * const map = new Map();\n * for (const addr of addresses) {\n * map.set(addr, { exists: false }); // All accounts \"missing\"\n * }\n * return map;\n * };\n *\n * const client = await getUmbraClient(\n * { signer, network: \"devnet\", rpcUrl: \"http://localhost:8899\" },\n * { accountInfoProvider: mockFetchAccounts },\n * );\n * ```\n *\n * @see {@link AccountInfoProviderFunction} for the returned function type\n * @see {@link RpcBasedAccountInfoProviderConfig} for the configuration interface\n * @since 2.0.0\n * @public\n */\nexport function getRpcAccountInfoProvider(\n config: RpcBasedAccountInfoProviderConfig,\n): AccountInfoProviderFunction {\n const { rpcUrl } = config;\n\n // Lazy RPC client singleton — created on first invocation, reused thereafter.\n // This avoids establishing an HTTP connection before the provider is actually\n // needed (e.g., when the provider is constructed at client init but the first\n // account fetch happens seconds later during a deposit operation).\n let rpcClient: RpcClient | null = null;\n\n /**\n * Gets or creates the RPC client instance.\n *\n * @returns The shared RPC client for this provider instance.\n * @internal\n */\n function getRpc(): RpcClient {\n rpcClient ??= createSolanaRpc(rpcUrl);\n return rpcClient;\n }\n\n return async (\n addresses: readonly Address[],\n options?: { readonly commitment?: Commitment },\n ): Promise<Map<Address, MaybeEncodedAccount>> => {\n const commitment: Commitment = options?.commitment ?? \"confirmed\";\n\n // Short-circuit for empty input — no RPC call needed.\n if (addresses.length === 0) {\n return new Map();\n }\n\n // Deduplicate input addresses while preserving first-seen order.\n // This prevents wasting `getMultipleAccounts` slots on duplicate addresses\n // (each slot costs bandwidth and counts toward the 100-address limit).\n const seen = new Set<string>();\n const uniqueAddresses: Address[] = [];\n for (const addr of addresses) {\n const key = addr.toString();\n if (!seen.has(key)) {\n seen.add(key);\n uniqueAddresses.push(addr);\n }\n }\n\n // Fetch all unique accounts in a single batch RPC call.\n // `fetchEncodedAccounts` internally calls `getMultipleAccounts` with\n // `encoding: \"base64\"` and returns results in the same order as input.\n const rpc = getRpc();\n const accounts = await fetchEncodedAccounts(rpc, uniqueAddresses, { commitment });\n\n // Build the result Map. Iteration order follows insertion order (Map spec),\n // which matches the first-seen order of the deduplicated input.\n const result = new Map<Address, MaybeEncodedAccount>();\n for (const [index, uniqueAddress] of uniqueAddresses.entries()) {\n result.set(uniqueAddress, accounts[index]);\n }\n\n return result;\n };\n}\n","/**\n * RPC-Based Blockhash Provider Implementation.\n *\n * This module provides a factory function for creating a blockhash provider\n * that fetches the latest blockhash from a Solana RPC endpoint. Blockhashes are\n * a core component of Solana transaction lifetime management: every transaction\n * must include a recent blockhash that bounds the window during which it is valid.\n *\n * @remarks\n * The provider created by {@link getRpcBlockhashProvider} is stateless and\n * performs no caching — every invocation of the returned function issues a fresh\n * `getLatestBlockhash` RPC call using the `\"confirmed\"` commitment level (the\n * default used by `@solana/kit`). Callers that need caching should wrap the\n * returned function in their own caching layer.\n *\n * The returned {@link GetLatestBlockhash} function is designed to be injected into\n * transaction-building pipelines, enabling straightforward mocking in tests by\n * substituting a static implementation.\n *\n * @see {@link GetLatestBlockhash} for the function type returned by the factory\n * @see {@link LatestBlockhashResult} for the shape of the fetched data\n *\n * @packageDocumentation\n * @since 2.0.0\n * @module implementation/solana/blockhash-provider\n */\n\nimport { createSolanaRpc as defaultCreateSolanaRpc, type Commitment } from \"@solana/kit\";\n\nimport type {\n GetLatestBlockhash,\n LatestBlockhashResult,\n CreateSolanaRpcFunction as CreateSolanaRpcFunction,\n} from \"./interfaces\";\n\n/* =============================================================================\n * TYPES\n * ============================================================================= */\n\n/**\n * Configuration for the RPC-based blockhash provider.\n *\n * @public\n */\nexport interface RpcBasedBlockhashProviderConfig {\n /**\n * The URL of the Solana RPC endpoint.\n *\n * @remarks\n * Should be an HTTP or HTTPS URL. WebSocket endpoints are used separately\n * by the transaction forwarder, not by this provider.\n *\n * @example `\"https://api.mainnet-beta.solana.com\"`\n * @example `\"https://my-rpc.example.com\"`\n */\n readonly rpcUrl: string;\n}\n\n/**\n * Optional dependencies for the RPC-based blockhash provider.\n *\n * @remarks\n * Providing custom dependencies is primarily useful for testing, where a mock\n * RPC factory can be injected to avoid real network calls. In production, omit\n * this parameter to use the default `createSolanaRpc` from `@solana/kit`.\n *\n * @public\n */\nexport interface RpcBasedBlockhashProviderDeps {\n /**\n * Optional custom Solana RPC client factory.\n *\n * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.\n * Override this in tests to inject a mock RPC client.\n *\n * @defaultValue `createSolanaRpc` from `@solana/kit`\n */\n readonly createRpc?: CreateSolanaRpcFunction;\n}\n\n/* =============================================================================\n * FACTORY FUNCTION\n * ============================================================================= */\n\n/**\n * Creates a blockhash provider that fetches the latest blockhash from a Solana\n * RPC endpoint.\n *\n * The factory constructs a single RPC client from the given URL and closes over\n * it. Every call to the returned {@link GetLatestBlockhash} function invokes\n * `rpc.getLatestBlockhash().send()` and extracts `blockhash` and\n * `lastValidBlockHeight` from the response's `value` field.\n *\n * @remarks\n * **Commitment** — Each call accepts an optional `{ commitment }` parameter\n * that defaults to `\"confirmed\"`. Pass `\"finalized\"` for maximum safety.\n *\n * **No caching** — each invocation of the returned function makes an RPC call.\n * If the same blockhash will be used across many operations in a short window,\n * callers should fetch it once and reuse the result rather than calling the\n * provider repeatedly.\n *\n *\n * **Error propagation** — RPC errors (network failures, endpoint unreachable,\n * rate limiting) are not caught and will propagate as rejections from the\n * returned promise. Callers are responsible for implementing retry logic if\n * needed.\n *\n * @param config - Configuration containing the RPC endpoint URL.\n * @param deps - Optional dependencies. Pass a custom `createRpc` for testing.\n * @returns A {@link GetLatestBlockhash} function. Each call to this function\n * issues one `getLatestBlockhash` RPC request and resolves to a\n * {@link LatestBlockhashResult}.\n *\n * @throws The returned function may throw (or reject) if the RPC endpoint is\n * unreachable, returns an unexpected response structure, or rate-limits the\n * caller.\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { getRpcBlockhashProvider } from \"./blockhash-provider\";\n *\n * const getLatestBlockhash = getRpcBlockhashProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const { blockhash, lastValidBlockHeight } = await getLatestBlockhash();\n * console.log(`Current blockhash: ${blockhash}`);\n * console.log(`Valid until block height: ${lastValidBlockHeight}`);\n * ```\n *\n * @example\n * Using in a transaction-building pipeline:\n * ```typescript\n * import { getRpcBlockhashProvider } from \"./blockhash-provider\";\n * import {\n * createTransactionMessage,\n * setTransactionMessageLifetimeUsingBlockhash,\n * pipe,\n * } from \"@solana/kit\";\n *\n * const getLatestBlockhash = getRpcBlockhashProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * async function buildTx() {\n * const blockhashResult = await getLatestBlockhash();\n * return pipe(\n * createTransactionMessage({ version: 0 }),\n * (m) => setTransactionMessageLifetimeUsingBlockhash(blockhashResult, m),\n * );\n * }\n * ```\n *\n * @example\n * Injecting a mock RPC factory in tests:\n * ```typescript\n * import { getRpcBlockhashProvider } from \"./blockhash-provider\";\n *\n * const mockCreateRpc = (_url: string) => ({\n * getLatestBlockhash: () => ({\n * send: async () => ({\n * value: {\n * blockhash: \"FakeBlockhash1111111111111111111111111111111\",\n * lastValidBlockHeight: 9999n,\n * },\n * }),\n * }),\n * });\n *\n * const getLatestBlockhash = getRpcBlockhashProvider(\n * { rpcUrl: \"http://localhost:8899\" },\n * { createRpc: mockCreateRpc as any },\n * );\n *\n * const result = await getLatestBlockhash();\n * // result.blockhash === \"FakeBlockhash1111111111111111111111111111111\"\n * ```\n *\n * @see {@link GetLatestBlockhash} for the returned function type\n * @see {@link LatestBlockhashResult} for the return value shape\n * @public\n */\nexport function getRpcBlockhashProvider(\n config: RpcBasedBlockhashProviderConfig,\n deps?: RpcBasedBlockhashProviderDeps,\n): GetLatestBlockhash {\n const { rpcUrl } = config;\n const { createRpc = defaultCreateSolanaRpc } = deps ?? {};\n const rpc = createRpc(rpcUrl);\n\n return async (options?: { readonly commitment?: Commitment }): Promise<LatestBlockhashResult> => {\n const commitment = options?.commitment ?? \"confirmed\";\n const response = await rpc.getLatestBlockhash({ commitment }).send();\n return {\n blockhash: response.value.blockhash,\n lastValidBlockHeight: response.value.lastValidBlockHeight,\n };\n };\n}\n","/**\n * RPC-Based Epoch Info Provider Implementation.\n *\n * This module provides a factory function for creating an epoch info provider\n * that fetches the current epoch information from a Solana RPC endpoint. Epoch\n * data is used throughout the Umbra SDK primarily to resolve Token-2022 transfer\n * fee schedules, which can change between epochs.\n *\n * @remarks\n * On Solana mainnet, one epoch spans approximately 432,000 slots (~2–3 days).\n * The Token-2022 program uses the epoch number to determine which of two\n * consecutive fee schedules (older vs. newer) is active for a given mint.\n * Fetching the epoch before constructing a deposit or transfer ensures the SDK\n * uses the correct fee when computing the amount that will actually land in the\n * destination account.\n *\n * The provider created by {@link getRpcEpochInfoProvider} is stateless and\n * performs no caching — every invocation of the returned function issues a fresh\n * `getEpochInfo` RPC call. Because epochs change slowly (every ~2–3 days on\n * mainnet), short-term caching at the call site is safe and can reduce RPC load.\n *\n * @see {@link GetEpochInfo} for the function type returned by the factory\n * @see {@link EpochInfoResult} for the shape of the fetched data\n *\n * @packageDocumentation\n * @since 2.0.0\n * @module implementation/solana/epoch-info-provider\n */\n\nimport { createSolanaRpc as defaultCreateSolanaRpc, type Commitment } from \"@solana/kit\";\n\nimport type {\n GetEpochInfo,\n EpochInfoResult,\n CreateSolanaRpcFunction as CreateSolanaRpcFunction,\n} from \"./interfaces\";\n\n/* =============================================================================\n * TYPES\n * ============================================================================= */\n\n/**\n * Configuration for the RPC-based epoch info provider.\n *\n * @public\n */\nexport interface RpcBasedEpochInfoProviderConfig {\n /**\n * The URL of the Solana RPC endpoint.\n *\n * @remarks\n * Should be an HTTP or HTTPS URL pointing to a Solana JSON-RPC server.\n *\n * @example `\"https://api.mainnet-beta.solana.com\"`\n * @example `\"https://api.devnet.solana.com\"`\n */\n readonly rpcUrl: string;\n}\n\n/**\n * Optional dependencies for the RPC-based epoch info provider.\n *\n * @remarks\n * Providing custom dependencies is primarily useful for testing, where a mock\n * RPC factory can be injected to return controlled epoch data without real\n * network calls. In production, omit this parameter to use the default\n * `createSolanaRpc` from `@solana/kit`.\n *\n * @public\n */\nexport interface RpcBasedEpochInfoProviderDeps {\n /**\n * Optional custom Solana RPC client factory.\n *\n * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.\n * Override in tests to inject a mock RPC client with predetermined responses.\n *\n * @defaultValue `createSolanaRpc` from `@solana/kit`\n */\n readonly createRpc?: CreateSolanaRpcFunction;\n}\n\n/* =============================================================================\n * FACTORY FUNCTION\n * ============================================================================= */\n\n/**\n * Creates an epoch info provider that fetches the current epoch information\n * from a Solana RPC endpoint.\n *\n * The factory constructs a single RPC client from the given URL and closes over\n * it. Every call to the returned {@link GetEpochInfo} function invokes\n * `rpc.getEpochInfo().send()` and maps the response fields into an\n * {@link EpochInfoResult}. The `transactionCount` field is conditionally\n * included only when the RPC node returns a non-null value.\n *\n * @remarks\n * **Commitment** — Each call accepts an optional `{ commitment }` parameter\n * that defaults to `\"confirmed\"`. Pass `\"finalized\"` for maximum safety.\n *\n * **No caching** — each invocation of the returned function issues one RPC\n * call. Epochs change approximately every 2–3 days on mainnet, so callers in\n * hot paths may safely cache the result for seconds or even minutes without\n * risk of using a stale epoch.\n *\n *\n * **Token-2022 transfer fees** — The primary consumer of this provider in the\n * SDK is the transfer-fee calculation logic. Token-2022 mints may define two\n * consecutive fee schedules. The active schedule is the one whose configured\n * epoch is less than or equal to `EpochInfoResult.epoch`. Always fetch epoch\n * info fresh when computing deposit amounts to avoid off-by-one errors at epoch\n * boundaries.\n *\n * **Error propagation** — RPC errors are not caught and will propagate as\n * rejections from the returned promise. Callers are responsible for implementing\n * retry logic if needed.\n *\n * @param config - Configuration containing the RPC endpoint URL.\n * @param deps - Optional dependencies. Pass a custom `createRpc` for testing.\n * @returns A {@link GetEpochInfo} function. Each call to this function issues\n * one `getEpochInfo` RPC request and resolves to an {@link EpochInfoResult}.\n *\n * @throws The returned function may throw (or reject) if the RPC endpoint is\n * unreachable, returns an unexpected response, or rate-limits the caller.\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { getRpcEpochInfoProvider } from \"./epoch-info-provider\";\n *\n * const getEpochInfo = getRpcEpochInfoProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const epochInfo = await getEpochInfo();\n * console.log(`Current epoch: ${epochInfo.epoch}`);\n * console.log(`Slot ${epochInfo.slotIndex} of ${epochInfo.slotsInEpoch}`);\n * ```\n *\n * @example\n * Using with Token-2022 transfer fee calculation:\n * ```typescript\n * import { getRpcEpochInfoProvider } from \"./epoch-info-provider\";\n * import { calculateTransferFee } from \"@umbra-privacy/sdk\";\n *\n * const getEpochInfo = getRpcEpochInfoProvider({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const epochInfo = await getEpochInfo();\n * const fee = calculateTransferFee(feeConfig, epochInfo.epoch, depositAmount);\n * // Use (depositAmount + fee) as the transfer amount so the recipient\n * // lands the exact depositAmount after the protocol deducts the fee.\n * ```\n *\n * @example\n * Injecting a mock RPC factory in tests:\n * ```typescript\n * import { getRpcEpochInfoProvider } from \"./epoch-info-provider\";\n *\n * const mockCreateRpc = (_url: string) => ({\n * getEpochInfo: () => ({\n * send: async () => ({\n * epoch: 500n,\n * slotIndex: 100n,\n * slotsInEpoch: 432000n,\n * absoluteSlot: 216100000n,\n * blockHeight: 215800000n,\n * transactionCount: null,\n * }),\n * }),\n * });\n *\n * const getEpochInfo = getRpcEpochInfoProvider(\n * { rpcUrl: \"http://localhost:8899\" },\n * { createRpc: mockCreateRpc as any },\n * );\n *\n * const info = await getEpochInfo();\n * // info.epoch === 500n, info.transactionCount === undefined\n * ```\n *\n * @see {@link GetEpochInfo} for the returned function type\n * @see {@link EpochInfoResult} for the return value shape\n * @public\n */\nexport function getRpcEpochInfoProvider(\n config: RpcBasedEpochInfoProviderConfig,\n deps?: RpcBasedEpochInfoProviderDeps,\n): GetEpochInfo {\n const { rpcUrl } = config;\n const { createRpc = defaultCreateSolanaRpc } = deps ?? {};\n const rpc = createRpc(rpcUrl);\n\n return async (options?: { readonly commitment?: Commitment }): Promise<EpochInfoResult> => {\n const commitment = options?.commitment ?? \"confirmed\";\n const response = await rpc.getEpochInfo({ commitment }).send();\n\n return {\n epoch: response.epoch,\n slotIndex: response.slotIndex,\n slotsInEpoch: response.slotsInEpoch,\n absoluteSlot: response.absoluteSlot,\n blockHeight: response.blockHeight,\n ...(response.transactionCount !== null && {\n transactionCount: response.transactionCount,\n }),\n };\n };\n}\n","/**\n * Signer Helper Functions\n *\n * Factory functions and adapters for creating `IUmbraSigner` instances from\n * common Solana wallet primitives. Covers in-memory keypairs (for testing and\n * scripting), `@solana/kit` `KeyPairSigner` instances, and browser wallets\n * that implement the Wallet Standard (`@wallet-standard/core`).\n *\n * @since 2.0.0\n * @module solana/signers\n * @packageDocumentation\n */\n\nimport {\n generateKeyPairSigner as kitGenerateKeyPairSigner,\n createKeyPairSignerFromBytes,\n signBytes,\n getTransactionEncoder,\n getTransactionDecoder,\n type KeyPairSigner,\n type Address,\n type SignatureBytes,\n} from \"@solana/kit\";\n\nimport {\n SolanaSignTransaction,\n SolanaSignMessage,\n type SolanaSignTransactionFeature,\n type SolanaSignMessageFeature,\n} from \"@solana/wallet-standard-features\";\n\nimport type { Wallet, WalletAccount } from \"@wallet-standard/core\";\n\nimport type { IUmbraSigner, SignedMessage, SignableTransaction } from \"../umbra/interfaces\";\nimport type { SignedTransaction } from \"./types\";\n\n// ============================================================================================\n// Internal Adapters\n// ============================================================================================\n\n/**\n * Adapts a `@solana/kit` `KeyPairSigner` to the `IUmbraSigner` interface.\n *\n * `KeyPairSigner` uses the partial-signer pattern — `signTransactions` returns\n * `SignatureDictionary[]` (maps of address → signature) rather than signed transaction\n * objects. This adapter bridges that gap by merging each returned signature dictionary\n * back into the transaction's `signatures` map.\n *\n * `signMessage` uses `signBytes` directly on the CryptoKey to produce a `SignedMessage`\n * with the correct structure, since `KeyPairSigner` only has `signMessages` (plural) which\n * also returns dictionaries.\n */\nfunction convertSolanaKitKeypairSignerToIUmbraSigner(kps: KeyPairSigner): IUmbraSigner {\n return {\n address: kps.address,\n\n async signTransaction(transaction: SignableTransaction): Promise<SignedTransaction> {\n // Cast required: IUmbraSigner's SignableTransaction does not carry the\n // full generic type parameters that KeyPairSigner.signTransactions expects\n // (lifetime constraint, fee payer, signer list). The runtime values are\n // identical — only the TypeScript generics differ.\n const kitTx = transaction as unknown as Parameters<KeyPairSigner[\"signTransactions\"]>[0][number];\n const [sigDict] = await kps.signTransactions([kitTx]);\n return {\n ...transaction,\n signatures: { ...transaction.signatures, ...sigDict },\n } as unknown as SignedTransaction;\n },\n\n async signTransactions(\n transactions: readonly SignableTransaction[],\n ): Promise<SignedTransaction[]> {\n // Same cast rationale as signTransaction above.\n const kitTxs = transactions as unknown as Parameters<KeyPairSigner[\"signTransactions\"]>[0];\n const sigDicts = await kps.signTransactions(kitTxs);\n return transactions.map((tx, index) => ({\n ...tx,\n signatures: { ...tx.signatures, ...sigDicts[index] },\n })) as unknown as SignedTransaction[];\n },\n\n async signMessage(message: Uint8Array): Promise<SignedMessage> {\n const signature = await signBytes(kps.keyPair.privateKey, message);\n return { message, signature, signer: kps.address };\n },\n };\n}\n\n/**\n * Adapts a Wallet Standard `WalletAccount` to the `IUmbraSigner` interface.\n *\n * Requires both the `Wallet` (for feature implementations) and the specific `WalletAccount`\n * (the signing key to use). The wallet must support both `\"solana:signTransaction\"` and\n * `\"solana:signMessage\"` features — an error is thrown at call time if either is absent.\n *\n * Transaction round-trip: `SignableTransaction` → wire bytes via `getTransactionEncoder`,\n * wallet signs → signed wire bytes, decoded via `getTransactionDecoder` to extract the\n * returned signatures, merged back into the original transaction object (preserving\n * `lifetimeConstraint`).\n */\nfunction convertWalletStandardAccountToIUmbraSigner(\n wallet: Wallet,\n account: WalletAccount,\n): IUmbraSigner {\n const walletFeatures = wallet.features as Partial<\n SolanaSignTransactionFeature & SolanaSignMessageFeature\n >;\n\n const signTxFeature = walletFeatures[SolanaSignTransaction];\n const signMessageFeature = walletFeatures[SolanaSignMessage];\n\n if (signTxFeature === undefined) {\n throw new Error(\n `Wallet \"${wallet.name}\" does not support the \"${SolanaSignTransaction}\" feature`,\n );\n }\n if (signMessageFeature === undefined) {\n throw new Error(`Wallet \"${wallet.name}\" does not support the \"${SolanaSignMessage}\" feature`);\n }\n\n const encoder = getTransactionEncoder();\n const decoder = getTransactionDecoder();\n\n return {\n address: account.address as Address,\n\n async signTransaction(transaction: SignableTransaction): Promise<SignedTransaction> {\n // Cast required: @solana/kit's encoder returns a branded ReadonlyUint8Array,\n // but the Wallet Standard signTransaction expects plain Uint8Array.\n // The runtime value is identical.\n const wireBytes = encoder.encode(transaction) as unknown as Uint8Array;\n const [output] = await signTxFeature.signTransaction({ account, transaction: wireBytes });\n const decoded = decoder.decode(output.signedTransaction);\n return {\n ...transaction,\n signatures: { ...transaction.signatures, ...decoded.signatures },\n } as unknown as SignedTransaction;\n },\n\n async signTransactions(\n transactions: readonly SignableTransaction[],\n ): Promise<SignedTransaction[]> {\n // Same cast rationale as signTransaction above.\n const inputs = transactions.map((tx) => ({\n account,\n transaction: encoder.encode(tx) as unknown as Uint8Array,\n }));\n const outputs = await signTxFeature.signTransaction(...inputs);\n return transactions.map((tx, index) => {\n const decoded = decoder.decode(outputs[index].signedTransaction);\n return {\n ...tx,\n signatures: { ...tx.signatures, ...decoded.signatures },\n } as unknown as SignedTransaction;\n });\n },\n\n async signMessage(message: Uint8Array): Promise<SignedMessage> {\n const [output] = await signMessageFeature.signMessage({ account, message });\n return {\n message,\n // Cast required: Wallet Standard returns its own signature type,\n // we need @solana/kit's SignatureBytes. Both are 64-byte Uint8Array.\n signature: output.signature as unknown as SignatureBytes,\n signer: account.address as Address,\n };\n },\n };\n}\n\n// ============================================================================================\n// Keypair Helpers\n// ============================================================================================\n\n/**\n * Creates a new random in-memory keypair signer.\n *\n * Generates a fresh Ed25519 keypair using the platform's CSPRNG. The private key\n * is non-extractable by default (Web Crypto API constraint). Use this for tests,\n * CI pipelines, and scripts where the wallet lifecycle is managed by your code.\n *\n * @example\n * ```typescript\n * import { createInMemorySigner, getUmbraClient } from \"@umbra-privacy/sdk\";\n *\n * const signer = await createInMemorySigner();\n * const client = await getUmbraClient({ signer, network: \"devnet\", rpcUrl, rpcSubscriptionsUrl });\n * ```\n *\n * @remarks\n * The keypair exists only in memory. It is lost when the process exits or the page\n * refreshes. Only use this for testing or scripts where you control the key lifecycle.\n */\nexport async function createInMemorySigner(): Promise<IUmbraSigner> {\n const kps = await kitGenerateKeyPairSigner();\n return convertSolanaKitKeypairSignerToIUmbraSigner(kps);\n}\n\n/**\n * Creates a signer from raw private key bytes.\n *\n * Accepts either 64-byte combined key material (private key + public key) or a\n * 32-byte seed — `@solana/kit` handles both formats via `createKeyPairSignerFromBytes`.\n *\n * @param bytes - Raw private key bytes (64-byte keypair or 32-byte seed).\n *\n * @example\n * ```typescript\n * import { createSignerFromPrivateKeyBytes, getUmbraClient } from \"@umbra-privacy/sdk\";\n * import { readFileSync } from \"fs\";\n *\n * // Load from a Solana CLI JSON key file (array of numbers)\n * const keyFile = JSON.parse(readFileSync(\"/path/to/keypair.json\", \"utf8\"));\n * const signer = await createSignerFromPrivateKeyBytes(new Uint8Array(keyFile));\n * ```\n */\nexport async function createSignerFromPrivateKeyBytes(bytes: Uint8Array): Promise<IUmbraSigner> {\n const kps = await createKeyPairSignerFromBytes(bytes);\n return convertSolanaKitKeypairSignerToIUmbraSigner(kps);\n}\n\n/**\n * Adapts an existing `@solana/kit` `KeyPairSigner` to `IUmbraSigner`.\n *\n * Use this when you already have a `KeyPairSigner` (e.g., from `generateKeyPairSigner`\n * or `createKeyPairSignerFromBytes`) and want to pass it to `getUmbraClient`.\n *\n * @param kps - A `KeyPairSigner` from `@solana/kit`.\n *\n * @example\n * ```typescript\n * import { generateKeyPairSigner } from \"@solana/kit\";\n * import { createSignerFromKeyPair, getUmbraClient } from \"@umbra-privacy/sdk\";\n *\n * const kps = await generateKeyPairSigner();\n * const signer = createSignerFromKeyPair(kps);\n * const client = await getUmbraClient({ signer, network: \"devnet\", rpcUrl, rpcSubscriptionsUrl });\n * ```\n */\nexport function createSignerFromKeyPair(kps: KeyPairSigner): IUmbraSigner {\n return convertSolanaKitKeypairSignerToIUmbraSigner(kps);\n}\n\n// ============================================================================================\n// Wallet-Standard Helpers\n// ============================================================================================\n\n/**\n * Creates an `IUmbraSigner` from a Wallet Standard `WalletAccount`.\n *\n * This is the primary adapter for browser wallets (Phantom, Backpack, Solflare, etc.)\n * that implement the Wallet Standard. Obtain `wallet` and `account` via\n * `@wallet-standard/react`'s `useWallets()` hook or `@solana/react`.\n *\n * Both `\"solana:signTransaction\"` and `\"solana:signMessage\"` must be present in\n * `wallet.features` — an error is thrown immediately if either is missing.\n *\n * @param wallet - The Wallet Standard `Wallet` object (provides feature implementations).\n * @param account - The specific `WalletAccount` to sign with.\n *\n * @example\n * ```typescript\n * import { useWallet } from \"@solana/react\"; // or @wallet-standard/react\n * import { createSignerFromWalletAccount, getUmbraClient } from \"@umbra-privacy/sdk\";\n *\n * function useUmbraClient(rpcUrl: string, rpcSubscriptionsUrl: string) {\n * const { wallet, account } = useWallet();\n * const signer = createSignerFromWalletAccount(wallet, account);\n * return await getUmbraClient({ signer, network: \"mainnet-beta\", rpcUrl, rpcSubscriptionsUrl });\n * }\n * ```\n */\nexport function createSignerFromWalletAccount(\n wallet: Wallet,\n account: WalletAccount,\n): IUmbraSigner {\n return convertWalletStandardAccountToIUmbraSigner(wallet, account);\n}\n","/**\n * RPC-Based Transaction Forwarder Implementations.\n *\n * This module provides two factory functions that produce {@link TransactionForwarder}\n * implementations with different confirmation strategies:\n *\n * - **{@link getWebsocketTransactionForwarder}** — Uses\n * `sendAndConfirmTransaction` from `@solana/kit`, which subscribes to\n * transaction signature notifications via WebSocket for low-latency\n * confirmation. Requires access to a WebSocket RPC endpoint.\n *\n * - **{@link getPollingTransactionForwarder}** — Sends transactions via\n * HTTP and polls `getSignatureStatuses` on a configurable interval to detect\n * confirmation. Suitable for environments that block WebSocket connections\n * (e.g., certain CI systems, restricted networks). Supports configurable\n * timeouts, retry counts, and a per-transaction confirmation callback.\n *\n * Both factories implement the {@link TransactionForwarder} interface, exposing\n * `forwardSequentially` (ordered, one-at-a-time) and `forwardInParallel`\n * (concurrent submission) methods.\n *\n * @remarks\n * **Choosing a strategy:**\n * - Use the WebSocket forwarder in browser and server environments where\n * WebSocket access is available — it produces lower confirmation latency by\n * receiving push notifications instead of polling.\n * - Use the polling forwarder in environments without WebSocket support or when\n * you need fine-grained control over timeout and retry behavior.\n *\n * **Error handling:**\n * - Both forwarders propagate RPC errors as rejections. Callers are responsible\n * for wrapping the returned promises in their own retry/error-boundary logic.\n * - The polling forwarder includes internal send-level retries with exponential\n * backoff; the WebSocket forwarder relies on `@solana/kit`'s built-in\n * confirmation timeout.\n * - Confirmation timeout in the polling forwarder throws a plain `Error` with\n * a human-readable timeout message.\n *\n * **Wire format:**\n * - Both forwarders serialize transactions using `getBase64EncodedWireTransaction`\n * from `@solana/kit` and submit them via `sendTransaction` with\n * `encoding: \"base64\"`.\n *\n * @packageDocumentation\n * @since 2.0.0\n * @module implementation/solana/transaction-forwarder\n */\n\nimport {\n createSolanaRpc as defaultCreateSolanaRpc,\n createSolanaRpcSubscriptions as defaultCreateSolanaRpcSubscriptions,\n sendAndConfirmTransactionFactory as defaultSendAndConfirmTransactionFactory,\n getBase64EncodedWireTransaction,\n getSignatureFromTransaction,\n signature as toKitSignature,\n type Commitment,\n type Signature,\n} from \"@solana/kit\";\n\nimport type {\n TransactionForwarder,\n CreateSolanaRpcFunction as CreateSolanaRpcFunction,\n CreateSolanaRpcSubscriptionsFunction as CreateSolanaRpcSubscriptionsFunction,\n SendAndConfirmTransactionFactoryFunction as SendAndConfirmTransactionFactoryFunction,\n} from \"./interfaces\";\nimport type { TransactionSignature, SignedTransaction } from \"../types\";\nimport { assertTransactionSignature } from \"../types\";\nimport { sleep } from \"../common/converters\";\n\n/* =============================================================================\n * TYPES\n * ============================================================================= */\n\n/**\n * Configuration for the websocket-based transaction forwarder.\n *\n * @public\n */\nexport interface WebsocketBasedTransactionForwarderConfig {\n /**\n * The HTTP or HTTPS URL of the Solana JSON-RPC endpoint.\n *\n * @remarks\n * Used for `sendTransaction` calls and the `sendAndConfirmTransaction`\n * factory. Must be an HTTP/HTTPS URL, not a WebSocket URL.\n *\n * @example `\"https://api.mainnet-beta.solana.com\"`\n */\n readonly rpcUrl: string;\n\n /**\n * The WebSocket URL of the Solana RPC subscriptions endpoint.\n *\n * @remarks\n * Used to subscribe to signature notifications for real-time confirmation\n * detection. Must be a WebSocket URL (`ws://` or `wss://`).\n *\n * @example `\"wss://api.mainnet-beta.solana.com\"`\n */\n readonly rpcSubscriptionsUrl: string;\n}\n\n/**\n * Optional dependencies for the websocket-based transaction forwarder.\n *\n * @remarks\n * All three dependency slots default to the corresponding `@solana/kit`\n * implementations. Override any or all of them in tests to avoid real network\n * I/O.\n *\n * @public\n */\nexport interface WebsocketBasedTransactionForwarderDeps {\n /**\n * Optional custom Solana RPC client factory.\n *\n * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.\n *\n * @defaultValue `createSolanaRpc` from `@solana/kit`\n */\n readonly createRpc?: CreateSolanaRpcFunction;\n\n /**\n * Optional custom Solana RPC subscriptions client factory.\n *\n * If not provided, uses the default `createSolanaRpcSubscriptions` from\n * `@solana/kit`.\n *\n * @defaultValue `createSolanaRpcSubscriptions` from `@solana/kit`\n */\n readonly createRpcSubscriptions?: CreateSolanaRpcSubscriptionsFunction;\n\n /**\n * Optional custom `sendAndConfirmTransaction` factory.\n *\n * If not provided, uses the default `sendAndConfirmTransactionFactory` from\n * `@solana/kit`.\n *\n * @defaultValue `sendAndConfirmTransactionFactory` from `@solana/kit`\n */\n readonly sendAndConfirmTransactionFactory?: SendAndConfirmTransactionFactoryFunction;\n}\n\n/**\n * Common options shared by both forwarder strategies for a forwarding operation.\n *\n * @public\n */\nexport interface TransactionForwardOptions {\n /**\n * The commitment level to use for transaction confirmation.\n *\n * @remarks\n * - `\"processed\"` — confirmed by the local RPC node (weakest, fastest)\n * - `\"confirmed\"` — confirmed by a supermajority of the cluster (recommended)\n * - `\"finalized\"` — confirmed by a full supermajority and rooted (slowest, strongest)\n *\n * @defaultValue `\"confirmed\"`\n */\n readonly commitment?: Commitment;\n}\n\n/**\n * Configuration for the polling-based transaction forwarder.\n *\n * @public\n */\nexport interface PollingBasedTransactionForwarderConfig {\n /**\n * The HTTP or HTTPS URL of the Solana JSON-RPC endpoint.\n *\n * @example `\"https://api.mainnet-beta.solana.com\"`\n */\n readonly rpcUrl: string;\n}\n\n/**\n * Optional dependencies for the polling-based transaction forwarder.\n *\n * @remarks\n * Override `createRpc` in tests to inject a mock RPC client with controlled\n * responses.\n *\n * @public\n */\nexport interface PollingBasedTransactionForwarderDeps {\n /**\n * Optional custom Solana RPC client factory.\n *\n * If not provided, uses the default `createSolanaRpc` from `@solana/kit`.\n *\n * @defaultValue `createSolanaRpc` from `@solana/kit`\n */\n readonly createRpc?: CreateSolanaRpcFunction;\n}\n\n/**\n * Options specific to the polling-based transaction forwarder, extending the\n * base {@link TransactionForwardOptions}.\n *\n * @public\n */\nexport interface PollingTransactionForwardOptions extends TransactionForwardOptions {\n /**\n * Maximum time in milliseconds to wait for a transaction to reach the\n * desired commitment level before throwing a timeout error.\n *\n * @remarks\n * The polling loop checks elapsed time against this value. If `timeoutMs` is\n * exceeded, the forwarder throws `Error(\"Transaction confirmation timed out\n * after Xms\")`. The transaction may still land on-chain after the timeout —\n * callers should query `getSignatureStatus` if they need to determine the\n * final outcome.\n *\n * @defaultValue `60000` (60 seconds)\n */\n readonly timeoutMs?: number;\n\n /**\n * Time in milliseconds between successive `getSignatureStatuses` polling\n * attempts.\n *\n * @remarks\n * Lower values reduce confirmation latency at the cost of more RPC calls.\n * On mainnet, slots are ~400ms and blocks ~500ms, so polling more frequently\n * than every 500ms typically yields no benefit.\n *\n * @defaultValue `1000` (1 second)\n */\n readonly pollingIntervalMs?: number;\n\n /**\n * Maximum number of `sendTransaction` attempts before giving up.\n *\n * @remarks\n * Each retry is preceded by an exponential backoff delay:\n * - Attempt 1 → no delay before send\n * - Retry 1 → 1 second delay\n * - Retry 2 → 2 seconds delay\n * - ...\n *\n * Only `sendTransaction` failures trigger retries. Confirmation timeouts\n * are not retried — they throw immediately after `timeoutMs`.\n *\n * @defaultValue `3`\n */\n readonly maxRetries?: number;\n\n /**\n * Callback invoked after each transaction is confirmed in sequential mode.\n *\n * @remarks\n * Only called by `forwardSequentially` — not by `forwardInParallel`.\n * Useful for updating progress indicators in a UI while a multi-transaction\n * sequence is in flight.\n *\n * The callback is `await`-ed before the next transaction is submitted, so\n * async UI updates are safe.\n */\n readonly onTransactionConfirmed?: OnTransactionConfirmed;\n}\n\n/**\n * Metadata passed to the {@link OnTransactionConfirmed} callback after each\n * transaction is confirmed in a sequential forwarding sequence.\n *\n * @public\n */\nexport interface TransactionConfirmedInfo {\n /**\n * The base58-encoded signature of the confirmed transaction.\n */\n readonly signature: TransactionSignature;\n\n /**\n * The zero-based index of this transaction within the input array.\n *\n * @remarks\n * Use `index + 1` and `totalCount` to display `\"Transaction X of Y confirmed\"`.\n */\n readonly index: number;\n\n /**\n * The total number of transactions being forwarded in this sequence.\n */\n readonly totalCount: number;\n}\n\n/**\n * Async callback function type invoked after each transaction is confirmed\n * during sequential forwarding.\n *\n * @remarks\n * The callback is `await`-ed before the next transaction in the sequence is\n * submitted. This guarantees that UI updates or other async side effects\n * complete before the next round-trip begins.\n *\n * @param info - Metadata about the confirmed transaction.\n * @returns A `Promise<void>`. The returned promise is awaited before proceeding.\n *\n * @example\n * ```typescript\n * const onTransactionConfirmed: OnTransactionConfirmed = async ({ signature, index, totalCount }) => {\n * setProgress(Math.round(((index + 1) / totalCount) * 100));\n * console.log(`[${index + 1}/${totalCount}] confirmed: ${signature}`);\n * };\n * ```\n *\n * @see {@link PollingTransactionForwardOptions.onTransactionConfirmed}\n * @public\n */\nexport type OnTransactionConfirmed = (info: TransactionConfirmedInfo) => Promise<void>;\n\n/* =============================================================================\n * HELPER FUNCTIONS\n * ============================================================================= */\n\n/**\n * Asserts that a `SignedTransaction` carries the `messageBytes` field required\n * by `@solana/kit`'s internal `sendAndConfirmTransaction` function.\n *\n * @remarks\n * The Umbra `SignedTransaction` branded type wraps `Transaction &\n * TransactionWithBlockhashLifetime` from `@solana/kit`. The `sendAndConfirmTransaction`\n * function produced by `sendAndConfirmTransactionFactory` expects a slightly\n * different nominal type at its call site. This assertion acts as a runtime\n * compatibility guard, throwing early with a clear message rather than letting\n * the failure surface deeper inside `@solana/kit`.\n *\n * The assertion does NOT perform a deep schema check — it only verifies the\n * presence of `messageBytes`, which is the field most likely to be absent when\n * a caller accidentally passes a non-wire transaction.\n *\n * @typeParam T - The `@solana/kit` transaction type expected by the\n * `sendAndConfirmTransaction` call site, intersected with `SignedTransaction`.\n * @param transaction - The `SignedTransaction` to check.\n * @throws `Error` if `transaction.messageBytes` is `undefined`.\n * @internal\n */\n \nfunction assertKitCompatibleTransaction<T>(\n transaction: SignedTransaction,\n): asserts transaction is SignedTransaction & T {\n \n if (transaction.messageBytes === undefined) {\n throw new Error(\"Transaction is missing messageBytes — not a valid signed transaction\");\n }\n}\n\n/**\n * Converts a `Signature` value from `@solana/kit` into the Umbra SDK's branded\n * {@link TransactionSignature} type.\n *\n * @remarks\n * Both types are fundamentally base58-encoded strings, but they carry different\n * nominal brands. This function converts via `toString()` and then validates\n * the base58 invariants using {@link assertTransactionSignature}. The assertion\n * should never fail in practice because `@solana/kit` only produces valid\n * base58 signatures.\n *\n * @param signature - The `Signature` value from `@solana/kit`.\n * @returns The same string value branded as {@link TransactionSignature}.\n * @throws `SolanaAssertionError` if the string is somehow not valid base58\n * (should not occur under normal conditions).\n * @internal\n */\nfunction toTransactionSignature(signature: Signature): TransactionSignature {\n const sigString = signature.toString();\n assertTransactionSignature(sigString);\n return sigString;\n}\n\n/* =============================================================================\n * WEBSOCKET-BASED FORWARDER\n * ============================================================================= */\n\n/**\n * Creates a {@link TransactionForwarder} that uses WebSocket subscriptions for\n * transaction confirmation.\n *\n * The forwarder establishes an RPC client and a subscriptions client from the\n * provided URLs, then uses `@solana/kit`'s `sendAndConfirmTransactionFactory`\n * to produce a `sendAndConfirmTransaction` function. This function:\n *\n * 1. Serializes the transaction to base64 wire format using\n * `getBase64EncodedWireTransaction`.\n * 2. Submits it via `rpc.sendTransaction` to obtain the signature.\n * 3. Waits for confirmation by subscribing to the signature via WebSocket\n * through `sendAndConfirm`.\n *\n * @remarks\n * **WebSocket requirement** — This forwarder requires a WebSocket-capable RPC\n * endpoint. If the environment blocks WebSocket connections (e.g., some CI\n * runners, corporate proxies), use {@link getPollingTransactionForwarder}\n * instead.\n *\n * **Sequential behavior** — `forwardSequentially` submits and confirms\n * transactions one at a time in order. If any transaction fails, the method\n * rejects and subsequent transactions are not submitted.\n *\n * **Parallel behavior** — `forwardInParallel` maps all transactions through\n * `sendAndConfirmTransaction` and races them with `Promise.all`. Individual\n * failures propagate immediately through the `Promise.all` rejection.\n *\n * **Commitment** — Defaults to `\"confirmed\"`. Pass `{ commitment: \"finalized\" }`\n * in options for stronger guarantees at the cost of additional latency.\n *\n * **Error propagation** — RPC errors and confirmation failures are not caught\n * internally; they propagate as rejected promises.\n *\n * @param config - Configuration containing the HTTP RPC URL and the WebSocket\n * subscriptions URL.\n * @param deps - Optional dependency overrides for testing. All three slots\n * default to `@solana/kit` implementations.\n * @returns A {@link TransactionForwarder} with `forwardSequentially` and\n * `forwardInParallel` methods.\n *\n * @throws The returned methods may reject if the RPC endpoint is unreachable,\n * the WebSocket subscription cannot be established, or the transaction is\n * rejected on-chain.\n *\n * @example\n * Basic sequential forwarding:\n * ```typescript\n * import { getWebsocketTransactionForwarder } from \"./transaction-forwarder\";\n *\n * const forwarder = getWebsocketTransactionForwarder({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * rpcSubscriptionsUrl: \"wss://api.mainnet-beta.solana.com\",\n * });\n *\n * const [sig1, sig2] = await forwarder.forwardSequentially([initTx, depositTx], {\n * commitment: \"confirmed\",\n * });\n * console.log(\"Init:\", sig1);\n * console.log(\"Deposit:\", sig2);\n * ```\n *\n * @example\n * Parallel forwarding of independent transactions:\n * ```typescript\n * const signatures = await forwarder.forwardInParallel(transferTxs, {\n * commitment: \"confirmed\",\n * });\n * ```\n *\n * @example\n * Injecting mock dependencies in tests:\n * ```typescript\n * const forwarder = getWebsocketTransactionForwarder(\n * { rpcUrl: \"http://localhost:8899\", rpcSubscriptionsUrl: \"ws://localhost:8900\" },\n * {\n * createRpc: mockCreateRpc,\n * createRpcSubscriptions: mockCreateRpcSubscriptions,\n * sendAndConfirmTransactionFactory: mockSendAndConfirmFactory,\n * },\n * );\n * ```\n *\n * @see {@link getPollingTransactionForwarder} for the polling alternative\n * @see {@link TransactionForwarder} for the returned interface\n * @public\n */\nexport function getWebsocketTransactionForwarder(\n config: WebsocketBasedTransactionForwarderConfig,\n deps?: WebsocketBasedTransactionForwarderDeps,\n): TransactionForwarder {\n const { rpcUrl, rpcSubscriptionsUrl } = config;\n const {\n createRpc = defaultCreateSolanaRpc,\n createRpcSubscriptions = defaultCreateSolanaRpcSubscriptions,\n sendAndConfirmTransactionFactory = defaultSendAndConfirmTransactionFactory,\n } = deps ?? {};\n\n const rpc = createRpc(rpcUrl);\n const rpcSubscriptions = createRpcSubscriptions(rpcSubscriptionsUrl);\n\n // Create the sendAndConfirm function using websocket-based confirmation\n const sendAndConfirm = sendAndConfirmTransactionFactory({\n rpc,\n rpcSubscriptions,\n });\n\n /**\n * Serializes, submits, and confirms a single transaction using the\n * WebSocket-based `sendAndConfirm` function.\n *\n * @remarks\n * Two separate steps are performed:\n * 1. `rpc.sendTransaction` — submits the base64-encoded wire transaction to\n * get the signature back immediately.\n * 2. `sendAndConfirm` — waits for the signature to reach the requested\n * commitment level via WebSocket subscription.\n *\n * This two-step approach ensures the signature is available synchronously\n * after submission (step 1) while confirmation is awaited asynchronously\n * (step 2), which allows the signature to be returned even if the WebSocket\n * subscription fails after submission.\n *\n * @param transaction - The fully signed transaction to submit.\n * @param options - Forwarding options, in particular the commitment level.\n * @returns The base58-encoded {@link TransactionSignature}.\n * @internal\n */\n async function sendAndConfirmTransaction(\n transaction: SignedTransaction,\n options: TransactionForwardOptions,\n ): Promise<TransactionSignature> {\n const commitment = options.commitment ?? \"confirmed\";\n\n // Send and confirm in a single call — sendAndConfirm handles both\n // submission and websocket-based confirmation internally.\n assertKitCompatibleTransaction<Parameters<typeof sendAndConfirm>[0]>(transaction);\n await sendAndConfirm(transaction, {\n commitment,\n });\n\n // Extract signature from the signed transaction\n const signature = getSignatureFromTransaction(transaction) as unknown as Signature;\n return toTransactionSignature(signature);\n }\n\n return {\n /**\n * Forwards transactions one at a time, waiting for each to reach the\n * requested commitment level before submitting the next.\n *\n * @param transactions - Ordered array of signed transactions.\n * @param options - Forward options (commitment). Defaults to `\"confirmed\"`.\n * @returns Array of confirmed signatures in input order.\n */\n forwardSequentially: async (\n transactions: readonly SignedTransaction[],\n options: TransactionForwardOptions = {},\n ): Promise<readonly TransactionSignature[]> => {\n const signatures: TransactionSignature[] = [];\n\n for (const transaction of transactions) {\n const signature = await sendAndConfirmTransaction(transaction, options);\n signatures.push(signature);\n }\n\n return signatures;\n },\n\n /**\n * Forwards all transactions simultaneously via `Promise.all`.\n *\n * Each transaction is passed to `sendAndConfirmTransaction` concurrently.\n * Results are returned in input order regardless of confirmation order.\n *\n * @param transactions - Array of signed transactions to submit in parallel.\n * @param options - Forward options (commitment). Defaults to `\"confirmed\"`.\n * @returns Array of confirmed signatures in input order.\n */\n forwardInParallel: async (\n transactions: readonly SignedTransaction[],\n options: TransactionForwardOptions = {},\n ): Promise<readonly TransactionSignature[]> => {\n const signaturePromises = transactions.map(async (tx) =>\n sendAndConfirmTransaction(tx, options),\n );\n return Promise.all(signaturePromises);\n },\n\n fireAndForget: async (transaction: SignedTransaction): Promise<TransactionSignature> => {\n const wireTransaction = getBase64EncodedWireTransaction(transaction);\n const response = await rpc\n .sendTransaction(wireTransaction, {\n encoding: \"base64\",\n skipPreflight: true,\n })\n .send();\n return toTransactionSignature(response);\n },\n };\n}\n\n/* =============================================================================\n * POLLING-BASED FORWARDER\n * ============================================================================= */\n\n/**\n * Creates a {@link TransactionForwarder} that uses periodic polling of\n * `getSignatureStatuses` for transaction confirmation.\n *\n * The polling forwarder works as follows:\n * 1. Serialize the transaction and submit via `rpc.sendTransaction`.\n * 2. Begin polling `rpc.getSignatureStatuses` at `pollingIntervalMs` intervals.\n * 3. Compare `confirmationStatus` against the requested commitment level.\n * 4. Resolve when the status matches, or throw after `timeoutMs` elapses.\n *\n * Send failures are retried up to `maxRetries` times with exponential backoff.\n *\n * @remarks\n * **Environment compatibility** — This forwarder requires only HTTP/HTTPS\n * access and is suitable for environments that block WebSocket connections.\n *\n * **Commitment mapping** — The confirmation check maps `confirmationStatus`\n * from the RPC response as follows:\n * - `\"processed\"` commitment: any non-null `confirmationStatus` satisfies it.\n * - `\"confirmed\"` commitment: `confirmationStatus` must be `\"confirmed\"` or\n * `\"finalized\"`.\n * - `\"finalized\"` commitment: `confirmationStatus` must be `\"finalized\"`.\n *\n * **On-chain errors** — If `status.err` is non-null, the forwarder immediately\n * throws `Error(\"Transaction failed: <err JSON>\")`. This distinguishes an\n * on-chain execution failure from a network/timeout failure.\n *\n * **Sequential callback** — The `onTransactionConfirmed` callback in\n * {@link PollingTransactionForwardOptions} is invoked (and awaited) after each\n * transaction in a `forwardSequentially` call. It is NOT invoked during\n * `forwardInParallel`.\n *\n * **Parallel behavior** — `forwardInParallel` first sends all transactions\n * (with retries), then waits for all confirmations concurrently via\n * `Promise.all`.\n *\n * **Retry backoff** — Send retries use linear backoff: attempt N waits\n * `N * 1000ms` before retrying (attempt 1: 1s, attempt 2: 2s, etc.).\n *\n * @param config - Configuration containing the HTTP RPC endpoint URL.\n * @param deps - Optional dependency overrides for testing.\n * @returns A {@link TransactionForwarder} with `forwardSequentially` and\n * `forwardInParallel` methods. Both methods accept\n * {@link PollingTransactionForwardOptions} for polling-specific settings.\n *\n * @throws The returned methods may reject if:\n * - The transaction cannot be sent after `maxRetries` attempts.\n * - The confirmation timeout (`timeoutMs`) is exceeded.\n * - The transaction is rejected on-chain (`status.err !== null`).\n *\n * @example\n * Basic usage with progress callback:\n * ```typescript\n * import { getPollingTransactionForwarder } from \"./transaction-forwarder\";\n *\n * const forwarder = getPollingTransactionForwarder({\n * rpcUrl: \"https://api.mainnet-beta.solana.com\",\n * });\n *\n * const signatures = await forwarder.forwardSequentially(transactions, {\n * commitment: \"confirmed\",\n * timeoutMs: 30_000,\n * pollingIntervalMs: 500,\n * maxRetries: 5,\n * onTransactionConfirmed: async ({ signature, index, totalCount }) => {\n * console.log(`[${index + 1}/${totalCount}] confirmed: ${signature}`);\n * },\n * });\n * ```\n *\n * @example\n * Parallel forwarding without a callback:\n * ```typescript\n * const signatures = await forwarder.forwardInParallel(transferTxs, {\n * commitment: \"confirmed\",\n * timeoutMs: 60_000,\n * });\n * ```\n *\n * @example\n * Injecting a mock RPC for tests:\n * ```typescript\n * const forwarder = getPollingTransactionForwarder(\n * { rpcUrl: \"http://localhost:8899\" },\n * { createRpc: mockCreateRpc },\n * );\n * ```\n *\n * @see {@link getWebsocketTransactionForwarder} for the WebSocket alternative\n * @see {@link TransactionForwarder} for the returned interface\n * @see {@link PollingTransactionForwardOptions} for the full options reference\n * @public\n */\nexport function getPollingTransactionForwarder(\n config: PollingBasedTransactionForwarderConfig,\n deps?: PollingBasedTransactionForwarderDeps,\n): TransactionForwarder {\n const { rpcUrl } = config;\n const { createRpc = defaultCreateSolanaRpc } = deps ?? {};\n const rpc = createRpc(rpcUrl);\n\n /**\n * Serializes and submits a single transaction to the RPC node without\n * waiting for confirmation.\n *\n * @remarks\n * The transaction is base64-encoded using `getBase64EncodedWireTransaction`\n * and submitted with `preflightCommitment` set to the requested commitment\n * level. Preflight simulation runs at the same commitment level to catch\n * obvious failures before they reach the network.\n *\n * @param transaction - The fully signed transaction to submit.\n * @param commitment - The preflight commitment level.\n * @returns The base58-encoded {@link TransactionSignature} returned by the node.\n * @internal\n */\n async function sendTransactionOnly(\n transaction: SignedTransaction,\n commitment: Commitment,\n ): Promise<TransactionSignature> {\n const wireTransaction = getBase64EncodedWireTransaction(transaction);\n\n const response = await rpc\n .sendTransaction(wireTransaction, {\n encoding: \"base64\",\n preflightCommitment: commitment,\n })\n .send();\n\n return toTransactionSignature(response);\n }\n\n /**\n * Polls `getSignatureStatuses` until the transaction reaches the specified\n * commitment level, or until `timeoutMs` milliseconds elapse.\n *\n * @remarks\n * **Commitment mapping:**\n * - `\"processed\"` — satisfied by any non-null `confirmationStatus`.\n * - `\"confirmed\"` — satisfied by `\"confirmed\"` or `\"finalized\"`.\n * - `\"finalized\"` — satisfied only by `\"finalized\"`.\n *\n * The poll uses `searchTransactionHistory: false` to query only recent\n * signature history. Transactions that have fallen out of recent history\n * (very old) will time out rather than resolve.\n *\n * On-chain failures (non-null `status.err`) throw immediately without\n * waiting for the timeout.\n *\n * @param signature - The base58-encoded transaction signature to poll.\n * @param commitment - The target commitment level.\n * @param timeoutMs - Maximum polling duration in milliseconds.\n * @param pollingIntervalMs - Sleep duration between polling attempts.\n * @returns A `Promise<void>` that resolves when confirmed or rejects on error/timeout.\n * @internal\n */\n async function waitForConfirmation(\n signature: TransactionSignature,\n commitment: Commitment,\n timeoutMs: number,\n pollingIntervalMs: number,\n ): Promise<void> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n const response = await rpc\n .getSignatureStatuses([toKitSignature(signature)], {\n searchTransactionHistory: false,\n })\n .send();\n\n const status = response.value[0];\n\n if (status !== null) {\n // Check if the transaction has reached the desired commitment\n const confirmedCommitments: Commitment[] = [\"confirmed\", \"finalized\"];\n const finalizedCommitments: Commitment[] = [\"finalized\"];\n\n const confirmationStatus = status.confirmationStatus;\n\n if (commitment === \"processed\" && confirmationStatus !== null) {\n return;\n }\n\n if (\n commitment === \"confirmed\" &&\n confirmationStatus !== null &&\n confirmedCommitments.includes(confirmationStatus)\n ) {\n return;\n }\n\n if (\n commitment === \"finalized\" &&\n confirmationStatus !== null &&\n finalizedCommitments.includes(confirmationStatus)\n ) {\n return;\n }\n\n // Check for errors\n if (status.err !== null) {\n throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);\n }\n }\n\n await sleep(pollingIntervalMs);\n }\n\n throw new Error(`Transaction confirmation timed out after ${String(timeoutMs)}ms`);\n }\n\n /**\n * Submits a transaction with up to `maxRetries` attempts, using linear\n * backoff between attempts.\n *\n * @remarks\n * On each failed attempt (except the last), the forwarder sleeps for\n * `attempt * 1000ms` before retrying, where `attempt` is the 0-based attempt\n * index (so: 1s, 2s, 3s, ...). If all attempts fail, the last error is\n * re-thrown.\n *\n * @param transaction - The signed transaction to send.\n * @param commitment - The preflight commitment level for `sendTransaction`.\n * @param maxRetries - Total number of attempts (minimum 1).\n * @returns The {@link TransactionSignature} returned by the first successful send.\n * @internal\n */\n async function sendWithRetries(\n transaction: SignedTransaction,\n commitment: Commitment,\n maxRetries: number,\n ): Promise<TransactionSignature> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await sendTransactionOnly(transaction, commitment);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt < maxRetries - 1) {\n await sleep(1000 * (attempt + 1)); // Exponential backoff\n }\n }\n }\n\n throw lastError ?? new Error(\"Failed to send transaction after retries\");\n }\n\n return {\n /**\n * Forwards transactions sequentially using polling-based confirmation.\n *\n * @remarks\n * For each transaction in order:\n * 1. Sends with retries (up to `maxRetries`).\n * 2. Polls until confirmed (up to `timeoutMs`).\n * 3. Invokes `onTransactionConfirmed` if provided (awaited).\n *\n * Aborts on first failure without submitting subsequent transactions.\n *\n * @param transactions - Ordered array of signed transactions.\n * @param options - Polling-specific options. All fields have defaults.\n * @returns Array of confirmed signatures in input order.\n */\n forwardSequentially: async (\n transactions: readonly SignedTransaction[],\n options: PollingTransactionForwardOptions = {},\n ): Promise<readonly TransactionSignature[]> => {\n const {\n commitment = \"confirmed\",\n timeoutMs = 60_000,\n pollingIntervalMs = 1000,\n maxRetries = 3,\n onTransactionConfirmed,\n } = options;\n\n const signatures: TransactionSignature[] = [];\n const totalCount = transactions.length;\n\n for (const [index, transaction] of transactions.entries()) {\n // Send transaction with retries\n const signature = await sendWithRetries(transaction, commitment, maxRetries);\n\n // Wait for confirmation\n await waitForConfirmation(signature, commitment, timeoutMs, pollingIntervalMs);\n\n signatures.push(signature);\n\n // Call the callback if provided\n if (onTransactionConfirmed !== undefined) {\n await onTransactionConfirmed({\n signature,\n index,\n totalCount,\n });\n }\n }\n\n return signatures;\n },\n\n /**\n * Forwards all transactions in parallel using polling-based confirmation.\n *\n * @remarks\n * All transactions are sent concurrently (with retries), then all\n * confirmations are awaited concurrently. The `onTransactionConfirmed`\n * callback is NOT invoked during parallel forwarding.\n *\n * @param transactions - Array of signed transactions to submit in parallel.\n * @param options - Polling-specific options. All fields have defaults.\n * @returns Array of confirmed signatures in input order.\n */\n forwardInParallel: async (\n transactions: readonly SignedTransaction[],\n options: PollingTransactionForwardOptions = {},\n ): Promise<readonly TransactionSignature[]> => {\n const {\n commitment = \"confirmed\",\n timeoutMs = 60_000,\n pollingIntervalMs = 1000,\n maxRetries = 3,\n } = options;\n\n // Send all transactions in parallel\n const sendPromises = transactions.map(async (tx) =>\n sendWithRetries(tx, commitment, maxRetries),\n );\n const signatures = await Promise.all(sendPromises);\n\n // Wait for all confirmations in parallel\n const confirmationPromises = signatures.map(async (sig) =>\n waitForConfirmation(sig, commitment, timeoutMs, pollingIntervalMs),\n );\n await Promise.all(confirmationPromises);\n\n return signatures;\n },\n\n fireAndForget: async (transaction: SignedTransaction): Promise<TransactionSignature> => {\n return sendTransactionOnly(transaction, \"confirmed\");\n },\n };\n}\n","/**\n * Address Lookup Table utilities for transaction compression.\n *\n * Address Lookup Tables (ALTs) are Solana on-chain accounts that store a list of\n * public keys. When building versioned (v0) transactions, ALTs allow instructions\n * to reference accounts by their index in the table rather than including the full\n * 32-byte public key inline. This reduces transaction size and allows a single\n * transaction to reference more accounts than the standard per-transaction limit.\n *\n * This module provides helpers for converting the SDK's internal {@link AltEntry}\n * representation into the record format that `@solana/kit`'s\n * `compressTransactionMessageUsingAddressLookupTables` expects.\n *\n * @remarks\n * ALTs must be pre-loaded on-chain before they can be used in transactions. The\n * Umbra SDK stores pre-baked ALT addresses and their contents in {@link NetworkConfig}\n * so clients do not need to fetch ALT account data at runtime — the addresses are\n * already known at build time.\n *\n * @see {@link AltEntry} for the SDK's internal ALT representation\n * @see {@link buildAltAddressesRecord} for converting an ALT entry to the kit-compatible format\n *\n * @packageDocumentation\n */\n\nimport type { Address } from \"@solana/kit\";\nimport type { AltEntry } from \"../constants/networks\";\n\n/**\n * Converts an {@link AltEntry} into the record format expected by\n * `compressTransactionMessageUsingAddressLookupTables`.\n *\n * The `@solana/kit` function `compressTransactionMessageUsingAddressLookupTables`\n * accepts a `Record<Address, Address[]>` where each key is an ALT account address\n * and each value is the ordered list of addresses stored within that table. This\n * function performs that conversion from the SDK's {@link AltEntry} shape.\n *\n * @remarks\n * The returned record is a shallow copy of the addresses array — mutations to the\n * original {@link AltEntry} after this call will not affect the returned record.\n *\n * When a transaction message is compressed using this record, `@solana/kit` will\n * replace any account references that match entries in the ALT with the\n * corresponding table index, shrinking the serialized transaction size.\n *\n * @param altEntry - The pre-baked ALT entry sourced from {@link NetworkConfig}.\n * Contains the on-chain ALT address and the ordered list of addresses it holds.\n * @returns A `Record` mapping the ALT account address to the ordered array of\n * addresses stored in the table. This format is consumed directly by\n * `compressTransactionMessageUsingAddressLookupTables`.\n *\n * @example\n * ```typescript\n * import { buildAltAddressesRecord } from \"./alt-utils\";\n * import { compressTransactionMessageUsingAddressLookupTables } from \"@solana/kit\";\n * import { MAINNET_CONFIG } from \"../constants/networks\";\n *\n * const altRecord = buildAltAddressesRecord(MAINNET_CONFIG.alt);\n *\n * const compressedMessage = compressTransactionMessageUsingAddressLookupTables(\n * transactionMessage,\n * altRecord,\n * );\n * ```\n *\n * @example\n * Combining multiple ALT entries into one record:\n * ```typescript\n * import { buildAltAddressesRecord } from \"./alt-utils\";\n *\n * const combinedAltRecord = {\n * ...buildAltAddressesRecord(MAINNET_CONFIG.umbraAlt),\n * ...buildAltAddressesRecord(MAINNET_CONFIG.arciumAlt),\n * };\n * ```\n *\n * @see {@link AltEntry} for the input type definition\n * @public\n */\nexport function buildAltAddressesRecord(altEntry: AltEntry): Record<Address, Address[]> {\n return { [altEntry.altAddress]: [...altEntry.addresses] };\n}\n\n/**\n * Looks up an ALT entry from the nested address lookup tables structure.\n *\n * The lookup tables are keyed by cluster offset (number), then by instruction\n * name (string). Returns `undefined` if no ALT exists for the given cluster\n * offset or instruction name.\n *\n * @param addressLookupTables - The nested ALT structure from {@link NetworkConfig}.\n * @param clusterOffset - The Arcium cluster offset (fetched from the on-chain MXE account).\n * @param instructionName - The snake_case instruction name.\n * @returns The matching {@link AltEntry}, or `undefined` if not found.\n *\n * @public\n */\nexport function lookupAltEntry(\n addressLookupTables: Readonly<Record<number, Readonly<Record<string, AltEntry>>>>,\n clusterOffset: number,\n instructionName: string,\n): AltEntry | undefined {\n return addressLookupTables[clusterOffset]?.[instructionName];\n}\n"]}
|