@piprail/sdk 2.16.2 → 3.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +343 -0
  2. package/README.md +1 -0
  3. package/dist/{algorand-25FMBCT3.js → algorand-HL57PQHE.js} +157 -2
  4. package/dist/{algorand-Q3TUQLPK.cjs → algorand-W776EEM2.cjs} +205 -50
  5. package/dist/{aptos-VBJONBFY.cjs → aptos-5QBXJ6NV.cjs} +255 -39
  6. package/dist/{aptos-SBV6SGDP.js → aptos-H5VLH2QL.js} +217 -1
  7. package/dist/{chunk-QONQSZHJ.cjs → chunk-6XTNI2OQ.cjs} +44 -44
  8. package/dist/{chunk-OXEFPLZA.cjs → chunk-6ZRAIQXF.cjs} +4 -4
  9. package/dist/chunk-GVCGUSTE.js +30 -0
  10. package/dist/{chunk-V2IJ5HUW.cjs → chunk-MZAJQYM3.cjs} +1 -0
  11. package/dist/chunk-MZXVXC3C.cjs +30 -0
  12. package/dist/{chunk-2CX7XRZK.js → chunk-TZDVZCTC.js} +1 -0
  13. package/dist/{chunk-C52H5TYB.js → chunk-YXBQKBDH.js} +2 -2
  14. package/dist/index.cjs +2929 -1444
  15. package/dist/index.d.cts +625 -13
  16. package/dist/index.d.ts +625 -13
  17. package/dist/index.js +2506 -1021
  18. package/dist/{near-OLKCMTBI.js → near-5X2CQGML.js} +197 -1
  19. package/dist/{near-5LTTDU6G.cjs → near-XSQXEIHT.cjs} +242 -46
  20. package/dist/{solana-AI2G7V33.cjs → solana-EBV6PUCU.cjs} +211 -52
  21. package/dist/{solana-HTKDRTD3.js → solana-O6Q6QILH.js} +167 -8
  22. package/dist/{stellar-E2KWEV2E.cjs → stellar-5C7FQLPS.cjs} +191 -29
  23. package/dist/{stellar-EUFZLX6J.js → stellar-YF5LOJEM.js} +163 -1
  24. package/dist/{sui-KCIITCYH.js → sui-JFQSDNSZ.js} +165 -3
  25. package/dist/{sui-Q3NJOJZS.cjs → sui-WIZEAO7E.cjs} +185 -23
  26. package/dist/{ton-J7TQWRN4.cjs → ton-AGTKLQA5.cjs} +264 -22
  27. package/dist/{ton-WH2JVQOO.js → ton-OZKFNRLT.js} +244 -2
  28. package/dist/{tron-FXBXDNEY.js → tron-HG3IOOCG.js} +283 -1
  29. package/dist/{tron-HIPMOX7S.cjs → tron-S4WX7OHY.cjs} +323 -41
  30. package/dist/{xrpl-RUOB37QH.js → xrpl-GOVHMYYK.js} +222 -5
  31. package/dist/{xrpl-SVVS445B.cjs → xrpl-YS3IXPLV.cjs} +273 -56
  32. package/package.json +12 -2
  33. package/dist/{chunk-QU25LSVS.js → chunk-5GRBEMCA.js} +44 -44
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  exactTransferMethod
3
- } from "./chunk-C52H5TYB.js";
4
- import "./chunk-2CX7XRZK.js";
3
+ } from "./chunk-YXBQKBDH.js";
4
+ import "./chunk-TZDVZCTC.js";
5
+ import {
6
+ applySlippage
7
+ } from "./chunk-GVCGUSTE.js";
5
8
  import {
6
9
  delay
7
10
  } from "./chunk-FURB5RP7.js";
@@ -15,10 +18,11 @@ import {
15
18
  WrongFamilyError,
16
19
  assertNoLegacyWalletKey,
17
20
  floorUnits,
21
+ formatUnits,
18
22
  nativeCost,
19
23
  rejectForeignToken,
20
24
  toInsufficientFundsError
21
- } from "./chunk-QU25LSVS.js";
25
+ } from "./chunk-5GRBEMCA.js";
22
26
 
23
27
  // src/drivers/xrpl/index.ts
24
28
  import { isValidClassicAddress } from "xrpl";
