@piprail/sdk 1.20.1 → 1.21.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,715 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ var _chunkPA6YD3HLcjs = require('./chunk-PA6YD3HL.cjs');
11
+
12
+ // src/drivers/solana/index.ts
13
+ var _web3js = require('@solana/web3.js');
14
+
15
+
16
+
17
+
18
+
19
+ var _spltoken = require('@solana/spl-token');
20
+
21
+ // src/drivers/solana/chains.ts
22
+ var SOL_DECIMALS = 9;
23
+ var SOLANA_MAINNET = {
24
+ caip2: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
25
+ defaultRpc: "https://api.mainnet-beta.solana.com",
26
+ tokens: {
27
+ USDC: {
28
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
29
+ decimals: 6,
30
+ symbol: "USDC"
31
+ },
32
+ USDT: {
33
+ mint: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
34
+ decimals: 6,
35
+ symbol: "USDT"
36
+ }
37
+ }
38
+ };
39
+
40
+ // src/drivers/solana/pay.ts
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+ async function paySolana(params) {
52
+ const { connection, keypair, accept } = params;
53
+ const payTo = new (0, _web3js.PublicKey)(accept.payTo);
54
+ const amount = BigInt(accept.amount);
55
+ const tx = new (0, _web3js.Transaction)();
56
+ if (accept.asset === "native") {
57
+ tx.add(
58
+ _web3js.SystemProgram.transfer({
59
+ fromPubkey: keypair.publicKey,
60
+ toPubkey: payTo,
61
+ lamports: amount
62
+ })
63
+ );
64
+ } else {
65
+ const mint = new (0, _web3js.PublicKey)(accept.asset);
66
+ const source = _spltoken.getAssociatedTokenAddressSync.call(void 0, mint, keypair.publicKey);
67
+ const dest = _spltoken.getAssociatedTokenAddressSync.call(void 0, mint, payTo);
68
+ tx.add(
69
+ _spltoken.createAssociatedTokenAccountIdempotentInstruction.call(void 0,
70
+ keypair.publicKey,
71
+ // payer for any rent
72
+ dest,
73
+ payTo,
74
+ mint
75
+ )
76
+ );
77
+ tx.add(
78
+ _spltoken.createTransferCheckedInstruction.call(void 0,
79
+ source,
80
+ mint,
81
+ dest,
82
+ keypair.publicKey,
83
+ amount,
84
+ accept.extra.decimals
85
+ )
86
+ );
87
+ }
88
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
89
+ tx.recentBlockhash = blockhash;
90
+ tx.lastValidBlockHeight = lastValidBlockHeight;
91
+ tx.feePayer = keypair.publicKey;
92
+ tx.sign(keypair);
93
+ const signature = await connection.sendRawTransaction(tx.serialize(), {
94
+ maxRetries: 5
95
+ });
96
+ await connection.confirmTransaction(
97
+ { signature, blockhash, lastValidBlockHeight },
98
+ "confirmed"
99
+ );
100
+ return signature;
101
+ }
102
+
103
+ // src/drivers/solana/verify.ts
104
+ async function verifySolana(params) {
105
+ const { connection, signature, accept } = params;
106
+ const required = BigInt(accept.amount);
107
+ let tx;
108
+ try {
109
+ tx = await connection.getParsedTransaction(signature, {
110
+ commitment: "confirmed",
111
+ maxSupportedTransactionVersion: 0
112
+ });
113
+ } catch (e2) {
114
+ return notFound(signature);
115
+ }
116
+ if (!tx) return notFound(signature);
117
+ const meta = tx.meta;
118
+ if (!meta) {
119
+ return { ok: false, error: "no_meta", detail: `No metadata for ${signature}.` };
120
+ }
121
+ if (meta.err !== null) {
122
+ return {
123
+ ok: false,
124
+ error: "tx_reverted",
125
+ detail: `Transaction ${signature} failed on-chain.`
126
+ };
127
+ }
128
+ if (typeof tx.blockTime !== "number") {
129
+ return {
130
+ ok: false,
131
+ error: "payment_expired",
132
+ detail: `Cannot determine the age of ${signature} (no blockTime).`
133
+ };
134
+ }
135
+ const ageSeconds = Math.floor(Date.now() / 1e3) - tx.blockTime;
136
+ if (ageSeconds > accept.maxTimeoutSeconds) {
137
+ return {
138
+ ok: false,
139
+ error: "payment_expired",
140
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
141
+ };
142
+ }
143
+ const accountKeys = tx.transaction.message.accountKeys.map(
144
+ (k) => k.pubkey.toBase58()
145
+ );
146
+ const payer = _nullishCoalesce(accountKeys[0], () => ( ""));
147
+ if (accept.asset === "native") {
148
+ const idx = accountKeys.indexOf(accept.payTo);
149
+ if (idx < 0) {
150
+ return {
151
+ ok: false,
152
+ error: "wrong_recipient",
153
+ detail: `Native payment in ${signature} did not credit ${accept.payTo}.`
154
+ };
155
+ }
156
+ const delta = BigInt(_nullishCoalesce(meta.postBalances[idx], () => ( 0))) - BigInt(_nullishCoalesce(meta.preBalances[idx], () => ( 0)));
157
+ if (delta < required) {
158
+ return {
159
+ ok: false,
160
+ error: "amount_too_low",
161
+ detail: `Credited ${delta} lamports, required ${required}.`
162
+ };
163
+ }
164
+ } else {
165
+ const mint = accept.asset;
166
+ const pre = _nullishCoalesce(meta.preTokenBalances, () => ( []));
167
+ const post = _nullishCoalesce(meta.postTokenBalances, () => ( []));
168
+ let delta = 0n;
169
+ for (const p of post) {
170
+ if (p.mint !== mint || p.owner !== accept.payTo) continue;
171
+ const before = pre.find((x) => x.accountIndex === p.accountIndex);
172
+ delta += BigInt(p.uiTokenAmount.amount) - BigInt(_nullishCoalesce(_optionalChain([before, 'optionalAccess', _ => _.uiTokenAmount, 'access', _2 => _2.amount]), () => ( "0")));
173
+ }
174
+ if (delta < required) {
175
+ return {
176
+ ok: false,
177
+ error: "transfer_not_found",
178
+ detail: `No SPL transfer of >= ${required} (mint ${mint}) to ${accept.payTo} in ${signature}.`
179
+ };
180
+ }
181
+ }
182
+ return {
183
+ ok: true,
184
+ receipt: {
185
+ scheme: "onchain-proof",
186
+ success: true,
187
+ network: accept.network,
188
+ transaction: signature,
189
+ asset: accept.asset,
190
+ amount: accept.amount,
191
+ payer,
192
+ payTo: accept.payTo,
193
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
194
+ }
195
+ };
196
+ }
197
+ function notFound(signature) {
198
+ return {
199
+ ok: false,
200
+ error: "tx_not_found",
201
+ detail: `Signature ${signature} not found or not yet confirmed.`
202
+ };
203
+ }
204
+
205
+ // src/drivers/solana/exact.ts
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+ var _bs58 = require('bs58'); var _bs582 = _interopRequireDefault(_bs58);
221
+ var COMPUTE_UNIT_LIMIT = 2e4;
222
+ var COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
223
+ function tokenProgramFor(accept) {
224
+ return accept.extra.tokenProgram === "token-2022" ? _spltoken.TOKEN_2022_PROGRAM_ID : _spltoken.TOKEN_PROGRAM_ID;
225
+ }
226
+ function isEmptySig(sig) {
227
+ return !sig || sig.every((b) => b === 0);
228
+ }
229
+ async function payExactSolana(input) {
230
+ const { connection, keypair, accept } = input;
231
+ if (accept.asset === "native") {
232
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
233
+ "SVM exact is SPL-token only (TransferChecked); native SOL is not exact-payable. Pay via onchain-proof."
234
+ );
235
+ }
236
+ if (!accept.extra.feePayer) {
237
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)("SVM exact rail must advertise extra.feePayer (the merchant sponsor key).");
238
+ }
239
+ if (accept.extra.decimals === void 0) {
240
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)("SVM exact rail must advertise extra.decimals for the TransferChecked.");
241
+ }
242
+ let feePayer;
243
+ let mint;
244
+ let payTo;
245
+ try {
246
+ feePayer = new (0, _web3js.PublicKey)(accept.extra.feePayer);
247
+ mint = new (0, _web3js.PublicKey)(accept.asset);
248
+ payTo = new (0, _web3js.PublicKey)(accept.payTo);
249
+ } catch (err) {
250
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
251
+ `SVM exact: bad feePayer/asset/payTo (${err instanceof Error ? err.message : String(err)}).`
252
+ );
253
+ }
254
+ if (feePayer.equals(payTo)) {
255
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
256
+ "SVM exact: the fee payer must differ from payTo \u2014 payTo appears in the transfer instruction, which the fee payer must not (a scheme MUST-rule). Use a separate relayer key for the gate."
257
+ );
258
+ }
259
+ const program = tokenProgramFor(accept);
260
+ const source = _spltoken.getAssociatedTokenAddressSync.call(void 0, mint, keypair.publicKey, true, program);
261
+ const dest = _spltoken.getAssociatedTokenAddressSync.call(void 0, mint, payTo, true, program);
262
+ let destInfo = "unknown";
263
+ try {
264
+ destInfo = await input.connection.getAccountInfo(dest);
265
+ } catch (e3) {
266
+ destInfo = "unknown";
267
+ }
268
+ if (destInfo === null) {
269
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
270
+ `SVM exact: the recipient's token account for ${mint.toBase58()} doesn't exist yet \u2014 the exact rail can't create it. Pay via onchain-proof (which creates it), or have ${payTo.toBase58()} create its associated token account first.`
271
+ );
272
+ }
273
+ const instructions = [
274
+ _web3js.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNIT_LIMIT }),
275
+ _web3js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_UNIT_PRICE_MICROLAMPORTS }),
276
+ _spltoken.createTransferCheckedInstruction.call(void 0,
277
+ source,
278
+ mint,
279
+ dest,
280
+ keypair.publicKey,
281
+ BigInt(accept.amount),
282
+ accept.extra.decimals,
283
+ [],
284
+ program
285
+ )
286
+ ];
287
+ const { blockhash } = await connection.getLatestBlockhash();
288
+ const message = new (0, _web3js.TransactionMessage)({
289
+ payerKey: feePayer,
290
+ recentBlockhash: blockhash,
291
+ instructions
292
+ }).compileToV0Message();
293
+ const tx = new (0, _web3js.VersionedTransaction)(message);
294
+ tx.sign([keypair]);
295
+ const buyerIndex = message.staticAccountKeys.findIndex((k) => k.equals(keypair.publicKey));
296
+ if (buyerIndex < 1 || buyerIndex >= message.header.numRequiredSignatures) {
297
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)("SVM exact: could not locate the buyer signer slot.");
298
+ }
299
+ const buyerSig = tx.signatures[buyerIndex];
300
+ if (!buyerSig || isEmptySig(buyerSig)) {
301
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)("SVM exact: the wallet did not produce a buyer signature.");
302
+ }
303
+ const transaction = Buffer.from(tx.serialize()).toString("base64");
304
+ return {
305
+ payload: { transaction },
306
+ payerFrom: keypair.publicKey.toBase58(),
307
+ nonce: _bs582.default.encode(buyerSig)
308
+ };
309
+ }
310
+ function shorten(msg) {
311
+ const oneLine = msg.replace(/\s+/g, " ").trim();
312
+ return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
313
+ }
314
+ function fail(error, detail) {
315
+ return { ok: false, error, detail };
316
+ }
317
+ async function verifyAndSettleExactSolana(input) {
318
+ const { connection, feePayerKeypair, payload, accept } = input;
319
+ let payTo;
320
+ let mint;
321
+ let railFeePayer;
322
+ try {
323
+ payTo = new (0, _web3js.PublicKey)(accept.payTo);
324
+ mint = new (0, _web3js.PublicKey)(accept.asset);
325
+ if (!accept.extra.feePayer) throw new Error("rail is missing extra.feePayer");
326
+ railFeePayer = new (0, _web3js.PublicKey)(accept.extra.feePayer);
327
+ } catch (err) {
328
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(`SVM exact: rail has a bad payTo/asset/feePayer (${err instanceof Error ? err.message : String(err)}).`);
329
+ }
330
+ if (!railFeePayer.equals(feePayerKeypair.publicKey)) {
331
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
332
+ "SVM exact: the gate relayer key does not match the rail extra.feePayer \u2014 misconfigured rail."
333
+ );
334
+ }
335
+ if (feePayerKeypair.publicKey.equals(payTo)) {
336
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)("SVM exact: the fee payer must differ from payTo \u2014 misconfigured rail.");
337
+ }
338
+ const program = tokenProgramFor(accept);
339
+ let tx;
340
+ try {
341
+ tx = _web3js.VersionedTransaction.deserialize(Buffer.from(payload.transaction, "base64"));
342
+ } catch (err) {
343
+ return fail("signature_invalid", `Unparseable SVM transaction: ${shorten(err instanceof Error ? err.message : String(err))}.`);
344
+ }
345
+ const message = tx.message;
346
+ if (message.version !== 0) {
347
+ return fail("signature_invalid", "SVM exact requires a versioned (v0) transaction.");
348
+ }
349
+ const altAccounts = [];
350
+ for (const lookup of message.addressTableLookups) {
351
+ let value;
352
+ try {
353
+ ;
354
+ ({ value } = await connection.getAddressLookupTable(lookup.accountKey));
355
+ } catch (e4) {
356
+ return fail("tx_not_found", `Could not read lookup table ${lookup.accountKey.toBase58()} (transient RPC) \u2014 retry.`);
357
+ }
358
+ if (!value) {
359
+ return fail("signature_invalid", `Lookup table ${lookup.accountKey.toBase58()} is not resolvable \u2014 account visibility cannot be guaranteed.`);
360
+ }
361
+ altAccounts.push(value);
362
+ }
363
+ const keys = message.getAccountKeys({ addressLookupTableAccounts: altAccounts });
364
+ const feePayerKey = keys.get(0);
365
+ if (!feePayerKey || !feePayerKey.equals(feePayerKeypair.publicKey)) {
366
+ return fail("signature_invalid", "Transaction fee payer is not the merchant sponsor key.");
367
+ }
368
+ if (!isEmptySig(tx.signatures[0])) {
369
+ return fail("signature_invalid", "The fee-payer signature slot must be empty \u2014 the buyer must not sign it.");
370
+ }
371
+ for (const ix of message.compiledInstructions) {
372
+ const ixProgram = keys.get(ix.programIdIndex);
373
+ if (ixProgram && ixProgram.equals(feePayerKey)) {
374
+ return fail("signature_invalid", "The fee payer is invoked as a program.");
375
+ }
376
+ for (const i of ix.accountKeyIndexes) {
377
+ if (_optionalChain([keys, 'access', _3 => _3.get, 'call', _4 => _4(i), 'optionalAccess', _5 => _5.equals, 'call', _6 => _6(feePayerKey)])) {
378
+ return fail("signature_invalid", "The fee payer appears in an instruction account list (would risk a fund drain).");
379
+ }
380
+ }
381
+ }
382
+ const isSignedSigner = (pubkey) => {
383
+ const idx = message.staticAccountKeys.findIndex((k) => k.equals(pubkey));
384
+ return idx >= 1 && idx < message.header.numRequiredSignatures && !isEmptySig(tx.signatures[idx]);
385
+ };
386
+ const expectedDest = _spltoken.getAssociatedTokenAddressSync.call(void 0, mint, payTo, true, program);
387
+ const requiredAmount = BigInt(accept.amount);
388
+ let paidToPayTo = 0n;
389
+ let buyerOwner = null;
390
+ let sawTransferToPayTo = false;
391
+ for (const ix of message.compiledInstructions) {
392
+ const programId = keys.get(ix.programIdIndex);
393
+ if (!programId || !programId.equals(program)) continue;
394
+ let decoded;
395
+ try {
396
+ decoded = _spltoken.decodeTransferCheckedInstruction.call(void 0,
397
+ new (0, _web3js.TransactionInstruction)({
398
+ programId,
399
+ keys: ix.accountKeyIndexes.map((i) => ({
400
+ pubkey: keys.get(i),
401
+ isSigner: message.isAccountSigner(i),
402
+ isWritable: message.isAccountWritable(i)
403
+ })),
404
+ data: Buffer.from(ix.data)
405
+ }),
406
+ program
407
+ );
408
+ } catch (e5) {
409
+ continue;
410
+ }
411
+ if (!decoded.keys.mint.pubkey.equals(mint)) continue;
412
+ if (!decoded.keys.destination.pubkey.equals(expectedDest)) {
413
+ return fail("wrong_recipient", `A transfer pays ${decoded.keys.destination.pubkey.toBase58()}, not payTo's ATA ${expectedDest.toBase58()}.`);
414
+ }
415
+ if (decoded.data.decimals !== accept.extra.decimals) {
416
+ return fail("transfer_not_found", `Transfer decimals ${decoded.data.decimals} \u2260 rail decimals ${accept.extra.decimals}.`);
417
+ }
418
+ sawTransferToPayTo = true;
419
+ if (!isSignedSigner(decoded.keys.owner.pubkey)) continue;
420
+ paidToPayTo += BigInt(decoded.data.amount);
421
+ buyerOwner = decoded.keys.owner.pubkey;
422
+ }
423
+ if (!buyerOwner) {
424
+ return fail(
425
+ sawTransferToPayTo ? "signature_invalid" : "transfer_not_found",
426
+ sawTransferToPayTo ? "A TransferChecked to payTo's ATA is not authorized by a transaction signer." : `No TransferChecked of mint ${mint.toBase58()} (program ${program.toBase58()}) found.`
427
+ );
428
+ }
429
+ if (paidToPayTo < requiredAmount) {
430
+ return fail("amount_too_low", `Signed transfers pay ${paidToPayTo} to payTo, required ${requiredAmount}.`);
431
+ }
432
+ const buyerIndex = message.staticAccountKeys.findIndex((k) => k.equals(buyerOwner));
433
+ if (buyerIndex < 1 || buyerIndex >= message.header.numRequiredSignatures) {
434
+ return fail("signature_invalid", "The transfer authority is not a transaction signer.");
435
+ }
436
+ if (isEmptySig(tx.signatures[buyerIndex])) {
437
+ return fail("signature_invalid", "The buyer signature slot is empty.");
438
+ }
439
+ tx.sign([feePayerKeypair]);
440
+ try {
441
+ const sim = await connection.simulateTransaction(tx, { sigVerify: true, replaceRecentBlockhash: false, commitment: "confirmed" });
442
+ if (sim.value.err) {
443
+ const errStr = typeof sim.value.err === "string" ? sim.value.err : JSON.stringify(sim.value.err);
444
+ if (/blockhash|block height|expired/i.test(errStr)) return fail("payment_expired", `Transaction blockhash is no longer valid: ${shorten(errStr)}.`);
445
+ if (/signature/i.test(errStr)) return fail("signature_invalid", `Signature verification failed: ${shorten(errStr)}.`);
446
+ return fail("tx_reverted", `Transaction would fail on-chain: ${shorten(errStr)}.`);
447
+ }
448
+ } catch (err) {
449
+ const msg = err instanceof Error ? err.message : String(err);
450
+ if (/signature verification|invalid signature/i.test(msg)) return fail("signature_invalid", `Signature verification failed: ${shorten(msg)}.`);
451
+ if (/blockhash|block height|expired/i.test(msg)) return fail("payment_expired", `Transaction blockhash is no longer valid: ${shorten(msg)}.`);
452
+ return fail("tx_not_found", `Could not simulate the transaction (transient RPC) \u2014 retry: ${shorten(msg)}.`);
453
+ }
454
+ let txid;
455
+ try {
456
+ txid = await connection.sendRawTransaction(tx.serialize(), { maxRetries: 5 });
457
+ } catch (err) {
458
+ const msg = err instanceof Error ? err.message : String(err);
459
+ if (/blockhash|block height|expired/i.test(msg)) return fail("payment_expired", `Settle broadcast rejected \u2014 blockhash expired: ${shorten(msg)}.`);
460
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
461
+ `SVM exact settle: the merchant fee payer failed to broadcast (${shorten(msg)}). The buyer's signed transaction is still valid \u2014 fund/fix the fee payer and the buyer can re-present it.`,
462
+ { cause: err }
463
+ );
464
+ }
465
+ const confirmed = await pollConfirmed(connection, txid);
466
+ if (confirmed === "reverted") {
467
+ return fail("tx_reverted", `Settle tx ${txid} reverted on-chain (a post-simulate race).`);
468
+ }
469
+ if (confirmed === "timeout") {
470
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
471
+ `SVM exact settle: broadcast ${txid} but it did not confirm in time. It likely landed \u2014 re-verify by signature before re-presenting; do NOT re-pay.`
472
+ );
473
+ }
474
+ return {
475
+ ok: true,
476
+ receipt: {
477
+ scheme: "exact",
478
+ success: true,
479
+ network: accept.network,
480
+ transaction: txid,
481
+ asset: accept.asset,
482
+ amount: accept.amount,
483
+ payer: buyerOwner.toBase58(),
484
+ payTo: accept.payTo,
485
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
486
+ }
487
+ };
488
+ }
489
+ async function pollConfirmed(connection, signature) {
490
+ const deadline = Date.now() + 3e4;
491
+ for (; ; ) {
492
+ let info;
493
+ try {
494
+ const { value } = await connection.getSignatureStatuses([signature], { searchTransactionHistory: true });
495
+ info = value[0];
496
+ } catch (e6) {
497
+ info = null;
498
+ }
499
+ if (info) {
500
+ if (info.err) return "reverted";
501
+ if (info.confirmationStatus === "confirmed" || info.confirmationStatus === "finalized") return "ok";
502
+ }
503
+ if (Date.now() >= deadline) return "timeout";
504
+ await new Promise((r) => setTimeout(r, 1e3));
505
+ }
506
+ }
507
+
508
+ // src/drivers/solana/wallet.ts
509
+
510
+
511
+ function toKeypair(wallet, network) {
512
+ if (typeof wallet !== "object" || wallet === null) {
513
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
514
+ `chain ${network} is Solana; wallet must be { secretKey } or { signer }.`
515
+ );
516
+ }
517
+ if ("privateKey" in wallet || "walletClient" in wallet) {
518
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
519
+ `chain ${network} is Solana; an EVM wallet can't be used \u2014 pass { secretKey } or { signer }.`
520
+ );
521
+ }
522
+ if ("signer" in wallet) {
523
+ return wallet.signer;
524
+ }
525
+ if ("secretKey" in wallet) {
526
+ const sk = wallet.secretKey;
527
+ const bytes = typeof sk === "string" ? _bs582.default.decode(sk) : sk;
528
+ return _web3js.Keypair.fromSecretKey(bytes);
529
+ }
530
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
531
+ `chain ${network} is Solana; wallet must be { secretKey } or { signer }.`
532
+ );
533
+ }
534
+
535
+ // src/drivers/solana/index.ts
536
+ var solanaDriver = {
537
+ family: "solana",
538
+ resolve(opts) {
539
+ if (opts.chain !== "solana") return null;
540
+ const rpcUrl = _nullishCoalesce(opts.rpcUrl, () => ( SOLANA_MAINNET.defaultRpc));
541
+ return makeSolanaNetwork(SOLANA_MAINNET, rpcUrl);
542
+ }
543
+ };
544
+ function makeSolanaNetwork(preset, rpcUrl) {
545
+ const connection = new (0, _web3js.Connection)(rpcUrl, "confirmed");
546
+ const network = preset.caip2;
547
+ return {
548
+ family: "solana",
549
+ network,
550
+ supports: (n) => n === network,
551
+ resolveToken(token) {
552
+ if (token === "native") {
553
+ return { asset: "native", decimals: SOL_DECIMALS, symbol: "SOL" };
554
+ }
555
+ if (typeof token === "string") {
556
+ const info = preset.tokens[token.toUpperCase()];
557
+ if (!info) {
558
+ const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
559
+ throw new (0, _chunkPA6YD3HLcjs.UnknownTokenError)(
560
+ `token "${token}" isn't built in for Solana (known: ${known}). Pass { mint, decimals } instead, or use 'native'.`
561
+ );
562
+ }
563
+ return { asset: info.mint, decimals: info.decimals, symbol: info.symbol };
564
+ }
565
+ _chunkPA6YD3HLcjs.rejectForeignToken.call(void 0, token, "solana", network);
566
+ if (!("mint" in token)) {
567
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
568
+ `chain ${network} is Solana; a custom token must be { mint, decimals }.`
569
+ );
570
+ }
571
+ return {
572
+ asset: token.mint,
573
+ decimals: token.decimals,
574
+ ...token.symbol ? { symbol: token.symbol } : {}
575
+ };
576
+ },
577
+ describeAsset(asset) {
578
+ if (asset === "native") return { symbol: "SOL", decimals: SOL_DECIMALS };
579
+ for (const info of Object.values(preset.tokens)) {
580
+ if (info.mint === asset) return { symbol: info.symbol, decimals: info.decimals };
581
+ }
582
+ return null;
583
+ },
584
+ assertValidPayTo(payTo) {
585
+ if (payTo.startsWith("0x")) {
586
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
587
+ `chain ${network} is Solana, but payTo "${payTo}" looks like an EVM address.`
588
+ );
589
+ }
590
+ try {
591
+ new (0, _web3js.PublicKey)(payTo);
592
+ } catch (e7) {
593
+ throw new (0, _chunkPA6YD3HLcjs.WrongFamilyError)(
594
+ `chain ${network} is Solana, but payTo "${payTo}" is not a base58 address.`
595
+ );
596
+ }
597
+ },
598
+ bindWallet(wallet) {
599
+ return { _native: toKeypair(wallet, network) };
600
+ },
601
+ async send(wallet, accept) {
602
+ try {
603
+ return await paySolana({ connection, keypair: wallet._native, accept });
604
+ } catch (err) {
605
+ throw _nullishCoalesce(_chunkPA6YD3HLcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
606
+ }
607
+ },
608
+ async confirm(ref) {
609
+ let info;
610
+ try {
611
+ const { value } = await connection.getSignatureStatuses([ref], {
612
+ searchTransactionHistory: true
613
+ });
614
+ info = value[0];
615
+ } catch (err) {
616
+ throw new (0, _chunkPA6YD3HLcjs.ConfirmationTimeoutError)(
617
+ `Solana payment ${ref} could not be confirmed (RPC read failed).`,
618
+ { cause: err }
619
+ );
620
+ }
621
+ if (!info || info.err || info.confirmationStatus !== "confirmed" && info.confirmationStatus !== "finalized") {
622
+ throw new (0, _chunkPA6YD3HLcjs.ConfirmationTimeoutError)(`Solana payment ${ref} did not confirm in time.`);
623
+ }
624
+ return { height: String(info.slot) };
625
+ },
626
+ async estimateCost(accept) {
627
+ const base = 5000n;
628
+ if (accept.asset === "native") {
629
+ return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
630
+ symbol: "SOL",
631
+ decimals: SOL_DECIMALS,
632
+ fee: base,
633
+ basis: "heuristic",
634
+ detail: "1 signature (5000 lamports)"
635
+ });
636
+ }
637
+ const ataRent = 2039280n;
638
+ return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
639
+ symbol: "SOL",
640
+ decimals: SOL_DECIMALS,
641
+ fee: base + ataRent,
642
+ basis: "heuristic",
643
+ detail: "1 signature + recipient token-account rent (~0.00204 SOL, if not already created)"
644
+ });
645
+ },
646
+ async balanceOf(wallet, asset) {
647
+ const owner = wallet._native.publicKey;
648
+ const native = await connection.getBalance(owner).then((n) => BigInt(n)).catch(() => null);
649
+ if (asset === "native") return { token: native, native };
650
+ let token;
651
+ try {
652
+ const ata = _spltoken.getAssociatedTokenAddressSync.call(void 0, new (0, _web3js.PublicKey)(asset), owner);
653
+ token = (await _spltoken.getAccount.call(void 0, connection, ata, "confirmed")).amount;
654
+ } catch (e) {
655
+ token = e instanceof _spltoken.TokenAccountNotFoundError ? 0n : null;
656
+ }
657
+ return { token, native };
658
+ },
659
+ // No receive prerequisite — the payer's tx idempotently creates the recipient's ATA (pay.ts).
660
+ async recipientReady() {
661
+ return { ready: "n/a" };
662
+ },
663
+ async verify(ref, accept) {
664
+ return verifySolana({ connection, signature: ref, accept });
665
+ },
666
+ // Standard x402 `exact` rail, BUYER side — partial-sign an SPL TransferChecked with the
667
+ // merchant as fee payer (the buyer spends zero SOL). Never broadcasts. Throws
668
+ // UnsupportedSchemeError for native / a missing feePayer / feePayer === payTo.
669
+ async payExact(wallet, accept) {
670
+ const { payload, payerFrom, nonce } = await payExactSolana({
671
+ connection,
672
+ keypair: wallet._native,
673
+ accept
674
+ });
675
+ return { payload, accepted: accept, payerFrom, nonce };
676
+ },
677
+ // Standard x402 `exact` rail, SELLER side — verify the partial-signed tx against the
678
+ // trusted accept, then co-sign as the fee payer + broadcast (self-settle, no facilitator).
679
+ async settleExactSelf({ relayer, payload, accept }) {
680
+ if (!("transaction" in payload)) {
681
+ return { ok: false, error: "signature_invalid", detail: "SVM exact expects a { transaction } payload." };
682
+ }
683
+ return verifyAndSettleExactSolana({
684
+ connection,
685
+ feePayerKeypair: relayer._native,
686
+ payload,
687
+ accept
688
+ });
689
+ },
690
+ // The gate's rail-advertisement SPI. The SVM fee payer is EITHER a facilitator's sponsor
691
+ // pubkey (`feePayer` — facilitator mode, neither buyer nor merchant pays gas) OR the
692
+ // merchant's own bound `relayer` (self mode); native isn't exact-payable. Reads the mint's
693
+ // owner once to flag token-2022 (so both ends derive the same ATA). `null` ⇒ no exact rail.
694
+ async resolveExactRail({ asset, relayer, feePayer }) {
695
+ if (asset === "native") return null;
696
+ const fp = _nullishCoalesce(feePayer, () => ( (relayer ? relayer._native.publicKey.toBase58() : void 0)));
697
+ if (!fp) return null;
698
+ try {
699
+ new (0, _web3js.PublicKey)(fp);
700
+ } catch (e8) {
701
+ return null;
702
+ }
703
+ let tokenProgram = "spl-token";
704
+ try {
705
+ const info = await connection.getAccountInfo(new (0, _web3js.PublicKey)(asset));
706
+ if (_optionalChain([info, 'optionalAccess', _7 => _7.owner, 'access', _8 => _8.equals, 'call', _9 => _9(_spltoken.TOKEN_2022_PROGRAM_ID)])) tokenProgram = "token-2022";
707
+ } catch (e9) {
708
+ }
709
+ return { method: "svm", extra: { feePayer: fp, tokenProgram } };
710
+ }
711
+ };
712
+ }
713
+
714
+
715
+ exports.solanaDriver = solanaDriver;