@utxopia/sdk 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/package.json +108 -0
  4. package/packages/btc-client/src/esplora-client.ts +153 -0
  5. package/packages/btc-client/src/index.ts +3 -0
  6. package/packages/btc-client/src/op-return.ts +93 -0
  7. package/packages/btc-client/src/types.ts +112 -0
  8. package/packages/sdk/README.md +277 -0
  9. package/packages/sdk/dist/announcement-client.d.ts +64 -0
  10. package/packages/sdk/dist/announcement-client.js +337 -0
  11. package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
  12. package/packages/sdk/dist/auditor-ciphertext.js +135 -0
  13. package/packages/sdk/dist/auditor.d.ts +115 -0
  14. package/packages/sdk/dist/auditor.js +292 -0
  15. package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
  16. package/packages/sdk/dist/bitcoin/ika.js +74 -0
  17. package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
  18. package/packages/sdk/dist/bitcoin/index.js +1 -0
  19. package/packages/sdk/dist/bound-params.d.ts +96 -0
  20. package/packages/sdk/dist/bound-params.js +249 -0
  21. package/packages/sdk/dist/chadbuffer.d.ts +124 -0
  22. package/packages/sdk/dist/chadbuffer.js +405 -0
  23. package/packages/sdk/dist/claim-link.d.ts +30 -0
  24. package/packages/sdk/dist/claim-link.js +50 -0
  25. package/packages/sdk/dist/client.d.ts +233 -0
  26. package/packages/sdk/dist/client.js +416 -0
  27. package/packages/sdk/dist/commitment-tree.d.ts +268 -0
  28. package/packages/sdk/dist/commitment-tree.js +564 -0
  29. package/packages/sdk/dist/config.d.ts +177 -0
  30. package/packages/sdk/dist/config.js +426 -0
  31. package/packages/sdk/dist/core/esplora.d.ts +110 -0
  32. package/packages/sdk/dist/core/esplora.js +187 -0
  33. package/packages/sdk/dist/core/mempool.d.ts +60 -0
  34. package/packages/sdk/dist/core/mempool.js +107 -0
  35. package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
  36. package/packages/sdk/dist/crypto-babyjub.js +312 -0
  37. package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
  38. package/packages/sdk/dist/crypto-ed25519.js +235 -0
  39. package/packages/sdk/dist/crypto.d.ts +59 -0
  40. package/packages/sdk/dist/crypto.js +140 -0
  41. package/packages/sdk/dist/event-client.d.ts +64 -0
  42. package/packages/sdk/dist/event-client.js +165 -0
  43. package/packages/sdk/dist/events.d.ts +127 -0
  44. package/packages/sdk/dist/events.js +305 -0
  45. package/packages/sdk/dist/explorer.d.ts +86 -0
  46. package/packages/sdk/dist/explorer.js +197 -0
  47. package/packages/sdk/dist/index.d.ts +61 -0
  48. package/packages/sdk/dist/index.js +216 -0
  49. package/packages/sdk/dist/instructions.d.ts +1125 -0
  50. package/packages/sdk/dist/instructions.js +1760 -0
  51. package/packages/sdk/dist/keys.d.ts +467 -0
  52. package/packages/sdk/dist/keys.js +799 -0
  53. package/packages/sdk/dist/logger.d.ts +10 -0
  54. package/packages/sdk/dist/logger.js +40 -0
  55. package/packages/sdk/dist/magicblock.d.ts +90 -0
  56. package/packages/sdk/dist/magicblock.js +164 -0
  57. package/packages/sdk/dist/merkle.d.ts +82 -0
  58. package/packages/sdk/dist/merkle.js +141 -0
  59. package/packages/sdk/dist/note.d.ts +348 -0
  60. package/packages/sdk/dist/note.js +483 -0
  61. package/packages/sdk/dist/pda.d.ts +160 -0
  62. package/packages/sdk/dist/pda.js +384 -0
  63. package/packages/sdk/dist/pool-state.d.ts +100 -0
  64. package/packages/sdk/dist/pool-state.js +126 -0
  65. package/packages/sdk/dist/poseidon.d.ts +53 -0
  66. package/packages/sdk/dist/poseidon.js +136 -0
  67. package/packages/sdk/dist/prover/index.d.ts +10 -0
  68. package/packages/sdk/dist/prover/index.js +10 -0
  69. package/packages/sdk/dist/prover/mobile.d.ts +45 -0
  70. package/packages/sdk/dist/prover/mobile.js +227 -0
  71. package/packages/sdk/dist/prover/web.d.ts +136 -0
  72. package/packages/sdk/dist/prover/web.js +597 -0
  73. package/packages/sdk/dist/psbt.d.ts +73 -0
  74. package/packages/sdk/dist/psbt.js +202 -0
  75. package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
  76. package/packages/sdk/dist/selective-disclosure.js +172 -0
  77. package/packages/sdk/dist/sender-memo.d.ts +149 -0
  78. package/packages/sdk/dist/sender-memo.js +250 -0
  79. package/packages/sdk/dist/sns-resolver.d.ts +121 -0
  80. package/packages/sdk/dist/sns-resolver.js +229 -0
  81. package/packages/sdk/dist/solana/connection.d.ts +77 -0
  82. package/packages/sdk/dist/solana/connection.js +133 -0
  83. package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
  84. package/packages/sdk/dist/solana/priority-fee.js +137 -0
  85. package/packages/sdk/dist/stealth.d.ts +429 -0
  86. package/packages/sdk/dist/stealth.js +703 -0
  87. package/packages/sdk/dist/taproot.d.ts +172 -0
  88. package/packages/sdk/dist/taproot.js +494 -0
  89. package/packages/sdk/dist/token-registry.d.ts +96 -0
  90. package/packages/sdk/dist/token-registry.js +122 -0
  91. package/packages/sdk/dist/utils/encoding.d.ts +19 -0
  92. package/packages/sdk/dist/utils/encoding.js +29 -0
  93. package/packages/sdk/dist/vk-registry.d.ts +100 -0
  94. package/packages/sdk/dist/vk-registry.js +212 -0