@@ -150,6 +154,197 @@ function isAffordabilityCode(code) {
150
154
  return /^(tecUNFUNDED|tecINSUFF|terINSUF)/.test(code);
151
155
  }
152
156
 
157
+ // src/drivers/xrpl/swap.ts
158
+ var SOURCE = {
159
+ kind: "protocol",
160
+ name: "XRPL DEX + AMM",
161
+ note: "Rate from the ledger\u2019s own order books and AMM pools, read live via ripple_path_find, with auto-bridging through XRP where cheaper. Order books charge no protocol fee; AMM pools charge a per-pool fee (0%\u20131%) paid to the pool."
162
+ };
163
+ function toAmount(t, base) {
164
+ if (t.asset === "native") return base.toString();
165
+ const parts = parseXrplAssetId(t.asset);
166
+ if (!parts) throw new Error(`XRPL: malformed asset id "${t.asset}".`);
167
+ return { currency: parts.currencyHex, issuer: parts.issuer, value: formatUnits(base, t.decimals) };
168
+ }
169
+ function parseCeil(value, decimals) {
170
+ const [whole = "0", frac = ""] = value.trim().split(".");
171
+ const kept = frac.slice(0, decimals).padEnd(decimals, "0");
172
+ const rest = frac.slice(decimals);
173
+ const base = BigInt(whole + kept);
174
+ return /[1-9]/.test(rest) ? base + 1n : base;
175
+ }
176
+ function fromAmount(t, a) {
177
+ if (a == null) return null;
178
+ try {
179
+ return typeof a === "string" ? BigInt(a) : parseCeil(a.value, t.decimals);
180
+ } catch {
181
+ return null;
182
+ }
183
+ }
184
+ function sourceCurrency(t) {
185
+ if (t.asset === "native") return { currency: "XRP" };
186
+ const parts = parseXrplAssetId(t.asset);
187
+ if (!parts) throw new Error(`XRPL: malformed asset id "${t.asset}".`);
188
+ return { currency: parts.currencyHex, issuer: parts.issuer };
189
+ }
190
+ function side(t, amount) {
191
+ return {
192
+ asset: t.asset,
193
+ symbol: t.symbol ?? (t.asset === "native" ? XRP_SYMBOL : t.asset),
194
+ decimals: t.asset === "native" ? XRP_DECIMALS : t.decimals,
195
+ amount: amount.toString(),
196
+ amountFormatted: formatUnits(amount, t.asset === "native" ? XRP_DECIMALS : t.decimals)
197
+ };
198
+ }
199
+ async function quoteXrplSwap(p) {
200
+ if (p.from.asset === p.to.asset) return null;
201
+ if (p.wantAmount <= 0n) return null;
202
+ let alternatives = [];
203
+ for (let attempt = 0; attempt < 2; attempt += 1) {
204
+ try {
205
+ const res = await p.client.pathFind({
206
+ source_account: p.owner,
207
+ destination_account: p.owner,
208
+ // ← ourselves: this is what makes it a swap
209
+ destination_amount: toAmount(p.to, p.wantAmount),
210
+ source_currencies: [sourceCurrency(p.from)]
211
+ });
212
+ alternatives = res.alternatives ?? [];
213
+ } catch {
214
+ alternatives = [];
215
+ }
216
+ if (alternatives.length) break;
217
+ if (attempt === 0) await new Promise((r) => setTimeout(r, 600));
218
+ }
219
+ if (!alternatives.length) return null;
220
+ let best = null;
221
+ let bestSpend = null;
222
+ for (const alt of alternatives) {
223
+ const spend = fromAmount(p.from, alt.source_amount);
224
+ if (spend == null || spend <= 0n) continue;
225
+ if (bestSpend == null || spend < bestSpend) {
226
+ bestSpend = spend;
227
+ best = alt;
228
+ }
229
+ }
230
+ if (!best || bestSpend == null) return null;
231
+ const maxSpend = applySlippage(bestSpend, p.slippageBps);
232
+ return {
233
+ source: SOURCE,
234
+ network: p.network,
235
+ from: side(p.from, bestSpend),
236
+ to: side(p.to, p.wantAmount),
237
+ maxSpend: maxSpend.toString(),
238
+ maxSpendFormatted: formatUnits(maxSpend, p.from.asset === "native" ? XRP_DECIMALS : p.from.decimals),
239
+ slippageBps: p.slippageBps,
240
+ // `paths_computed` drops straight into the transaction's Paths field.
241
+ route: { paths: best.paths_computed ?? [], from: p.from, to: p.to }
242
+ };
243
+ }
244
+ function sanitizePaths(paths) {
245
+ if (!Array.isArray(paths)) return [];
246
+ return paths.map(
247
+ (path) => (Array.isArray(path) ? path : []).map((raw) => {
248
+ const step = raw;
249
+ const clean = {};
250
+ if (typeof step.account === "string") clean.account = step.account;
251
+ if (typeof step.currency === "string") clean.currency = step.currency;
252
+ if (typeof step.issuer === "string") clean.issuer = step.issuer;
253
+ return clean;
254
+ }).filter((step) => Object.keys(step).length > 0)
255
+ ).filter((path) => path.length > 0);
256
+ }
257
+ async function swapXrpl(p) {
258
+ const { client, wallet, quote } = p;
259
+ const route = quote.route;
260
+ if (!route?.from || !route?.to) {
261
+ throw new Error("XRPL: swap quote is missing its routing data \u2014 re-quote before swapping.");
262
+ }
263
+ try {
264
+ const [sequence, feeDrops, ledgerIndex] = await Promise.all([
265
+ client.accountSequence(wallet.classicAddress),
266
+ client.feeDrops(),
267
+ client.currentLedgerIndex()
268
+ ]);
269
+ const tx = {
270
+ TransactionType: "Payment",
271
+ Account: wallet.classicAddress,
272
+ Destination: wallet.classicAddress,
273
+ // ← ourselves: the ledger's own swap idiom
274
+ Amount: toAmount(route.to, BigInt(quote.to.amount)),
275
+ // exact output
276
+ SendMax: toAmount(route.from, BigInt(quote.maxSpend)),
277
+ // the on-chain slippage cap
278
+ // Sanitized: raw path steps from ripple_path_find can break xrpl.js encoding.
279
+ ...sanitizePaths(route.paths).length ? { Paths: sanitizePaths(route.paths) } : {},
280
+ Sequence: sequence,
281
+ Fee: feeForSubmit2(feeDrops),
282
+ LastLedgerSequence: ledgerIndex + 20,
283
+ // NOT tfPartialPayment: we want the full `Amount` delivered or nothing at all.
284
+ Flags: 0
285
+ };
286
+ let signed;
287
+ try {
288
+ signed = wallet.sign(tx);
289
+ } catch (cause) {
290
+ const shape = Object.fromEntries(
291
+ Object.entries(tx).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : `${typeof v}:${String(v)}`])
292
+ );
293
+ throw new Error(
294
+ `XRPL could not encode the swap transaction: ${String(cause?.message ?? cause)}. Transaction was ${JSON.stringify(shape)}`,
295
+ { cause }
296
+ );
297
+ }
298
+ const res = await client.submit(signed.tx_blob);
299
+ const code = res.engine_result;
300
+ if (code.startsWith("tes")) {
301
+ return {
302
+ transaction: res.tx_json?.hash ?? signed.hash,
303
+ network: quote.network,
304
+ source: quote.source,
305
+ from: quote.from,
306
+ to: quote.to
307
+ };
308
+ }
309
+ throw mapEngineResult(code, res);
310
+ } catch (err) {
311
+ if (err instanceof InsufficientFundsError || err instanceof RecipientNotReadyError) throw err;
312
+ throw toInsufficientFundsError(err) ?? err;
313
+ }
314
+ }
315
+ function feeForSubmit2(openLedgerFee) {
316
+ try {
317
+ const f = BigInt(openLedgerFee);
318
+ const padded = f * 2n;
319
+ return (padded < 10n ? 10n : padded).toString();
320
+ } catch {
321
+ return "12";
322
+ }
323
+ }
324
+ function mapEngineResult(code, res) {
325
+ const err = new Error(`XRPL swap rejected: ${code}`);
326
+ err.cause = res;
327
+ if (code === "tecPATH_PARTIAL" || code === "tecPATH_DRY") {
328
+ return new InsufficientFundsError(
329
+ `XRPL swap refused: the price moved past your slippage cap, or the path ran dry, so nothing was spent. Re-quote and retry, or raise slippageBps. (XRPL: ${code})`,
330
+ { cause: res }
331
+ );
332
+ }
333
+ if (code === "tecNO_LINE" || code === "tecNO_AUTH" || code === "tecNO_ISSUER") {
334
+ return new RecipientNotReadyError(
335
+ `XRPL swap refused: your account needs a trustline for the currency you're swapping INTO before it can hold it. (XRPL: ${code})`,
336
+ { cause: res }
337
+ );
338
+ }
339
+ if (code === "tecUNFUNDED_PAYMENT" || code === "terINSUF_FEE_B" || code === "tecINSUFF_FEE") {
340
+ return new InsufficientFundsError(
341
+ `XRPL swap failed: the account can't cover it \u2014 balance or the 1 XRP base reserve. (XRPL: ${code})`,
342
+ { cause: res }
343
+ );
344
+ }
345
+ return err;
346
+ }
347
+
153
348
  // src/drivers/xrpl/exact.ts
