@provablehq/aleo-bridge-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,606 @@
1
+ // src/errors/bridgeErrors.ts
2
+ var BridgeError = class extends Error {
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "BridgeError";
6
+ }
7
+ };
8
+
9
+ // src/utils/xreserve.ts
10
+ import {
11
+ encodeAbiParameters,
12
+ getAddress,
13
+ hexToBytes,
14
+ isAddress,
15
+ isHex,
16
+ keccak256,
17
+ padHex,
18
+ toHex
19
+ } from "viem";
20
+ var HOOK_DATA_BYTES = 65;
21
+ var BECH32_ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
22
+ function bech32Polymod(values) {
23
+ const generators = [996825010, 642813549, 513874426, 1027748829, 705979059];
24
+ let checksum = 1;
25
+ for (const value of values) {
26
+ const top = checksum >>> 25;
27
+ checksum = (checksum & 33554431) << 5 ^ value;
28
+ for (let index = 0; index < 5; index++) if (top >>> index & 1) checksum ^= generators[index];
29
+ }
30
+ return checksum >>> 0;
31
+ }
32
+ function decodeAleoBech32m(address) {
33
+ const separator = address.lastIndexOf("1");
34
+ const prefix = address.slice(0, separator);
35
+ const encoded = address.slice(separator + 1);
36
+ const words = [...encoded].map((character) => BECH32_ALPHABET.indexOf(character));
37
+ if (prefix !== "aleo" || separator < 1 || words.some((word) => word < 0)) throw new Error("invalid encoding");
38
+ const expanded = [...prefix].map((character) => character.charCodeAt(0) >>> 5).concat([0], [...prefix].map((character) => character.charCodeAt(0) & 31), words);
39
+ if (bech32Polymod(expanded) !== 734539939) throw new Error("invalid checksum");
40
+ const payload = words.slice(0, -6);
41
+ const bytes = [];
42
+ let accumulator = 0;
43
+ let bits = 0;
44
+ for (const word of payload) {
45
+ accumulator = accumulator << 5 | word;
46
+ bits += 5;
47
+ while (bits >= 8) {
48
+ bits -= 8;
49
+ bytes.push(accumulator >>> bits & 255);
50
+ }
51
+ }
52
+ if (bits >= 5 || (accumulator << 8 - bits & 255) !== 0) throw new Error("invalid padding");
53
+ return Uint8Array.from(bytes);
54
+ }
55
+ function aleoAddressToBytes32(address) {
56
+ try {
57
+ if (!address.startsWith("aleo1") || address.length !== 63) throw new Error("invalid prefix or length");
58
+ const bytes = decodeAleoBech32m(address);
59
+ if (bytes.length !== 32) throw new Error("invalid payload");
60
+ return toHex(bytes);
61
+ } catch (cause) {
62
+ throw new BridgeError(`Invalid Aleo recipient address: ${address}`, { cause });
63
+ }
64
+ }
65
+ function bytes32ToAleoAddress(recipient) {
66
+ if (!/^0x[0-9a-fA-F]{64}$/.test(recipient)) {
67
+ throw new BridgeError(`Invalid 32-byte Aleo recipient: ${recipient}`);
68
+ }
69
+ const prefix = "aleo";
70
+ const words = [];
71
+ let accumulator = 0;
72
+ let bits = 0;
73
+ for (const byte of hexToBytes(recipient)) {
74
+ accumulator = accumulator << 8 | byte;
75
+ bits += 8;
76
+ while (bits >= 5) {
77
+ bits -= 5;
78
+ words.push(accumulator >>> bits & 31);
79
+ }
80
+ }
81
+ if (bits > 0) words.push(accumulator << 5 - bits & 31);
82
+ const expanded = [...prefix].map((character) => character.charCodeAt(0) >>> 5).concat([0], [...prefix].map((character) => character.charCodeAt(0) & 31));
83
+ const checksum = bech32Polymod([...expanded, ...words, 0, 0, 0, 0, 0, 0]) ^ 734539939;
84
+ for (let index = 0; index < 6; index++) {
85
+ words.push(checksum >>> 5 * (5 - index) & 31);
86
+ }
87
+ return `${prefix}1${words.map((word) => BECH32_ALPHABET[word]).join("")}`;
88
+ }
89
+ async function loadAleoSdk(environment) {
90
+ const moduleName = "@provablehq/sdk/dynamic.js";
91
+ try {
92
+ const sdk = await import(moduleName);
93
+ return sdk.loadNetwork(environment);
94
+ } catch (cause) {
95
+ throw new BridgeError("Private xReserve mints require the optional @provablehq/sdk package", { cause });
96
+ }
97
+ }
98
+ async function aleoProgramAddress(programId, environment) {
99
+ const sdk = await loadAleoSdk(environment);
100
+ return sdk.Address.fromProgramId(programId).to_string();
101
+ }
102
+ async function buildXReserveHookData(mode, recipient, environment, secretNonce = "0scalar") {
103
+ const bytes = new Uint8Array(HOOK_DATA_BYTES);
104
+ bytes[0] = mode === "public" ? 0 : mode === "record" ? 1 : 2;
105
+ if (mode === "private") {
106
+ const sdk = await loadAleoSdk(environment);
107
+ const bits = sdk.Plaintext.fromString(recipient).toBitsLe();
108
+ let scalar;
109
+ try {
110
+ scalar = sdk.Scalar.fromString(secretNonce);
111
+ } catch (cause) {
112
+ throw new BridgeError(`Invalid private mint secret nonce: ${secretNonce}`, { cause });
113
+ }
114
+ const commitment = new sdk.BHP256().commit(bits, scalar).toBytesLe();
115
+ if (commitment.length !== 32) throw new BridgeError("Private mint commitment must contain 32 bytes");
116
+ bytes.set(commitment, 1);
117
+ }
118
+ return toHex(bytes);
119
+ }
120
+ function calculateXReserveDepositNonce(sourceDomain, transactionHash, logIndex) {
121
+ const domain = encodeAbiParameters([{ type: "uint32" }], [sourceDomain]);
122
+ const index = encodeAbiParameters([{ type: "uint256" }], [BigInt(logIndex)]);
123
+ return keccak256(`0x${domain.slice(2)}${transactionHash.slice(2)}${index.slice(2)}`);
124
+ }
125
+ function uintBytes(value, bytes) {
126
+ if (value < 0n || value >= 1n << BigInt(bytes * 8)) throw new BridgeError(`Unsigned value does not fit in ${bytes} bytes`);
127
+ return hexToBytes(toHex(value, { size: bytes }));
128
+ }
129
+ function buildXReserveDepositPayload(params) {
130
+ if (!isHex(params.remoteToken, { strict: true }) || hexToBytes(params.remoteToken).length !== 32) throw new BridgeError("remoteToken must contain 32 bytes");
131
+ if (!isHex(params.remoteRecipient, { strict: true }) || hexToBytes(params.remoteRecipient).length !== 32) throw new BridgeError("remoteRecipient must contain 32 bytes");
132
+ if (!isHex(params.hookData, { strict: true }) || hexToBytes(params.hookData).length !== HOOK_DATA_BYTES) throw new BridgeError("hookData must contain 65 bytes");
133
+ if (!isAddress(params.localToken) || !isAddress(params.depositor)) throw new BridgeError("Payload EVM address is invalid");
134
+ const payload = new Uint8Array(305);
135
+ payload.set([90, 46, 10, 205, 0, 0, 0, 1], 0);
136
+ payload.set(uintBytes(params.amount, 32), 8);
137
+ payload.set(uintBytes(BigInt(params.remoteDomain), 4), 40);
138
+ payload.set(hexToBytes(params.remoteToken), 44);
139
+ payload.set(hexToBytes(params.remoteRecipient), 76);
140
+ payload.set(hexToBytes(padHex(getAddress(params.localToken), { size: 32 })), 108);
141
+ payload.set(hexToBytes(padHex(getAddress(params.depositor), { size: 32 })), 140);
142
+ payload.set(uintBytes(params.maxFee, 32), 172);
143
+ payload.set(hexToBytes(params.nonce), 204);
144
+ payload.set(uintBytes(BigInt(HOOK_DATA_BYTES), 4), 236);
145
+ payload.set(hexToBytes(params.hookData), 240);
146
+ return toHex(payload);
147
+ }
148
+ function calculateXReserveMessageHash(payload) {
149
+ return keccak256(payload);
150
+ }
151
+ function xReserveDepositNonceFromPayload(payload) {
152
+ if (!isHex(payload, { strict: true })) throw new BridgeError("xReserve payload must be prefixed hexadecimal");
153
+ const bytes = hexToBytes(payload);
154
+ if (bytes.length !== 305 || toHex(bytes.slice(0, 8)) !== "0x5a2e0acd00000001" || toHex(bytes.slice(236, 240)) !== "0x00000041") {
155
+ throw new BridgeError("xReserve payload has an invalid deposit layout");
156
+ }
157
+ return toHex(bytes.slice(204, 236));
158
+ }
159
+ function xReserveHexToAleoBytes(value, expectedBytes) {
160
+ if (!isHex(value, { strict: true })) throw new BridgeError("Aleo byte-array input must be prefixed hexadecimal");
161
+ const bytes = hexToBytes(value);
162
+ if (bytes.length !== expectedBytes) throw new BridgeError(`Aleo byte-array input must contain ${expectedBytes} bytes`);
163
+ return `[${[...bytes].map((byte) => `${byte}u8`).join(",")}]`;
164
+ }
165
+ function evmAddressToXReserveBytes32(address) {
166
+ if (!isAddress(address)) throw new BridgeError(`Invalid Ethereum recipient address: ${address}`);
167
+ return padHex(getAddress(address), { size: 32 });
168
+ }
169
+
170
+ // src/solana/igp.ts
171
+ var TOKEN_EXCHANGE_RATE_SCALE = 10n ** 19n;
172
+ var SOL_DECIMALS = 9;
173
+ var INITIALIZED_BYTES = 1;
174
+ var DISCRIMINATOR_BYTES = 8;
175
+ var BUMP_SEED_BYTES = 1;
176
+ var SALT_BYTES = 32;
177
+ var PUBKEY_BYTES = 32;
178
+ var ORACLE_COUNT_BYTES = 4;
179
+ var GAS_ORACLE_ENTRY_BYTES = 38;
180
+ var DOMAIN_BYTES = 4;
181
+ var GAS_ORACLE_TAG_BYTES = 1;
182
+ var EXCHANGE_RATE_BYTES = 16;
183
+ var GAS_PRICE_BYTES = 16;
184
+ function readUint128LE(view, offset) {
185
+ let value = 0n;
186
+ for (let index = EXCHANGE_RATE_BYTES - 1; index >= 0; index--) {
187
+ value = value << 8n | BigInt(view.getUint8(offset + index));
188
+ }
189
+ return value;
190
+ }
191
+ function quoteIgpGasPayment(params) {
192
+ const { igpAccountData } = params;
193
+ const view = new DataView(igpAccountData.buffer, igpAccountData.byteOffset, igpAccountData.byteLength);
194
+ const requireBytes = (offset2, length) => {
195
+ if (offset2 < 0 || length < 0 || offset2 + length > view.byteLength) {
196
+ throw new BridgeError("malformed Sealevel IGP account data: declared layout exceeds the supplied bytes");
197
+ }
198
+ };
199
+ let offset = INITIALIZED_BYTES + DISCRIMINATOR_BYTES + BUMP_SEED_BYTES + SALT_BYTES;
200
+ requireBytes(offset, 1);
201
+ const ownerOptionTag = view.getUint8(offset);
202
+ offset += 1;
203
+ if (ownerOptionTag !== 0 && ownerOptionTag !== 1) {
204
+ throw new BridgeError(`malformed Sealevel IGP account data: unsupported owner option tag ${ownerOptionTag}`);
205
+ }
206
+ if (ownerOptionTag === 1) {
207
+ requireBytes(offset, PUBKEY_BYTES);
208
+ offset += PUBKEY_BYTES;
209
+ }
210
+ requireBytes(offset, PUBKEY_BYTES + ORACLE_COUNT_BYTES);
211
+ offset += PUBKEY_BYTES;
212
+ const oracleCount = view.getUint32(offset, true);
213
+ offset += ORACLE_COUNT_BYTES;
214
+ for (let index = 0; index < oracleCount; index++) {
215
+ const entryStart = offset;
216
+ requireBytes(entryStart, GAS_ORACLE_ENTRY_BYTES);
217
+ const domain = view.getUint32(entryStart, true);
218
+ if (domain === params.destinationDomain) {
219
+ const tagOffset = entryStart + DOMAIN_BYTES;
220
+ const gasOracleTag = view.getUint8(tagOffset);
221
+ if (gasOracleTag !== 0) {
222
+ throw new BridgeError(
223
+ `Sealevel IGP account has an unexpected GasOracle variant tag ${gasOracleTag} for domain ${params.destinationDomain}; only variant 0 (RemoteGasData) is decoded`
224
+ );
225
+ }
226
+ const exchangeRateOffset = tagOffset + GAS_ORACLE_TAG_BYTES;
227
+ const gasPriceOffset = exchangeRateOffset + EXCHANGE_RATE_BYTES;
228
+ const decimalsOffset = gasPriceOffset + GAS_PRICE_BYTES;
229
+ const tokenExchangeRate = readUint128LE(view, exchangeRateOffset);
230
+ const gasPrice = readUint128LE(view, gasPriceOffset);
231
+ const tokenDecimals = view.getUint8(decimalsOffset);
232
+ const destinationCost = params.gasAmount * gasPrice;
233
+ const originCost = destinationCost * tokenExchangeRate / TOKEN_EXCHANGE_RATE_SCALE;
234
+ return SOL_DECIMALS >= tokenDecimals ? originCost * 10n ** BigInt(SOL_DECIMALS - tokenDecimals) : originCost / 10n ** BigInt(tokenDecimals - SOL_DECIMALS);
235
+ }
236
+ offset += GAS_ORACLE_ENTRY_BYTES;
237
+ }
238
+ throw new BridgeError(
239
+ `Sealevel IGP account has no gas-oracle entry for destination domain ${params.destinationDomain}`
240
+ );
241
+ }
242
+
243
+ // src/solana/kit.ts
244
+ var kitModulePromise;
245
+ async function loadKit() {
246
+ kitModulePromise ??= import("@solana/kit");
247
+ try {
248
+ return await kitModulePromise;
249
+ } catch (cause) {
250
+ throw new BridgeError(
251
+ "Solana support requires the optional peer dependency @solana/kit; install it with: pnpm add @solana/kit",
252
+ { cause }
253
+ );
254
+ }
255
+ }
256
+
257
+ // src/solana/transferRemote.ts
258
+ import { hexToBytes as hexToBytes2 } from "viem";
259
+ var PROGRAM_INSTRUCTION_DISCRIMINATOR = Uint8Array.of(1, 1, 1, 1, 1, 1, 1, 1);
260
+ var TRANSFER_REMOTE_VARIANT_TAG = 1;
261
+ var SYSTEM_PROGRAM_ADDRESS = "11111111111111111111111111111111";
262
+ var DISPATCHED_MESSAGE_PDA_SEED_PREFIX = ["hyperlane", "-", "dispatched_message", "-"];
263
+ var GAS_PAYMENT_PDA_SEED_PREFIX = ["hyperlane_igp", "-", "gas_payment", "-"];
264
+ var INSTRUCTION_DATA_BYTES = 77;
265
+ var U256_BYTES = 32;
266
+ function writeU32LE(bytes, offset, value) {
267
+ bytes[offset] = value & 255;
268
+ bytes[offset + 1] = value >>> 8 & 255;
269
+ bytes[offset + 2] = value >>> 16 & 255;
270
+ bytes[offset + 3] = value >>> 24 & 255;
271
+ }
272
+ function writeU256LE(bytes, offset, value) {
273
+ if (value < 0n || value >= 1n << BigInt(U256_BYTES * 8)) {
274
+ throw new BridgeError(`amountLamports does not fit in a ${U256_BYTES}-byte unsigned integer`);
275
+ }
276
+ let remaining = value;
277
+ for (let index = 0; index < U256_BYTES; index++) {
278
+ bytes[offset + index] = Number(remaining & 0xffn);
279
+ remaining >>= 8n;
280
+ }
281
+ }
282
+ async function buildTransferRemoteInstruction(params) {
283
+ const { metadata } = params;
284
+ const kit = await loadKit();
285
+ const addressEncoder = kit.getAddressEncoder();
286
+ const uniqueMessageBytes = addressEncoder.encode(kit.address(params.uniqueMessageAddress));
287
+ const [dispatchedMessagePda] = await kit.getProgramDerivedAddress({
288
+ programAddress: kit.address(metadata.mailboxProgramAddress),
289
+ seeds: [...DISPATCHED_MESSAGE_PDA_SEED_PREFIX, uniqueMessageBytes]
290
+ });
291
+ const [gasPaymentPda] = await kit.getProgramDerivedAddress({
292
+ programAddress: kit.address(metadata.igpProgramAddress),
293
+ seeds: [...GAS_PAYMENT_PDA_SEED_PREFIX, uniqueMessageBytes]
294
+ });
295
+ const data = new Uint8Array(INSTRUCTION_DATA_BYTES);
296
+ data.set(PROGRAM_INSTRUCTION_DISCRIMINATOR, 0);
297
+ data[8] = TRANSFER_REMOTE_VARIANT_TAG;
298
+ writeU32LE(data, 9, metadata.destinationDomain);
299
+ data.set(hexToBytes2(aleoAddressToBytes32(params.recipientAleoAddress)), 13);
300
+ writeU256LE(data, 45, params.amountLamports);
301
+ const accounts = [
302
+ { address: SYSTEM_PROGRAM_ADDRESS, signer: false, writable: false },
303
+ // row 0
304
+ { address: metadata.splNoopProgramAddress, signer: false, writable: false },
305
+ // row 1
306
+ { address: metadata.tokenPda, signer: false, writable: false },
307
+ // row 2
308
+ { address: metadata.mailboxProgramAddress, signer: false, writable: false },
309
+ // row 3
310
+ { address: metadata.mailboxOutboxPda, signer: false, writable: true },
311
+ // row 4
312
+ { address: metadata.dispatchAuthorityPda, signer: false, writable: false },
313
+ // row 5
314
+ { address: params.senderAddress, signer: true, writable: true },
315
+ // row 6
316
+ { address: params.uniqueMessageAddress, signer: true, writable: false },
317
+ // row 7
318
+ { address: dispatchedMessagePda, signer: false, writable: true },
319
+ // row 8
320
+ { address: metadata.igpProgramAddress, signer: false, writable: false },
321
+ // row 9
322
+ { address: metadata.igpProgramDataPda, signer: false, writable: true },
323
+ // row 10
324
+ { address: gasPaymentPda, signer: false, writable: true },
325
+ // row 11
326
+ // row 12 (optional): only present when the route wraps its IGP in an
327
+ // `OverheadIgp` — omitted entirely otherwise (SEALEVEL_NOTES.md §2 row
328
+ // 12, "optional slot").
329
+ ...metadata.igpOverheadAccount ? [{ address: metadata.igpOverheadAccount, signer: false, writable: false }] : [],
330
+ { address: metadata.igpAccount, signer: false, writable: true },
331
+ // row 13
332
+ { address: SYSTEM_PROGRAM_ADDRESS, signer: false, writable: false },
333
+ // row 14
334
+ { address: metadata.nativeCollateralPda, signer: false, writable: true }
335
+ // row 15
336
+ ];
337
+ return { programAddress: metadata.warpProgramAddress, accounts, data };
338
+ }
339
+
340
+ // src/solana/rpc.ts
341
+ function decodeBase64(value) {
342
+ const binary = atob(value);
343
+ const bytes = new Uint8Array(binary.length);
344
+ for (let index = 0; index < binary.length; index++) {
345
+ bytes[index] = binary.charCodeAt(index);
346
+ }
347
+ return bytes;
348
+ }
349
+ function createSolanaRpcClient(config) {
350
+ async function call(method, params) {
351
+ const transport = config.transport ?? globalThis.fetch;
352
+ const response = await transport(config.url, {
353
+ method: "POST",
354
+ headers: { "content-type": "application/json", "cache-control": "no-cache" },
355
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
356
+ cache: "no-store"
357
+ });
358
+ if (!response.ok) {
359
+ throw new BridgeError(`Solana RPC ${method} request failed with HTTP status ${response.status}`, {
360
+ cause: { status: response.status }
361
+ });
362
+ }
363
+ let rawBody;
364
+ try {
365
+ rawBody = await response.json();
366
+ } catch (error) {
367
+ throw new BridgeError(`Solana RPC ${method} returned invalid JSON`, { cause: error });
368
+ }
369
+ if (!rawBody || typeof rawBody !== "object") {
370
+ throw new BridgeError(`Solana RPC ${method} returned an invalid JSON-RPC response`);
371
+ }
372
+ const body = rawBody;
373
+ if (body.error) {
374
+ throw new BridgeError(`Solana RPC ${method} returned a JSON-RPC error: ${body.error.message ?? "unknown error"}`, {
375
+ cause: body.error
376
+ });
377
+ }
378
+ if (!Object.prototype.hasOwnProperty.call(body, "result") || body.result === void 0) {
379
+ throw new BridgeError(`Solana RPC ${method} returned an invalid result envelope`);
380
+ }
381
+ return body.result;
382
+ }
383
+ function integer(method, value) {
384
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
385
+ throw new BridgeError(`Solana RPC ${method} returned an invalid result`);
386
+ }
387
+ return BigInt(value);
388
+ }
389
+ function contextualValue(method, result) {
390
+ if (!result || typeof result !== "object" || !Object.prototype.hasOwnProperty.call(result, "value")) {
391
+ throw new BridgeError(`Solana RPC ${method} returned an invalid contextual result`);
392
+ }
393
+ return result.value;
394
+ }
395
+ return {
396
+ async getLatestBlockhash() {
397
+ const result = await call(
398
+ "getLatestBlockhash",
399
+ [{ commitment: "confirmed" }]
400
+ );
401
+ const value = contextualValue("getLatestBlockhash", result);
402
+ if (!value || typeof value.blockhash !== "string" || !value.blockhash) {
403
+ throw new BridgeError("Solana RPC getLatestBlockhash returned an invalid result");
404
+ }
405
+ return { blockhash: value.blockhash, lastValidBlockHeight: integer("getLatestBlockhash", value.lastValidBlockHeight) };
406
+ },
407
+ async getBlockHeight() {
408
+ return integer("getBlockHeight", await call("getBlockHeight", []));
409
+ },
410
+ async isBlockhashValid(blockhash) {
411
+ const result = await call("isBlockhashValid", [blockhash, { commitment: "confirmed" }]);
412
+ const value = contextualValue("isBlockhashValid", result);
413
+ if (typeof value !== "boolean") {
414
+ throw new BridgeError("Solana RPC isBlockhashValid returned an invalid result");
415
+ }
416
+ return value;
417
+ },
418
+ async getBalance(address) {
419
+ const value = contextualValue("getBalance", await call("getBalance", [address]));
420
+ return integer("getBalance", value);
421
+ },
422
+ async getFeeForMessage(message) {
423
+ const base64 = btoa(String.fromCharCode(...message));
424
+ const result = await call("getFeeForMessage", [base64, { commitment: "confirmed" }]);
425
+ return integer("getFeeForMessage", contextualValue("getFeeForMessage", result));
426
+ },
427
+ async getMinimumBalanceForRentExemption(dataLength) {
428
+ if (!Number.isSafeInteger(dataLength) || dataLength < 0) {
429
+ throw new BridgeError("Solana rent data length must be a non-negative integer");
430
+ }
431
+ return integer("getMinimumBalanceForRentExemption", await call("getMinimumBalanceForRentExemption", [dataLength]));
432
+ },
433
+ async getAccountData(address) {
434
+ const result = await call("getAccountInfo", [
435
+ address,
436
+ { encoding: "base64" }
437
+ ]);
438
+ const value = contextualValue("getAccountInfo", result);
439
+ if (!value) return null;
440
+ if (!Array.isArray(value.data) || typeof value.data[0] !== "string" || value.data[1] !== "base64") {
441
+ throw new BridgeError("Solana RPC getAccountInfo returned invalid base64 account data");
442
+ }
443
+ try {
444
+ return decodeBase64(value.data[0]);
445
+ } catch (error) {
446
+ throw new BridgeError("Solana RPC getAccountInfo returned invalid base64 account data", { cause: error });
447
+ }
448
+ },
449
+ async getSignatureStatus(signature) {
450
+ const result = await call(
451
+ "getSignatureStatuses",
452
+ [[signature], { searchTransactionHistory: true }]
453
+ );
454
+ const value = contextualValue("getSignatureStatuses", result);
455
+ if (!Array.isArray(value) || value.length !== 1) {
456
+ throw new BridgeError("Solana RPC getSignatureStatuses returned an invalid result");
457
+ }
458
+ const status = value[0];
459
+ if (!status) return null;
460
+ if (typeof status !== "object" || !Object.prototype.hasOwnProperty.call(status, "err")) {
461
+ throw new BridgeError("Solana RPC getSignatureStatuses returned an invalid status");
462
+ }
463
+ if (status.err) return "failed";
464
+ if (status.confirmationStatus === void 0) return null;
465
+ if (!["processed", "confirmed", "finalized"].includes(status.confirmationStatus)) {
466
+ throw new BridgeError(`Solana RPC getSignatureStatuses returned unsupported confirmation status: ${status.confirmationStatus}`);
467
+ }
468
+ return status.confirmationStatus;
469
+ },
470
+ async getTransactionLogs(signature) {
471
+ const result = await call("getTransaction", [
472
+ signature,
473
+ { maxSupportedTransactionVersion: 0, commitment: "confirmed" }
474
+ ]);
475
+ if (result === null) return null;
476
+ if (!result || typeof result !== "object" || !Object.prototype.hasOwnProperty.call(result, "meta")) {
477
+ throw new BridgeError("Solana RPC getTransaction returned an invalid result");
478
+ }
479
+ const meta = result.meta;
480
+ if (!meta || typeof meta !== "object" || !Object.prototype.hasOwnProperty.call(meta, "logMessages")) {
481
+ throw new BridgeError("Solana RPC getTransaction returned invalid metadata");
482
+ }
483
+ const logs = meta.logMessages;
484
+ if (logs !== null && (!Array.isArray(logs) || logs.some((line) => typeof line !== "string"))) {
485
+ throw new BridgeError("Solana RPC getTransaction returned invalid logs");
486
+ }
487
+ return logs ?? null;
488
+ }
489
+ };
490
+ }
491
+
492
+ // src/connections/solana.ts
493
+ import bs58 from "bs58";
494
+ var SOLANA_SIGN_AND_SEND_TRANSACTION_FEATURE = "solana:signAndSendTransaction";
495
+ var DEFAULT_SOLANA_RPC_URL = "https://api.mainnet-beta.solana.com";
496
+ function solanaHttp(url, options = {}) {
497
+ return { type: "http", url, fetch: options.fetch };
498
+ }
499
+ function solanaCustom(request) {
500
+ return { type: "custom", request };
501
+ }
502
+ function solanaWallet(params) {
503
+ return { type: "wallet", ...params };
504
+ }
505
+ function solanaKeyPair(secretKeyBytes) {
506
+ if (secretKeyBytes.length !== 64) throw new BridgeError("Solana secret key must contain exactly 64 bytes");
507
+ return { type: "local", secretKeyBytes: new Uint8Array(secretKeyBytes) };
508
+ }
509
+ function createSolanaClient(config) {
510
+ if (!config.transport) throw new BridgeError("Solana client requires a transport");
511
+ return materializeSolanaClient(config, globalThis.fetch);
512
+ }
513
+ function materializeSolanaClient(config, defaultFetch) {
514
+ const transportDefinition = config.transport;
515
+ const httpTransport = transportDefinition.type === "http" ? transportDefinition.fetch ?? defaultFetch : async (_url, init) => {
516
+ const body = JSON.parse(init.body);
517
+ return { ok: true, status: 200, json: async () => ({ result: await transportDefinition.request(body.method, body.params) }) };
518
+ };
519
+ const url = transportDefinition.type === "http" ? transportDefinition.url : "solana:custom";
520
+ const rpcClient = createSolanaRpcClient({ url, transport: httpTransport });
521
+ const publicClient = {
522
+ ...rpcClient,
523
+ async sendTransaction(signedTransaction) {
524
+ const base64 = btoa(String.fromCharCode(...signedTransaction));
525
+ const sendOptions = { encoding: "base64", preflightCommitment: "confirmed" };
526
+ if (transportDefinition.type === "custom") {
527
+ const signature = await transportDefinition.request("sendTransaction", [base64, sendOptions]);
528
+ if (typeof signature !== "string" || !signature) throw new BridgeError("Solana RPC sendTransaction returned an invalid signature");
529
+ return { signature };
530
+ }
531
+ const response = await httpTransport(url, {
532
+ method: "POST",
533
+ headers: { "content-type": "application/json", "cache-control": "no-cache" },
534
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "sendTransaction", params: [base64, sendOptions] }),
535
+ cache: "no-store"
536
+ });
537
+ const body = await response.json();
538
+ if (!response.ok || body.error || typeof body.result !== "string" || !body.result) {
539
+ const details = body.error?.data === void 0 ? "" : `; ${JSON.stringify(body.error.data)}`;
540
+ throw new BridgeError(`Solana RPC sendTransaction failed: ${body.error?.message ?? `HTTP ${response.status}`}${details}`);
541
+ }
542
+ return { signature: body.result };
543
+ }
544
+ };
545
+ let walletClient;
546
+ if (config.account?.type === "wallet") {
547
+ const walletAccount = config.account;
548
+ const feature = walletAccount.wallet.features[SOLANA_SIGN_AND_SEND_TRANSACTION_FEATURE];
549
+ if (!feature) throw new BridgeError(`Connected wallet does not expose the '${SOLANA_SIGN_AND_SEND_TRANSACTION_FEATURE}' feature`);
550
+ walletClient = {
551
+ getAddress: async () => walletAccount.account.address,
552
+ sendTransaction: async (transaction) => {
553
+ const [output] = await feature.signAndSendTransaction({
554
+ transaction,
555
+ account: walletAccount.account,
556
+ chain: walletAccount.chain
557
+ });
558
+ if (!output) throw new BridgeError("Wallet returned no signAndSendTransaction result");
559
+ return { signature: bs58.encode(output.signature) };
560
+ }
561
+ };
562
+ } else if (config.account?.type === "local") {
563
+ let signerPromise;
564
+ const create = () => signerPromise ??= createSigner(config.account.type === "local" ? config.account.secretKeyBytes : new Uint8Array());
565
+ walletClient = {
566
+ getAddress: async () => (await create()).address,
567
+ sendTransaction: async (wireTransaction) => {
568
+ const kit = await loadKit();
569
+ const signer = await create();
570
+ const transaction = kit.getTransactionDecoder().decode(wireTransaction);
571
+ const signed = await kit.partiallySignTransaction([signer.keyPair], transaction);
572
+ return publicClient.sendTransaction(new Uint8Array(kit.getTransactionEncoder().encode(signed)));
573
+ }
574
+ };
575
+ }
576
+ return { family: "solana", publicClient, walletClient };
577
+ }
578
+ async function createSigner(secretKeyBytes) {
579
+ const kit = await loadKit();
580
+ return kit.createKeyPairSignerFromBytes(secretKeyBytes);
581
+ }
582
+
583
+ export {
584
+ BridgeError,
585
+ aleoAddressToBytes32,
586
+ bytes32ToAleoAddress,
587
+ aleoProgramAddress,
588
+ buildXReserveHookData,
589
+ calculateXReserveDepositNonce,
590
+ buildXReserveDepositPayload,
591
+ calculateXReserveMessageHash,
592
+ xReserveDepositNonceFromPayload,
593
+ xReserveHexToAleoBytes,
594
+ evmAddressToXReserveBytes32,
595
+ quoteIgpGasPayment,
596
+ loadKit,
597
+ buildTransferRemoteInstruction,
598
+ createSolanaRpcClient,
599
+ DEFAULT_SOLANA_RPC_URL,
600
+ solanaHttp,
601
+ solanaCustom,
602
+ solanaWallet,
603
+ solanaKeyPair,
604
+ createSolanaClient
605
+ };
606
+ //# sourceMappingURL=chunk-OU6GVGG7.js.map