@piprail/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,449 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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
+ var _chunkCQREG5LEcjs = require('./chunk-CQREG5LE.cjs');
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ var _chunkWQWNPAYQcjs = require('./chunk-WQWNPAYQ.cjs');
14
+
15
+ // src/drivers/xrpl/index.ts
16
+ var _xrpl = require('xrpl');
17
+
18
+ // src/drivers/xrpl/chains.ts
19
+ var XRP_DECIMALS = 6;
20
+ var XRP_SYMBOL = "XRP";
21
+ var XRPL_MAINNET = {
22
+ caip2: "xrpl:0",
23
+ // Public community cluster (serves HTTPS JSON-RPC). Not for sustained
24
+ // business use — pass your own `rpcUrl` in production.
25
+ defaultRpc: "https://xrplcluster.com/",
26
+ tokens: {
27
+ // Circle USDC — issuer Domain (`https://circle.com`) + currency code verified
28
+ // live on mainnet (gateway_balances) before shipping.
29
+ USDC: {
30
+ issuer: "rGm7WCVp9gb4jZHWTEtGUr4dd74z2XuWhE",
31
+ currencyHex: "5553444300000000000000000000000000000000",
32
+ decimals: 6,
33
+ symbol: "USDC"
34
+ },
35
+ // Ripple RLUSD — issuer Domain (`https://ripple.com/`) + currency code
36
+ // verified live on mainnet before shipping. Note: the RLUSD issuer sets
37
+ // requireDestinationTag, so payments always carry a DestinationTag (set below).
38
+ RLUSD: {
39
+ issuer: "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
40
+ currencyHex: "524C555344000000000000000000000000000000",
41
+ decimals: 6,
42
+ symbol: "RLUSD"
43
+ }
44
+ }
45
+ };
46
+ function xrplAssetId(currencyHex, issuer) {
47
+ return `${currencyHex}:${issuer}`;
48
+ }
49
+ function parseXrplAssetId(asset) {
50
+ if (asset === "native") return null;
51
+ const i = asset.indexOf(":");
52
+ if (i <= 0 || i === asset.length - 1) return null;
53
+ return { currencyHex: asset.slice(0, i), issuer: asset.slice(i + 1) };
54
+ }
55
+
56
+ // src/drivers/xrpl/pay.ts
57
+ function nonceToMemoHex(nonce) {
58
+ return Buffer.from(nonce, "utf8").toString("hex").toUpperCase();
59
+ }
60
+ function nonceToDestinationTag(nonce) {
61
+ let h = 2166136261;
62
+ for (let i = 0; i < nonce.length; i += 1) {
63
+ h ^= nonce.charCodeAt(i);
64
+ h = Math.imul(h, 16777619);
65
+ }
66
+ const tag = h >>> 0;
67
+ return tag === 0 ? 1 : tag;
68
+ }
69
+ async function payXrpl(params) {
70
+ const { client, wallet, accept } = params;
71
+ try {
72
+ const [sequence, feeDrops, ledgerIndex] = await Promise.all([
73
+ client.accountSequence(wallet.classicAddress),
74
+ client.feeDrops(),
75
+ client.currentLedgerIndex()
76
+ ]);
77
+ const tx = {
78
+ TransactionType: "Payment",
79
+ Account: wallet.classicAddress,
80
+ Destination: accept.payTo,
81
+ DestinationTag: nonceToDestinationTag(accept.extra.nonce),
82
+ Amount: amountForAccept(accept),
83
+ Memos: [{ Memo: { MemoData: nonceToMemoHex(accept.extra.nonce) } }],
84
+ Sequence: sequence,
85
+ Fee: feeForSubmit(feeDrops),
86
+ LastLedgerSequence: ledgerIndex + 20,
87
+ Flags: 0
88
+ // NOT tfPartialPayment — require full delivery
89
+ };
90
+ const signed = wallet.sign(tx);
91
+ const res = await client.submit(signed.tx_blob);
92
+ const code = res.engine_result;
93
+ if (code.startsWith("tes")) return _nullishCoalesce(_optionalChain([res, 'access', _ => _.tx_json, 'optionalAccess', _2 => _2.hash]), () => ( signed.hash));
94
+ if (isAffordabilityCode(code)) {
95
+ throw new (0, _chunkWQWNPAYQcjs.InsufficientFundsError)(
96
+ `XRPL payment rejected (${code}): insufficient balance/reserve, or no trustline for this asset.`
97
+ );
98
+ }
99
+ throw new Error(
100
+ `XRPL payment rejected: ${code}${res.engine_result_message ? ` \u2014 ${res.engine_result_message}` : ""}`
101
+ );
102
+ } catch (err) {
103
+ if (err instanceof _chunkWQWNPAYQcjs.InsufficientFundsError) throw err;
104
+ throw _nullishCoalesce(_chunkWQWNPAYQcjs.toInsufficientFundsError.call(void 0, err), () => ( err));
105
+ }
106
+ }
107
+ function amountForAccept(accept) {
108
+ if (accept.asset === "native") return accept.amount;
109
+ const parts = parseXrplAssetId(accept.asset);
110
+ if (!parts) throw new Error(`XRPL: malformed asset id "${accept.asset}".`);
111
+ return {
112
+ currency: parts.currencyHex,
113
+ issuer: parts.issuer,
114
+ value: accept.extra.amountFormatted
115
+ };
116
+ }
117
+ function feeForSubmit(openLedgerFee) {
118
+ const fee = Number(openLedgerFee);
119
+ return String(Number.isFinite(fee) && fee > 12 ? Math.ceil(fee) : 12);
120
+ }
121
+ function isAffordabilityCode(code) {
122
+ return /^(tecUNFUNDED|tecPATH_DRY|tecPATH_PARTIAL|tecINSUFF|tecNO_LINE_INSUF|terINSUF|tecNO_LINE)/.test(
123
+ code
124
+ );
125
+ }
126
+
127
+ // src/drivers/xrpl/verify.ts
128
+ var RIPPLE_EPOCH_OFFSET = 946684800;
129
+ async function verifyXrpl(params) {
130
+ const { reader, accept } = params;
131
+ const required = BigInt(accept.amount);
132
+ const nonce = accept.extra.nonce;
133
+ const wantMemo = nonceToMemoHex(nonce);
134
+ const wantAsset = parseXrplAssetId(accept.asset);
135
+ let txs;
136
+ try {
137
+ txs = await reader.transactionsForAccount(accept.payTo, 50);
138
+ } catch (e2) {
139
+ return rpcFailed(nonce);
140
+ }
141
+ const tx = txs.find(
142
+ (t) => t.TransactionType === "Payment" && t.Destination === accept.payTo && hasNonceMemo(t.Memos, wantMemo)
143
+ );
144
+ if (!tx) return notFound(nonce);
145
+ if (!tx.validated) {
146
+ return {
147
+ ok: false,
148
+ error: "insufficient_confirmations",
149
+ detail: `XRPL tx ${tx.hash} for nonce ${nonce} is not yet validated \u2014 retry.`
150
+ };
151
+ }
152
+ if (tx.result !== "tesSUCCESS") {
153
+ return {
154
+ ok: false,
155
+ error: "tx_reverted",
156
+ detail: `XRPL tx ${tx.hash} for nonce ${nonce} failed on-ledger (${tx.result}).`
157
+ };
158
+ }
159
+ if (typeof tx.date === "number") {
160
+ const ageSeconds = Math.floor(Date.now() / 1e3) - (tx.date + RIPPLE_EPOCH_OFFSET);
161
+ if (ageSeconds > accept.maxTimeoutSeconds) {
162
+ return {
163
+ ok: false,
164
+ error: "payment_expired",
165
+ detail: `Payment is ${ageSeconds}s old; max allowed is ${accept.maxTimeoutSeconds}s.`
166
+ };
167
+ }
168
+ }
169
+ const paid = deliveredBaseUnits(tx.delivered_amount, wantAsset, accept.extra.decimals);
170
+ if (paid === null) {
171
+ return {
172
+ ok: false,
173
+ error: "transfer_not_found",
174
+ detail: `XRPL tx ${tx.hash} carries our nonce but delivered no matching ${wantAsset ? "IOU" : "XRP"} to ${accept.payTo} (wrong asset, or delivered_amount unavailable).`
175
+ };
176
+ }
177
+ if (paid < required) {
178
+ return {
179
+ ok: false,
180
+ error: "amount_too_low",
181
+ detail: `Delivered ${paid}, required ${required}.`
182
+ };
183
+ }
184
+ return {
185
+ ok: true,
186
+ receipt: {
187
+ scheme: "onchain-proof",
188
+ success: true,
189
+ network: accept.network,
190
+ transaction: tx.hash,
191
+ asset: accept.asset,
192
+ amount: accept.amount,
193
+ payer: tx.Account,
194
+ payTo: accept.payTo,
195
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
196
+ }
197
+ };
198
+ }
199
+ function hasNonceMemo(memos, wantMemo) {
200
+ if (!memos) return false;
201
+ return memos.some((m) => (_nullishCoalesce(_optionalChain([m, 'access', _3 => _3.Memo, 'optionalAccess', _4 => _4.MemoData]), () => ( ""))).toUpperCase() === wantMemo);
202
+ }
203
+ function deliveredBaseUnits(delivered, want, decimals) {
204
+ if (delivered === void 0) return null;
205
+ if (want === null) {
206
+ if (typeof delivered !== "string") return null;
207
+ if (!/^\d+$/.test(delivered)) return null;
208
+ return BigInt(delivered);
209
+ }
210
+ if (typeof delivered === "string") return null;
211
+ if (delivered.currency.toUpperCase() !== want.currencyHex.toUpperCase() || delivered.issuer !== want.issuer) {
212
+ return null;
213
+ }
214
+ try {
215
+ return _chunkWQWNPAYQcjs.floorUnits.call(void 0, delivered.value, decimals);
216
+ } catch (e3) {
217
+ return null;
218
+ }
219
+ }
220
+ function notFound(nonce) {
221
+ return {
222
+ ok: false,
223
+ error: "transfer_not_found",
224
+ detail: `No matching XRPL payment found for nonce ${nonce} (not yet validated, or wrong recipient/amount/asset/memo).`
225
+ };
226
+ }
227
+ function rpcFailed(nonce) {
228
+ return {
229
+ ok: false,
230
+ error: "tx_not_found",
231
+ detail: `Could not read XRPL for nonce ${nonce} (transient RPC failure) \u2014 retry.`
232
+ };
233
+ }
234
+
235
+ // src/drivers/xrpl/wallet.ts
236
+
237
+ function assertXrplWallet(wallet, network) {
238
+ if (typeof wallet !== "object" || wallet === null) {
239
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
240
+ `chain ${network} is XRPL; wallet must be { seed } (s\u2026 seed) or { wallet }.`
241
+ );
242
+ }
243
+ if ("privateKey" in wallet || "walletClient" in wallet) {
244
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
245
+ `chain ${network} is XRPL; an EVM wallet can't be used \u2014 pass { seed } (s\u2026 seed) or { wallet }.`
246
+ );
247
+ }
248
+ if ("secretKey" in wallet || "signer" in wallet || "mnemonic" in wallet || "keyPair" in wallet || "secret" in wallet || "keypair" in wallet) {
249
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
250
+ `chain ${network} is XRPL; that looks like a Solana/TON/Stellar wallet \u2014 pass { seed } (s\u2026 seed) or { wallet }.`
251
+ );
252
+ }
253
+ if (!("seed" in wallet) && !("wallet" in wallet)) {
254
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
255
+ `chain ${network} is XRPL; wallet must be { seed } (s\u2026 seed) or { wallet }.`
256
+ );
257
+ }
258
+ return wallet;
259
+ }
260
+ function resolveXrplWallet(config) {
261
+ if (config.wallet) return config.wallet;
262
+ if (config.seed) {
263
+ if (!_xrpl.isValidSecret.call(void 0, config.seed)) {
264
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)("XRPL wallet { seed } is not a valid s\u2026 secret seed.");
265
+ }
266
+ return _xrpl.Wallet.fromSeed(config.seed);
267
+ }
268
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)("XRPL wallet needs { seed } (s\u2026 seed) or { wallet }.");
269
+ }
270
+
271
+ // src/drivers/xrpl/index.ts
272
+ var xrplDriver = {
273
+ family: "xrpl",
274
+ resolve(opts) {
275
+ if (opts.chain !== "xrpl") return null;
276
+ const rpcUrl = _nullishCoalesce(opts.rpcUrl, () => ( XRPL_MAINNET.defaultRpc));
277
+ return makeXrplNetwork(XRPL_MAINNET, rpcUrl);
278
+ }
279
+ };
280
+ function makeXrplNetwork(preset, rpcUrl) {
281
+ const network = preset.caip2;
282
+ async function rpc(method, params) {
283
+ const res = await fetch(rpcUrl, {
284
+ method: "POST",
285
+ headers: { "content-type": "application/json" },
286
+ body: JSON.stringify({ method, params: [params] })
287
+ });
288
+ if (!res.ok) throw new Error(`XRPL RPC ${method} HTTP ${res.status}`);
289
+ const json = await res.json();
290
+ const result = json.result;
291
+ if (!result || result.status === "error" || result.error) {
292
+ throw new Error(`XRPL RPC ${method} error: ${_nullishCoalesce(_optionalChain([result, 'optionalAccess', _5 => _5.error]), () => ( "unknown"))}`);
293
+ }
294
+ return result;
295
+ }
296
+ const payClient = {
297
+ async accountSequence(account) {
298
+ const r = await rpc("account_info", {
299
+ account,
300
+ ledger_index: "current"
301
+ });
302
+ return r.account_data.Sequence;
303
+ },
304
+ async feeDrops() {
305
+ const r = await rpc("fee", {});
306
+ return r.drops.open_ledger_fee;
307
+ },
308
+ async currentLedgerIndex() {
309
+ const r = await rpc("ledger_current", {});
310
+ return r.ledger_current_index;
311
+ },
312
+ async submit(txBlob) {
313
+ return rpc("submit", { tx_blob: txBlob });
314
+ }
315
+ };
316
+ const reader = {
317
+ async transactionsForAccount(account, limit) {
318
+ const r = await rpc("account_tx", {
319
+ account,
320
+ ledger_index_min: -1,
321
+ ledger_index_max: -1,
322
+ forward: false,
323
+ limit
324
+ });
325
+ return r.transactions.map(mapAccountTxEntry);
326
+ }
327
+ };
328
+ return {
329
+ family: "xrpl",
330
+ network,
331
+ supports: (n) => n === network,
332
+ resolveToken(token) {
333
+ if (token === "native") {
334
+ return { asset: "native", decimals: XRP_DECIMALS, symbol: XRP_SYMBOL };
335
+ }
336
+ if (typeof token === "string") {
337
+ const info = preset.tokens[token.toUpperCase()];
338
+ if (!info) {
339
+ const known = Object.keys(preset.tokens).join(", ") || "(none built in)";
340
+ throw new (0, _chunkWQWNPAYQcjs.UnknownTokenError)(
341
+ `token "${token}" isn't built in for XRPL (known: ${known}). Pass { issuer, currencyHex, decimals } for a custom IOU, or use 'native'.`
342
+ );
343
+ }
344
+ return {
345
+ asset: xrplAssetId(info.currencyHex, info.issuer),
346
+ decimals: info.decimals,
347
+ symbol: info.symbol
348
+ };
349
+ }
350
+ _chunkWQWNPAYQcjs.rejectForeignToken.call(void 0, token, "xrpl", network);
351
+ const t = token;
352
+ if (!t.issuer || !t.currencyHex || typeof t.decimals !== "number") {
353
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
354
+ `chain ${network} is XRPL; a custom token must be { issuer, currencyHex, decimals }.`
355
+ );
356
+ }
357
+ return {
358
+ asset: xrplAssetId(t.currencyHex, t.issuer),
359
+ decimals: t.decimals,
360
+ symbol: _nullishCoalesce(t.symbol, () => ( t.currencyHex))
361
+ };
362
+ },
363
+ describeAsset(asset) {
364
+ if (asset === "native") return { symbol: XRP_SYMBOL, decimals: XRP_DECIMALS };
365
+ for (const info of Object.values(preset.tokens)) {
366
+ if (xrplAssetId(info.currencyHex, info.issuer) === asset) {
367
+ return { symbol: info.symbol, decimals: info.decimals };
368
+ }
369
+ }
370
+ return null;
371
+ },
372
+ assertValidPayTo(payTo) {
373
+ if (payTo.startsWith("0x")) {
374
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
375
+ `chain ${network} is XRPL, but payTo "${payTo}" looks like an EVM address.`
376
+ );
377
+ }
378
+ if (!_xrpl.isValidClassicAddress.call(void 0, payTo)) {
379
+ throw new (0, _chunkWQWNPAYQcjs.WrongFamilyError)(
380
+ `chain ${network} is XRPL, but payTo "${payTo}" is not a valid XRPL account (r\u2026).`
381
+ );
382
+ }
383
+ },
384
+ bindWallet(wallet) {
385
+ return { _native: assertXrplWallet(wallet, network) };
386
+ },
387
+ async send(wallet, accept) {
388
+ const w = resolveXrplWallet(wallet._native);
389
+ return payXrpl({ client: payClient, wallet: w, accept });
390
+ },
391
+ async confirm(ref) {
392
+ for (let i = 0; i < 12; i += 1) {
393
+ try {
394
+ const tx = await rpc("tx", {
395
+ transaction: ref
396
+ });
397
+ if (tx.validated) return { height: String(_nullishCoalesce(tx.ledger_index, () => ( 0))) };
398
+ } catch (e4) {
399
+ }
400
+ await _chunkCQREG5LEcjs.delay.call(void 0, 1500);
401
+ }
402
+ throw new (0, _chunkWQWNPAYQcjs.ConfirmationTimeoutError)(`XRPL tx ${ref} not validated on-ledger in time.`);
403
+ },
404
+ async estimateCost() {
405
+ try {
406
+ const drops = await payClient.feeDrops();
407
+ const n = Number(drops);
408
+ const fee = BigInt(Number.isFinite(n) && n > 12 ? Math.ceil(n) : 12);
409
+ return _chunkWQWNPAYQcjs.nativeCost.call(void 0, {
410
+ symbol: XRP_SYMBOL,
411
+ decimals: XRP_DECIMALS,
412
+ fee,
413
+ basis: "estimated",
414
+ detail: `network fee ${fee} drops`
415
+ });
416
+ } catch (e5) {
417
+ return _chunkWQWNPAYQcjs.nativeCost.call(void 0, {
418
+ symbol: XRP_SYMBOL,
419
+ decimals: XRP_DECIMALS,
420
+ fee: 12n,
421
+ basis: "heuristic",
422
+ detail: "~12 drops (open-ledger fee unavailable)"
423
+ });
424
+ }
425
+ },
426
+ async verify(_ref, accept) {
427
+ return verifyXrpl({ reader, accept });
428
+ }
429
+ };
430
+ }
431
+ function mapAccountTxEntry(entry) {
432
+ const e = entry;
433
+ const t = _nullishCoalesce(_nullishCoalesce(e.tx_json, () => ( e.tx)), () => ( {}));
434
+ const meta = _nullishCoalesce(_nullishCoalesce(e.meta, () => ( e.metaData)), () => ( {}));
435
+ return {
436
+ hash: _nullishCoalesce(_nullishCoalesce(e.hash, () => ( t.hash)), () => ( "")),
437
+ validated: Boolean(e.validated),
438
+ result: _nullishCoalesce(meta.TransactionResult, () => ( "")),
439
+ TransactionType: _nullishCoalesce(t.TransactionType, () => ( "")),
440
+ Account: _nullishCoalesce(t.Account, () => ( "")),
441
+ Destination: t.Destination,
442
+ Memos: t.Memos,
443
+ delivered_amount: _nullishCoalesce(meta.delivered_amount, () => ( meta.DeliveredAmount)),
444
+ date: typeof t.date === "number" ? t.date : void 0
445
+ };
446
+ }
447
+
448
+
449
+ exports.xrplDriver = xrplDriver;