@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,3 +1,6 @@
1
+ import {
2
+ applySlippage
3
+ } from "./chunk-GVCGUSTE.js";
1
4
  import {
2
5
  delay
3
6
  } from "./chunk-FURB5RP7.js";
@@ -8,11 +11,12 @@ import {
8
11
  UnknownTokenError,
9
12
  WrongFamilyError,
10
13
  assertNoLegacyWalletKey,
14
+ formatUnits,
11
15
  nativeCost,
12
16
  parseUnits,
13
17
  rejectForeignToken,
14
18
  toInsufficientFundsError
15
- } from "./chunk-QU25LSVS.js";
19
+ } from "./chunk-5GRBEMCA.js";
16
20
 
17
21
  // src/drivers/stellar/index.ts
18
22
  import { Horizon, StrKey as StrKey2 } from "@stellar/stellar-sdk";
@@ -133,6 +137,151 @@ function extractResultCodes(err) {
133
137
  return [extras.transaction ?? "", ...extras.operations ?? []].filter(Boolean);
134
138
  }
135
139
 
140
+ // src/drivers/stellar/swap.ts
141
+ import { Asset as Asset2, BASE_FEE as BASE_FEE2, Operation as Operation2, TransactionBuilder as TransactionBuilder2 } from "@stellar/stellar-sdk";
142
+ var SOURCE = {
143
+ kind: "protocol",
144
+ name: "Stellar SDEX",
145
+ note: "Rate from the ledger\u2019s own order books and liquidity pools, read live from Horizon. Order books charge no trading fee; liquidity pools charge a fixed 0.30%."
146
+ };
147
+ function assetForId(asset) {
148
+ if (asset === "native") return Asset2.native();
149
+ const parts = parseStellarAssetId(asset);
150
+ if (!parts) return null;
151
+ try {
152
+ return new Asset2(parts.code, parts.issuer);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function hopToAsset(h) {
158
+ return h.asset_type === "native" ? Asset2.native() : new Asset2(h.asset_code, h.asset_issuer);
159
+ }
160
+ function side(t, amount) {
161
+ return {
162
+ asset: t.asset,
163
+ symbol: t.symbol ?? (t.asset === "native" ? XLM_SYMBOL : t.asset),
164
+ decimals: STELLAR_DECIMALS,
165
+ amount: amount.toString(),
166
+ amountFormatted: formatUnits(amount, STELLAR_DECIMALS)
167
+ };
168
+ }
169
+ async function quoteStellarSwap(p) {
170
+ const fromAsset = assetForId(p.from.asset);
171
+ const toAsset = assetForId(p.to.asset);
172
+ if (!fromAsset || !toAsset) return null;
173
+ if (p.from.asset === p.to.asset) return null;
174
+ if (p.wantAmount <= 0n) return null;
175
+ const wantFormatted = formatUnits(p.wantAmount, STELLAR_DECIMALS);
176
+ let records;
177
+ try {
178
+ const page = await p.server.strictReceivePaths([fromAsset], toAsset, wantFormatted).call();
179
+ records = page.records;
180
+ } catch {
181
+ return null;
182
+ }
183
+ if (!records?.length) return null;
184
+ let best = null;
185
+ let spend = null;
186
+ for (const r of records) {
187
+ let candidate;
188
+ try {
189
+ candidate = parseUnits(r.source_amount, STELLAR_DECIMALS);
190
+ } catch {
191
+ continue;
192
+ }
193
+ if (candidate <= 0n) continue;
194
+ if (spend === null || candidate < spend) {
195
+ spend = candidate;
196
+ best = r;
197
+ }
198
+ }
199
+ if (best === null || spend === null) return null;
200
+ const maxSpend = applySlippage(spend, p.slippageBps);
201
+ return {
202
+ source: SOURCE,
203
+ network: p.network,
204
+ from: side(p.from, spend),
205
+ to: side(p.to, p.wantAmount),
206
+ maxSpend: maxSpend.toString(),
207
+ maxSpendFormatted: formatUnits(maxSpend, STELLAR_DECIMALS),
208
+ slippageBps: p.slippageBps,
209
+ route: best.path ?? []
210
+ };
211
+ }
212
+ async function swapStellar(p) {
213
+ const { server, keypair, quote } = p;
214
+ const sendAsset = assetForId(quote.from.asset);
215
+ const destAsset = assetForId(quote.to.asset);
216
+ if (!sendAsset || !destAsset) {
217
+ throw new Error(`Stellar: malformed asset id in swap quote (${quote.from.asset} \u2192 ${quote.to.asset}).`);
218
+ }
219
+ const path = Array.isArray(quote.route) ? quote.route.map(hopToAsset) : [];
220
+ try {
221
+ const source = await server.loadAccount(keypair.publicKey());
222
+ const tx = new TransactionBuilder2(source, { fee: BASE_FEE2, networkPassphrase: STELLAR_PASSPHRASE }).addOperation(
223
+ Operation2.pathPaymentStrictReceive({
224
+ sendAsset,
225
+ // The on-chain slippage cap. Validators enforce it; we do not.
226
+ sendMax: formatUnits(BigInt(quote.maxSpend), STELLAR_DECIMALS),
227
+ destination: keypair.publicKey(),
228
+ // ← ourselves: this is what makes it a swap
229
+ destAsset,
230
+ destAmount: quote.to.amountFormatted,
231
+ path
232
+ })
233
+ ).setTimeout(120).build();
234
+ tx.sign(keypair);
235
+ const res = await server.submitTransaction(tx);
236
+ return {
237
+ transaction: res.hash,
238
+ network: quote.network,
239
+ source: quote.source,
240
+ // Strict-RECEIVE fixes the output exactly; the input is whatever the market
241
+ // took, bounded by maxSpend. We report the quoted estimate for `from`.
242
+ from: quote.from,
243
+ to: quote.to
244
+ };
245
+ } catch (err) {
246
+ throw mapSwapError(err);
247
+ }
248
+ }
249
+ function mapSwapError(err) {
250
+ const codes = extractResultCodes2(err);
251
+ const seen = codes.join(", ");
252
+ const has = (re) => codes.some((c) => re.test(c));
253
+ if (has(/op_over_sendmax/i)) {
254
+ return new InsufficientFundsError(
255
+ `Stellar swap refused: the price moved past your slippage cap, so nothing was spent. Re-quote and retry, or raise slippageBps. (Stellar: ${seen})`,
256
+ { cause: err }
257
+ );
258
+ }
259
+ if (has(/op_too_few_offers|op_no_path/i)) {
260
+ return new InsufficientFundsError(
261
+ `Stellar swap refused: not enough liquidity on this pair right now, so nothing was spent. (Stellar: ${seen})`,
262
+ { cause: err }
263
+ );
264
+ }
265
+ if (has(/op_no_(trust|issuer)|op_not_authorized|op_line_full/i)) {
266
+ return new RecipientNotReadyError(
267
+ `Stellar swap refused: your account needs a trustline for the asset you're swapping INTO (and authorization) before it can hold it. (Stellar: ${seen})`,
268
+ { cause: err }
269
+ );
270
+ }
271
+ if (has(/underfunded|insufficient|low_reserve|src_no_trust/i)) {
272
+ return new InsufficientFundsError(
273
+ `Stellar swap failed: the account can't cover it \u2014 balance, base reserve, or no trustline to send this asset. (Stellar: ${seen})`,
274
+ { cause: err }
275
+ );
276
+ }
277
+ return toInsufficientFundsError(err) ?? err;
278
+ }
279
+ function extractResultCodes2(err) {
280
+ const extras = err?.response?.data?.extras?.result_codes;
281
+ if (!extras) return [];
282
+ return [extras.transaction ?? "", ...extras.operations ?? []].filter(Boolean);
283
+ }
284
+
136
285
  // src/drivers/stellar/verify.ts
137
286
  import { hash as hash2 } from "@stellar/stellar-sdk";
138
287
  function memoCandidatesForNonce(nonce) {
@@ -388,6 +537,11 @@ function makeStellarNetwork(preset, rpcUrl) {
388
537
  detail: "base fee 100 stroops (1 operation)"
389
538
  });
390
539
  },
540
+ /** The bound wallet's own address — where THIS wallet gets paid. Derived from the key
541
+ * material only: no RPC, nothing moved. See {@link ResolvedNetwork.addressOf}. */
542
+ async addressOf(wallet) {
543
+ return resolveStellarWallet(wallet._native).publicKey();
544
+ },
391
545
  async balanceOf(wallet, asset) {
392
546
  let owner;
393
547
  try {
@@ -434,6 +588,14 @@ function makeStellarNetwork(preset, rpcUrl) {
434
588
  );
435
589
  return hasTrustline ? { ready: true } : { ready: false, reason: "NO_TRUSTLINE" };
436
590
  },
591
+ /* ---- swap (OPTIONAL, opt-in): a path payment to yourself. See ./swap.ts ---- */
592
+ async quoteSwap({ from, to, wantAmount, slippageBps }) {
593
+ return quoteStellarSwap({ server, network, from, to, wantAmount, slippageBps });
594
+ },
595
+ async swap(wallet, quote) {
596
+ const keypair = resolveStellarWallet(wallet._native);
597
+ return swapStellar({ server, keypair, quote });
598
+ },
437
599
  async verify(_ref, accept) {
438
600
  return verifyStellar({ reader, accept });
439
601
  }
@@ -1,13 +1,17 @@
1
+ import {
2
+ applySlippage
3
+ } from "./chunk-GVCGUSTE.js";
1
4
  import {
2
5
  ConfirmationTimeoutError,
3
6
  InsufficientFundsError,
4
7
  UnknownTokenError,
5
8
  WrongFamilyError,
6
9
  assertNoLegacyWalletKey,
10
+ formatUnits,
7
11
  nativeCost,
8
12
  rejectForeignToken,
9
13
  toInsufficientFundsError
10
- } from "./chunk-QU25LSVS.js";
14
+ } from "./chunk-5GRBEMCA.js";
11
15
 
12
16
  // src/drivers/sui/index.ts
13
17
  import { SuiJsonRpcClient } from "@mysten/sui/jsonRpc";
@@ -94,6 +98,151 @@ function isSuiAffordability(err) {
94
98
  return /no valid gas|gas.*(balance|coin)|insufficient|balance is too low|GasBalanceTooLow/i.test(m);
95
99
  }
96
100
 
101
+ // src/drivers/sui/swap.ts
102
+ import { Transaction as Transaction2 } from "@mysten/sui/transactions";
103
+ var AFTERMATH_API = "https://aftermath.finance/api/router";
104
+ function sourceFor(netFeePct) {
105
+ const pct = typeof netFeePct === "number" ? `${(netFeePct * 100).toFixed(4)}%` : "the underlying pool fee";
106
+ return {
107
+ kind: "provider",
108
+ name: "Aftermath",
109
+ note: `Third-party Sui DEX aggregator (aftermath.finance), used keyless. It routes and returns a complete transaction block; your own keypair signs it locally and Aftermath never holds your funds. Cost on this route: ${pct}, which is the underlying pool fee. PipRail adds nothing.`
110
+ };
111
+ }
112
+ function coinType(t) {
113
+ return t.asset === "native" ? SUI_NATIVE_COINTYPE : t.asset;
114
+ }
115
+ function side(t, amount) {
116
+ const decimals = t.asset === "native" ? SUI_DECIMALS : t.decimals;
117
+ return {
118
+ asset: t.asset,
119
+ symbol: t.symbol ?? (t.asset === "native" ? SUI_SYMBOL : t.asset),
120
+ decimals,
121
+ amount: amount.toString(),
122
+ amountFormatted: formatUnits(amount, decimals)
123
+ };
124
+ }
125
+ function toBig(v) {
126
+ try {
127
+ if (typeof v === "bigint") return v;
128
+ const s = String(v ?? "").replace(/n$/, "");
129
+ return s ? BigInt(s) : null;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ async function postJson(url, body) {
135
+ const ctrl = new AbortController();
136
+ const timer = setTimeout(() => ctrl.abort(), 15e3);
137
+ try {
138
+ const res = await fetch(url, {
139
+ method: "POST",
140
+ headers: { "content-type": "application/json" },
141
+ body: JSON.stringify(body),
142
+ signal: ctrl.signal
143
+ });
144
+ if (!res.ok) return null;
145
+ return await res.json();
146
+ } catch {
147
+ return null;
148
+ } finally {
149
+ clearTimeout(timer);
150
+ }
151
+ }
152
+ async function quoteSuiSwap(p) {
153
+ if (p.wantAmount <= 0n) return null;
154
+ const coinInType = coinType(p.from);
155
+ const coinOutType = coinType(p.to);
156
+ if (coinInType === coinOutType) return null;
157
+ const route = (amountIn) => postJson(`${AFTERMATH_API}/trade-route`, {
158
+ coinInType,
159
+ coinOutType,
160
+ coinInAmount: amountIn.toString()
161
+ });
162
+ const fromDecimals = p.from.asset === "native" ? SUI_DECIMALS : p.from.decimals;
163
+ const probeIn = 10n ** BigInt(Math.max(fromDecimals - 2, 1));
164
+ const probe = await route(probeIn);
165
+ const probeOut = toBig(probe?.coinOut?.amount);
166
+ if (probeOut === null || probeOut <= 0n) return null;
167
+ let needIn = (probeIn * p.wantAmount + probeOut - 1n) / probeOut;
168
+ needIn = applySlippage(needIn, p.slippageBps);
169
+ const real = await route(needIn);
170
+ const realOut = toBig(real?.coinOut?.amount);
171
+ if (realOut === null) return null;
172
+ if (realOut < p.wantAmount) return null;
173
+ return {
174
+ source: sourceFor(real?.netTradeFeePercentage),
175
+ network: p.network,
176
+ from: side(p.from, needIn),
177
+ to: side(p.to, realOut),
178
+ // Exact-in: the input IS the cap. It cannot spend more than this.
179
+ maxSpend: needIn.toString(),
180
+ maxSpendFormatted: formatUnits(needIn, fromDecimals),
181
+ slippageBps: p.slippageBps,
182
+ // Aftermath needs its own route object back verbatim to build the transaction.
183
+ route: { completeRoute: real, slippage: p.slippageBps / 1e4 }
184
+ };
185
+ }
186
+ async function swapSui(p) {
187
+ const { client, keypair, quote } = p;
188
+ const route = quote.route;
189
+ if (!route?.completeRoute) {
190
+ throw new Error("Sui: swap quote is missing its Aftermath routing data \u2014 re-quote before swapping.");
191
+ }
192
+ const built = await postJson(
193
+ `${AFTERMATH_API}/transactions/trade`,
194
+ {
195
+ walletAddress: keypair.getPublicKey().toSuiAddress(),
196
+ completeRoute: route.completeRoute,
197
+ slippage: route.slippage ?? 0.01
198
+ }
199
+ );
200
+ const serialized = typeof built === "string" ? built : built?.serializedTx;
201
+ if (!serialized) {
202
+ throw new InsufficientFundsError(
203
+ "Aftermath could not build this swap (the route went stale, or the market moved). Nothing was spent \u2014 re-quote and retry."
204
+ );
205
+ }
206
+ let tx;
207
+ try {
208
+ tx = Transaction2.from(serialized);
209
+ } catch (cause) {
210
+ throw new Error("Sui: Aftermath returned a transaction this SDK could not decode.", { cause });
211
+ }
212
+ try {
213
+ const res = await client.signAndExecuteTransaction({ transaction: tx, signer: keypair });
214
+ const status = res.effects?.status?.status;
215
+ if (status && status !== "success") {
216
+ throw new InsufficientFundsError(
217
+ `Sui swap failed on-chain: ${res.effects?.status?.error ?? status}. Re-quote and retry.`
218
+ );
219
+ }
220
+ return {
221
+ transaction: res.digest,
222
+ network: quote.network,
223
+ source: quote.source,
224
+ from: quote.from,
225
+ to: quote.to
226
+ };
227
+ } catch (err) {
228
+ if (err instanceof InsufficientFundsError) throw err;
229
+ const msg = String(err?.message ?? err);
230
+ if (/insufficient|balance|GasBalanceTooLow/i.test(msg)) {
231
+ throw new InsufficientFundsError(
232
+ `Sui swap failed: the wallet can't cover it (coin balance or SUI for gas). (${msg.slice(0, 160)})`,
233
+ { cause: err }
234
+ );
235
+ }
236
+ if (/slippage|SlippageExceeded/i.test(msg)) {
237
+ throw new InsufficientFundsError(
238
+ "Sui swap refused: the price moved past your slippage cap, so nothing was spent. Re-quote and retry.",
239
+ { cause: err }
240
+ );
241
+ }
242
+ throw err;
243
+ }
244
+ }
245
+
97
246
  // src/drivers/sui/verify.ts
98
247
  async function verifySui(params) {
99
248
  const { reader, digest, accept } = params;
@@ -308,6 +457,11 @@ function makeSuiNetwork(preset, rpcUrl) {
308
457
  detail: "\u22480.003 SUI (computation + storage; storage is largely rebated on success)"
309
458
  });
310
459
  },
460
+ /** The bound wallet's own address — where THIS wallet gets paid. Derived from the key
461
+ * material only: no RPC, nothing moved. See {@link ResolvedNetwork.addressOf}. */
462
+ async addressOf(wallet) {
463
+ return resolveSuiKeypair(wallet._native).toSuiAddress();
464
+ },
311
465
  async balanceOf(wallet, asset) {
312
466
  let owner;
313
467
  try {
@@ -315,9 +469,9 @@ function makeSuiNetwork(preset, rpcUrl) {
315
469
  } catch {
316
470
  return { token: null, native: null };
317
471
  }
318
- const readBal = async (coinType) => {
472
+ const readBal = async (coinType2) => {
319
473
  try {
320
- return BigInt((await client.getBalance({ owner, coinType })).totalBalance);
474
+ return BigInt((await client.getBalance({ owner, coinType: coinType2 })).totalBalance);
321
475
  } catch {
322
476
  return null;
323
477
  }
@@ -331,6 +485,14 @@ function makeSuiNetwork(preset, rpcUrl) {
331
485
  async recipientReady() {
332
486
  return { ready: "n/a" };
333
487
  },
488
+ /* ---- swap (OPTIONAL, opt-in): via Aftermath, keyless + no added fee. See ./swap.ts ---- */
489
+ async quoteSwap({ from, to, wantAmount, slippageBps }) {
490
+ return quoteSuiSwap({ network, from, to, wantAmount, slippageBps });
491
+ },
492
+ async swap(wallet, quote) {
493
+ const keypair = resolveSuiKeypair(wallet._native);
494
+ return swapSui({ client, keypair, quote });
495
+ },
334
496
  async verify(ref, accept) {
335
497
  return verifySui({ reader, digest: ref, accept });
336
498
  }