@@ -0,0 +1,405 @@
1
+ /**
2
+ * ChadBuffer Client
3
+ *
4
+ * Helper functions to upload Bitcoin transaction data to ChadBuffer
5
+ * for SPV verification on Solana.
6
+ *
7
+ * Networks: Bitcoin Testnet4, Solana Devnet
8
+ *
9
+ * Reference: https://github.com/deanmlittle/chadbuffer
10
+ */
11
+ import { fromBase64 } from "./utils/encoding";
12
+ import { hexToBytes } from "./crypto";
13
+ import { generateKeyPairSigner, createKeyPairSignerFromBytes, pipe, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction, signTransactionMessageWithSigners, getSignatureFromTransaction, sendAndConfirmTransactionFactory, AccountRole, } from "@solana/kit";
14
+ import { getCreateAccountInstruction } from "@solana-program/system";
15
+ import { address } from "./config";
16
+ import { debug } from "./logger";
17
+ // ChadBuffer Program ID (deployed to devnet 2025-01-30)
18
+ export const CHADBUFFER_PROGRAM_ID = address("C5RpjtTMFXKVZCtXSzKXD4CDNTaWBg3dVeMfYvjZYHDF");
19
+ // System Program ID
20
+ const SYSTEM_PROGRAM_ID = address("11111111111111111111111111111111");
21
+ // Buffer authority size (32 bytes)
22
+ export const AUTHORITY_SIZE = 32;
23
+ // Solana transaction limit
24
+ export const SOLANA_TX_SIZE_LIMIT = 1232;
25
+ /**
26
+ * ChadBuffer Write TX overhead breakdown:
27
+ * - Signature: 64 bytes
28
+ * - Message header: 3 bytes
29
+ * - 2 account metas (payer + buffer): 66 bytes (33 each)
30
+ * - Instruction header: 4 bytes
31
+ * - Discriminator: 1 byte
32
+ * - u24 offset: 3 bytes
33
+ * Total fixed overhead: ~141 bytes (using 176 for safety margin)
34
+ */
35
+ const WRITE_TX_OVERHEAD = 176;
36
+ /**
37
+ * Maximum data bytes per ChadBuffer Write transaction
38
+ * Dynamically calculated from TX limit minus overhead
39
+ */
40
+ export const MAX_DATA_PER_WRITE = SOLANA_TX_SIZE_LIMIT - WRITE_TX_OVERHEAD;
41
+ /**
42
+ * ChadBuffer instruction discriminators
43
+ * See: https://github.com/deanmlittle/chadbuffer/blob/main/src/lib.rs
44
+ */
45
+ var ChadBufferInstruction;
46
+ (function (ChadBufferInstruction) {
47
+ ChadBufferInstruction[ChadBufferInstruction["Create"] = 0] = "Create";
48
+ ChadBufferInstruction[ChadBufferInstruction["Assign"] = 1] = "Assign";
49
+ ChadBufferInstruction[ChadBufferInstruction["Write"] = 2] = "Write";
50
+ ChadBufferInstruction[ChadBufferInstruction["Close"] = 3] = "Close";
51
+ })(ChadBufferInstruction || (ChadBufferInstruction = {}));
52
+ /**
53
+ * Create instruction data for ChadBuffer Init (discriminator 0)
54
+ * Format: discriminator(1) + data
55
+ */
56
+ function createInitInstructionData(data) {
57
+ const buffer = new Uint8Array(1 + data.length);
58
+ buffer[0] = ChadBufferInstruction.Create;
59
+ buffer.set(data, 1);
60
+ return buffer;
61
+ }
62
+ /**
63
+ * Create instruction data for ChadBuffer Write (discriminator 2)
64
+ * Format: discriminator(1) + u24_offset(3) + data
65
+ */
66
+ function createWriteInstructionData(offset, data) {
67
+ const buffer = new Uint8Array(1 + 3 + data.length);
68
+ buffer[0] = ChadBufferInstruction.Write;
69
+ // u24 offset (little-endian)
70
+ buffer[1] = offset & 0xff;
71
+ buffer[2] = (offset >> 8) & 0xff;
72
+ buffer[3] = (offset >> 16) & 0xff;
73
+ buffer.set(data, 4);
74
+ return buffer;
75
+ }
76
+ /**
77
+ * Create instruction data for ChadBuffer Close (discriminator 3)
78
+ * Format: discriminator(1) only
79
+ */
80
+ function createCloseInstructionData() {
81
+ return new Uint8Array([ChadBufferInstruction.Close]);
82
+ }
83
+ /**
84
+ * Upload raw Bitcoin transaction to ChadBuffer
85
+ *
86
+ * @param rpc - Solana RPC client
87
+ * @param rpcSubscriptions - Solana RPC subscriptions client
88
+ * @param payer - Transaction fee payer (KeyPairSigner)
89
+ * @param rawTx - Raw Bitcoin transaction bytes
90
+ * @param seed - Optional seed for buffer keypair derivation
91
+ * @returns Buffer address
92
+ */
93
+ export async function uploadTransactionToBuffer(rpc, rpcSubscriptions, payer, rawTx, seed) {
94
+ // Generate buffer keypair
95
+ const bufferKeypair = seed
96
+ ? await createKeyPairSignerFromBytes(seed.slice(0, 64).length === 64 ? seed.slice(0, 64) : padTo64Bytes(seed.slice(0, 32)))
97
+ : await generateKeyPairSigner();
98
+ // Calculate space: authority (32) + data
99
+ const space = AUTHORITY_SIZE + rawTx.length;
100
+ const rentExemption = await rpc.getMinimumBalanceForRentExemption(BigInt(space)).send();
101
+ // Split data into chunks using dynamic MAX_DATA_PER_WRITE
102
+ const chunks = splitIntoChunks(rawTx, MAX_DATA_PER_WRITE);
103
+ const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
104
+ // TX 1: CreateAccount only (no data yet)
105
+ const createAccountIx = getCreateAccountInstruction({
106
+ payer,
107
+ newAccount: bufferKeypair,
108
+ lamports: rentExemption,
109
+ space: BigInt(space),
110
+ programAddress: CHADBUFFER_PROGRAM_ID,
111
+ });
112
+ const { value: blockhash1 } = await rpc.getLatestBlockhash().send();
113
+ const tx1 = pipe(createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(payer.address, msg), (msg) => setTransactionMessageLifetimeUsingBlockhash(blockhash1, msg), (msg) => appendTransactionMessageInstruction(createAccountIx, msg));
114
+ await sendAndConfirm(await signTransactionMessageWithSigners(tx1), { commitment: "confirmed" });
115
+ // TX 2: ChadBuffer Init with first chunk
116
+ const initIx = {
117
+ programAddress: CHADBUFFER_PROGRAM_ID,
118
+ accounts: [
119
+ { address: payer.address, role: AccountRole.WRITABLE_SIGNER, signer: payer },
120
+ { address: bufferKeypair.address, role: AccountRole.WRITABLE },
121
+ ],
122
+ data: createInitInstructionData(chunks[0]),
123
+ };
124
+ const { value: blockhash2 } = await rpc.getLatestBlockhash().send();
125
+ const tx2 = pipe(createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(payer.address, msg), (msg) => setTransactionMessageLifetimeUsingBlockhash(blockhash2, msg), (msg) => appendTransactionMessageInstruction(initIx, msg));
126
+ await sendAndConfirm(await signTransactionMessageWithSigners(tx2), { commitment: "confirmed" });
127
+ // TX 3+: Write remaining chunks
128
+ let offset = chunks[0].length;
129
+ for (let i = 1; i < chunks.length; i++) {
130
+ const writeIx = {
131
+ programAddress: CHADBUFFER_PROGRAM_ID,
132
+ accounts: [
133
+ { address: payer.address, role: AccountRole.WRITABLE_SIGNER, signer: payer },
134
+ { address: bufferKeypair.address, role: AccountRole.WRITABLE },
135
+ ],
136
+ data: createWriteInstructionData(offset, chunks[i]),
137
+ };
138
+ offset += chunks[i].length;
139
+ const { value: blockhash } = await rpc.getLatestBlockhash().send();
140
+ const tx = pipe(createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(payer.address, msg), (msg) => setTransactionMessageLifetimeUsingBlockhash(blockhash, msg), (msg) => appendTransactionMessageInstruction(writeIx, msg));
141
+ await sendAndConfirm(await signTransactionMessageWithSigners(tx), { commitment: "confirmed" });
142
+ }
143
+ debug("chadbuffer", `Buffer created, tx ${rawTx.length}B, ${chunks.length} chunks`);
144
+ return bufferKeypair.address;
145
+ }
146
+ /**
147
+ * Helper to pad a 32-byte seed to 64 bytes for createKeyPairSignerFromBytes
148
+ */
149
+ function padTo64Bytes(seed32) {
150
+ const padded = new Uint8Array(64);
151
+ padded.set(seed32, 0);
152
+ return padded;
153
+ }
154
+ /**
155
+ * Close buffer and reclaim rent
156
+ */
157
+ export async function closeBuffer(rpc, rpcSubscriptions, payer, bufferAddress, recipient) {
158
+ // ChadBuffer Close instruction (expects exactly 2 accounts: signer + buffer)
159
+ // Lamports go back to signer automatically
160
+ const closeIx = {
161
+ programAddress: CHADBUFFER_PROGRAM_ID,
162
+ accounts: [
163
+ { address: payer.address, role: AccountRole.WRITABLE_SIGNER, signer: payer },
164
+ { address: bufferAddress, role: AccountRole.WRITABLE },
165
+ ],
166
+ data: createCloseInstructionData(),
167
+ };
168
+ // Get blockhash
169
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
170
+ // Build transaction message
171
+ const txMessage = pipe(createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(payer.address, msg), (msg) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, msg), (msg) => appendTransactionMessageInstruction(closeIx, msg));
172
+ const signedTx = await signTransactionMessageWithSigners(txMessage);
173
+ // Send and confirm
174
+ const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
175
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
176
+ await sendAndConfirm(signedTx, { commitment: "confirmed" });
177
+ // Return the signature as string
178
+ return getSignatureFromTransaction(signedTx);
179
+ }
180
+ /**
181
+ * Read buffer data
182
+ */
183
+ export async function readBufferData(rpc, bufferAddress) {
184
+ const accountInfo = await rpc.getAccountInfo(bufferAddress, { encoding: "base64" }).send();
185
+ if (!accountInfo.value) {
186
+ throw new Error("Buffer account not found");
187
+ }
188
+ // Decode base64 data
189
+ const rawData = fromBase64(accountInfo.value.data[0]);
190
+ const authorityBytes = rawData.slice(0, AUTHORITY_SIZE);
191
+ const data = new Uint8Array(rawData.slice(AUTHORITY_SIZE));
192
+ // Convert authority bytes to Address
193
+ const authority = address(bs58Encode(authorityBytes));
194
+ return { authority, data };
195
+ }
196
+ /**
197
+ * Simple base58 encoding for addresses
198
+ */
199
+ function bs58Encode(bytes) {
200
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
201
+ const byteArray = bytes;
202
+ let num = BigInt(0);
203
+ for (let i = 0; i < byteArray.length; i++) {
204
+ num = num * BigInt(256) + BigInt(byteArray[i]);
205
+ }
206
+ let result = "";
207
+ while (num > BigInt(0)) {
208
+ result = ALPHABET[Number(num % BigInt(58))] + result;
209
+ num = num / BigInt(58);
210
+ }
211
+ // Add leading zeros
212
+ for (let i = 0; i < byteArray.length; i++) {
213
+ if (byteArray[i] === 0) {
214
+ result = "1" + result;
215
+ }
216
+ else {
217
+ break;
218
+ }
219
+ }
220
+ return result || "1";
221
+ }
222
+ /**
223
+ * Split data into equal-sized chunks
224
+ */
225
+ function splitIntoChunks(data, chunkSize) {
226
+ const chunks = [];
227
+ for (let i = 0; i < data.length; i += chunkSize) {
228
+ chunks.push(data.slice(i, Math.min(i + chunkSize, data.length)));
229
+ }
230
+ return chunks;
231
+ }
232
+ /**
233
+ * Fetch raw Bitcoin transaction from Esplora/Blockstream API
234
+ */
235
+ export async function fetchRawTransaction(txid, network = "testnet4") {
236
+ const baseUrl = network === "mainnet"
237
+ ? "https://mempool.space/api"
238
+ : `https://mempool.space/${network}/api`;
239
+ const response = await fetch(`${baseUrl}/tx/${txid}/raw`);
240
+ if (!response.ok) {
241
+ throw new Error(`Failed to fetch transaction: ${response.statusText}`);
242
+ }
243
+ const buffer = await response.arrayBuffer();
244
+ return new Uint8Array(buffer);
245
+ }
246
+ /**
247
+ * Fetch merkle proof from Esplora/Blockstream API
248
+ */
249
+ export async function fetchMerkleProof(txid, network = "testnet4") {
250
+ const baseUrl = network === "mainnet"
251
+ ? "https://mempool.space/api"
252
+ : `https://mempool.space/${network}/api`;
253
+ const response = await fetch(`${baseUrl}/tx/${txid}/merkle-proof`);
254
+ if (!response.ok) {
255
+ throw new Error(`Failed to fetch merkle proof: ${response.statusText}`);
256
+ }
257
+ const data = (await response.json());
258
+ // Parse merkle proof
259
+ const merkleProof = data.merkle.map((hash) => {
260
+ const bytes = hexToBytes(hash);
261
+ // Reverse for internal byte order
262
+ bytes.reverse();
263
+ return bytes;
264
+ });
265
+ return {
266
+ blockHeight: data.block_height,
267
+ merkleProof,
268
+ txIndex: data.pos,
269
+ };
270
+ }
271
+ // bytesToHex lives in ./crypto (single source); re-exported to preserve surface.
272
+ export { bytesToHex } from "./crypto";
273
+ /**
274
+ * Build merkle proof data for on-chain verification
275
+ *
276
+ * Layout:
277
+ * - txid: [u8; 32]
278
+ * - num_siblings: u8
279
+ * - siblings: [[u8; 32]; num_siblings]
280
+ * - tx_index: u32 (little-endian)
281
+ *
282
+ * @param txidBytes - 32-byte txid (already reversed for internal byte order)
283
+ * @param merkleProof - Array of 32-byte sibling hashes
284
+ * @param txIndex - Transaction index in block
285
+ * @returns Merkle proof data as Uint8Array
286
+ */
287
+ export function buildMerkleProof(txidBytes, merkleProof, txIndex) {
288
+ if (txidBytes.length !== 32) {
289
+ throw new Error("txid must be 32 bytes");
290
+ }
291
+ const numSiblings = merkleProof.length;
292
+ // txid (32) + num_siblings (1) + siblings (32 * n) + tx_index (4)
293
+ const totalSize = 32 + 1 + numSiblings * 32 + 4;
294
+ const data = new Uint8Array(totalSize);
295
+ let offset = 0;
296
+ // txid
297
+ data.set(txidBytes, offset);
298
+ offset += 32;
299
+ // num_siblings
300
+ data[offset++] = numSiblings;
301
+ // siblings
302
+ for (const sibling of merkleProof) {
303
+ if (sibling.length !== 32) {
304
+ throw new Error("Each sibling must be 32 bytes");
305
+ }
306
+ data.set(sibling, offset);
307
+ offset += 32;
308
+ }
309
+ // tx_index (u32 little-endian)
310
+ const indexView = new DataView(data.buffer, offset, 4);
311
+ indexView.setUint32(0, txIndex, true);
312
+ return data;
313
+ }
314
+ /**
315
+ * Complete flow: Fetch tx, upload to buffer, return verification data
316
+ */
317
+ export async function prepareVerifyDeposit(rpc, rpcSubscriptions, payer, txid, network = "testnet4") {
318
+ debug("chadbuffer", `Preparing verification for txid: ${txid.slice(0, 12)}...`);
319
+ // Fetch raw transaction
320
+ debug("chadbuffer", "Fetching raw transaction...");
321
+ const rawTx = await fetchRawTransaction(txid, network);
322
+ debug("chadbuffer", `Raw tx size: ${rawTx.length} bytes`);
323
+ // Fetch merkle proof
324
+ debug("chadbuffer", "Fetching merkle proof...");
325
+ const { blockHeight, merkleProof, txIndex } = await fetchMerkleProof(txid, network);
326
+ debug("chadbuffer", `Block height: ${blockHeight}, tx index: ${txIndex}`);
327
+ // Upload to ChadBuffer
328
+ debug("chadbuffer", "Uploading to ChadBuffer...");
329
+ const bufferAddress = await uploadTransactionToBuffer(rpc, rpcSubscriptions, payer, rawTx);
330
+ // Convert txid to bytes (reversed)
331
+ const txidBytes = hexToBytes(txid);
332
+ txidBytes.reverse();
333
+ return {
334
+ bufferAddress,
335
+ transactionSize: rawTx.length,
336
+ merkleProof,
337
+ blockHeight,
338
+ txIndex,
339
+ txidBytes,
340
+ };
341
+ }
342
+ // =============================================================================
343
+ // Proof Upload Utilities
344
+ // =============================================================================
345
+ /**
346
+ * Check if a proof needs buffer mode (too large for inline)
347
+ *
348
+ * @param proofBytes - Proof data
349
+ * @param availableSpace - Available space in transaction (default: use buffer for any proof > 900 bytes)
350
+ */
351
+ export function needsBuffer(proofBytes, availableSpace = 900) {
352
+ return proofBytes.length > availableSpace;
353
+ }
354
+ /**
355
+ * Upload proof to ChadBuffer if needed
356
+ *
357
+ * Automatically determines if buffer mode is needed based on proof size.
358
+ * Returns buffer address for buffer mode, or null for inline mode.
359
+ *
360
+ * @param rpc - Solana RPC client
361
+ * @param rpcSubscriptions - Solana RPC subscriptions client
362
+ * @param payer - Transaction fee payer
363
+ * @param proofBytes - Raw proof bytes
364
+ * @returns Upload result with buffer address (or null if inline)
365
+ */
366
+ export async function uploadProofToBuffer(rpc, rpcSubscriptions, payer, proofBytes) {
367
+ if (!needsBuffer(proofBytes)) {
368
+ return {
369
+ bufferAddress: null,
370
+ usedBuffer: false,
371
+ chunksUploaded: 0,
372
+ proofSize: proofBytes.length,
373
+ };
374
+ }
375
+ const bufferAddress = await uploadTransactionToBuffer(rpc, rpcSubscriptions, payer, proofBytes);
376
+ const chunks = Math.ceil(proofBytes.length / MAX_DATA_PER_WRITE);
377
+ return {
378
+ bufferAddress,
379
+ usedBuffer: true,
380
+ chunksUploaded: chunks,
381
+ proofSize: proofBytes.length,
382
+ };
383
+ }
384
+ /**
385
+ * Helper to determine proof source mode
386
+ */
387
+ export function getProofSource(proofBytes) {
388
+ return needsBuffer(proofBytes) ? "buffer" : "inline";
389
+ }
390
+ /**
391
+ * Calculate required number of transactions for a proof upload
392
+ *
393
+ * @param proofSize - Size of proof in bytes
394
+ * @param useBuffer - Force buffer mode (default: true for proofs > MAX_DATA_PER_WRITE)
395
+ */
396
+ export function calculateUploadTransactions(proofSize, useBuffer = true) {
397
+ if (!useBuffer) {
398
+ return 0; // Inline mode
399
+ }
400
+ // TX 1: CreateAccount
401
+ // TX 2: Init with first chunk
402
+ // TX 3+: Write remaining chunks
403
+ const chunks = Math.ceil(proofSize / MAX_DATA_PER_WRITE);
404
+ return 1 + chunks; // CreateAccount + Init + (chunks-1) writes = 1 + chunks
405
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Claim link utilities for UTXOpia
3
+ *
4
+ * Claim links encode a seed phrase in a URL fragment (#note=...) so the
5
+ * secret is never sent to the server (fragments are client-side only).
6
+ *
7
+ * SECURITY: Claim links are bearer instruments - anyone with the link can claim!
8
+ */
9
+ /**
10
+ * Encode a seed phrase for use in claim links.
11
+ * URL-encodes the seed — much shorter than encoding nullifier+secret.
12
+ *
13
+ * @param seed - Seed phrase (user's secret note)
14
+ * @returns URL-safe encoded string
15
+ */
16
+ export declare function encodeClaimLink(seed: string): string;
17
+ /**
18
+ * Decode a claim link seed.
19
+ *
20
+ * @param encoded - URL-encoded seed string
21
+ * @returns Decoded seed string, or null if invalid
22
+ */
23
+ export declare function decodeClaimLink(encoded: string): string | null;
24
+ /**
25
+ * Parse claim URL — reads seed from URL fragment (#note=...).
26
+ *
27
+ * @param url - URL string
28
+ * @returns Seed string or null if invalid
29
+ */
30
+ export declare function parseClaimUrl(url: string): string | null;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Claim link utilities for UTXOpia
3
+ *
4
+ * Claim links encode a seed phrase in a URL fragment (#note=...) so the
5
+ * secret is never sent to the server (fragments are client-side only).
6
+ *
7
+ * SECURITY: Claim links are bearer instruments - anyone with the link can claim!
8
+ */
9
+ /**
10
+ * Encode a seed phrase for use in claim links.
11
+ * URL-encodes the seed — much shorter than encoding nullifier+secret.
12
+ *
13
+ * @param seed - Seed phrase (user's secret note)
14
+ * @returns URL-safe encoded string
15
+ */
16
+ export function encodeClaimLink(seed) {
17
+ return encodeURIComponent(seed);
18
+ }
19
+ /**
20
+ * Decode a claim link seed.
21
+ *
22
+ * @param encoded - URL-encoded seed string
23
+ * @returns Decoded seed string, or null if invalid
24
+ */
25
+ export function decodeClaimLink(encoded) {
26
+ try {
27
+ const decoded = decodeURIComponent(encoded);
28
+ if (/^[a-zA-Z0-9]/.test(decoded) && decoded.length >= 8) {
29
+ return decoded;
30
+ }
31
+ }
32
+ catch {
33
+ // Not URL-encoded
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Parse claim URL — reads seed from URL fragment (#note=...).
39
+ *
40
+ * @param url - URL string
41
+ * @returns Seed string or null if invalid
42
+ */
43
+ export function parseClaimUrl(url) {
44
+ if (url.includes("#note=")) {
45
+ const encoded = url.split("#note=")[1].split("&")[0];
46
+ if (encoded)
47
+ return decodeClaimLink(encoded);
48
+ }
49
+ return null;
50
+ }