154
349
  import { createHash } from "crypto";
155
350
  var SECONDS_PER_LEDGER = 4;
@@ -170,7 +365,7 @@ function feesAreSponsored(accept) {
170
365
  function invoiceIdHash(invoiceId) {
171
366
  return createHash("sha256").update(invoiceId, "utf8").digest("hex").toUpperCase();
172
367
  }
173
- function feeForSubmit2(openLedgerFee) {
368
+ function feeForSubmit3(openLedgerFee) {
174
369
  const fee = Number(openLedgerFee);
175
370
  const chosen = Number.isFinite(fee) && fee > MIN_FEE_DROPS ? Math.ceil(fee) : MIN_FEE_DROPS;
176
371
  return String(Math.min(chosen, MAX_FEE_DROPS));
@@ -224,7 +419,7 @@ async function payExactXrpl(input) {
224
419
  Amount: accept.amount,
225
420
  // integer drops, verbatim from the trusted accept
226
421
  Sequence: sequence,
227
- Fee: feeForSubmit2(openLedgerFee),
422
+ Fee: feeForSubmit3(openLedgerFee),
228
423
  LastLedgerSequence: ledgerIndex + ledgerWindowFor(accept.maxTimeoutSeconds),
229
424
  // tfFullyCanonicalSig ON, tfPartialPayment OFF — the shape every other XRPL client sends.
230
425
  Flags: TF_FULLY_CANONICAL_SIG
@@ -525,6 +720,9 @@ function makeXrplNetwork(preset, rpcUrl) {
525
720
  return result;
526
721
  }
527
722
  const payClient = {
723
+ async pathFind(params) {
724
+ return rpc("ripple_path_find", { ...params, ledger_index: "current" });
725
+ },
528
726
  async accountSequence(account) {
529
727
  const r = await rpc("account_info", {
530
728
  account,
@@ -713,6 +911,11 @@ function makeXrplNetwork(preset, rpcUrl) {
713
911
  });
714
912
  }
715
913
  },
914
+ /** The bound wallet's own address — where THIS wallet gets paid. Derived from the key
915
+ * material only: no RPC, nothing moved. See {@link ResolvedNetwork.addressOf}. */
916
+ async addressOf(wallet) {
917
+ return resolveXrplWallet(wallet._native).classicAddress;
918
+ },
716
919
  async balanceOf(wallet, asset) {
717
920
  let owner;
718
921
  try {
@@ -769,6 +972,20 @@ function makeXrplNetwork(preset, rpcUrl) {
769
972
  return { ready: "unknown" };
770
973
  }
771
974
  },
975
+ /* ---- swap (OPTIONAL, opt-in): a cross-currency payment to yourself. See ./swap.ts ---- */
976
+ async quoteSwap({ from, to, wantAmount, slippageBps, wallet }) {
977
+ let owner;
978
+ try {
979
+ owner = resolveXrplWallet(wallet._native).classicAddress;
980
+ } catch {
981
+ return null;
982
+ }
983
+ return quoteXrplSwap({ client: payClient, network, owner, from, to, wantAmount, slippageBps });
984
+ },
985
+ async swap(wallet, quote) {
986
+ const w = resolveXrplWallet(wallet._native);
987
+ return swapXrpl({ client: payClient, wallet: w, quote });
988
+ },
772
989
  async verify(_ref, accept) {
773
990
  return verifyXrpl({ reader, accept });
774
991
  }