@anywalletkit/protocol-swap2-core 0.4.0 → 0.5.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 (44) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-4H2JVYXT.js → chunk-3Y6HV2OL.js} +2 -2
  3. package/dist/{chunk-W2PHJINC.js → chunk-IC3MZIMB.js} +95 -19
  4. package/dist/chunk-IC3MZIMB.js.map +1 -0
  5. package/dist/{chunk-SKV5BVXK.js → chunk-MNJTMSFN.js} +8 -2
  6. package/dist/chunk-MNJTMSFN.js.map +1 -0
  7. package/dist/{chunk-5RR76RKH.js → chunk-OWO27YBK.js} +2 -2
  8. package/dist/index.cjs +106 -27
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +7 -5
  13. package/dist/remote.cjs +1 -0
  14. package/dist/remote.cjs.map +1 -1
  15. package/dist/remote.js +2 -2
  16. package/dist/server.cjs +50 -4
  17. package/dist/server.cjs.map +1 -1
  18. package/dist/server.d.cts +7 -0
  19. package/dist/server.d.ts +7 -0
  20. package/dist/server.js +14 -5
  21. package/dist/server.js.map +1 -1
  22. package/dist/swap2/base.cjs +90 -17
  23. package/dist/swap2/base.cjs.map +1 -1
  24. package/dist/swap2/base.d.cts +31 -2
  25. package/dist/swap2/base.d.ts +31 -2
  26. package/dist/swap2/base.js +1 -1
  27. package/dist/swap2/single.cjs +90 -17
  28. package/dist/swap2/single.cjs.map +1 -1
  29. package/dist/swap2/single.d.cts +1 -0
  30. package/dist/swap2/single.d.ts +1 -0
  31. package/dist/swap2/single.js +2 -2
  32. package/dist/types.cjs.map +1 -1
  33. package/dist/types.d.cts +6 -0
  34. package/dist/types.d.ts +6 -0
  35. package/dist/util.cjs +6 -0
  36. package/dist/util.cjs.map +1 -1
  37. package/dist/util.d.cts +12 -1
  38. package/dist/util.d.ts +12 -1
  39. package/dist/util.js +3 -1
  40. package/package.json +6 -6
  41. package/dist/chunk-SKV5BVXK.js.map +0 -1
  42. package/dist/chunk-W2PHJINC.js.map +0 -1
  43. /package/dist/{chunk-4H2JVYXT.js.map → chunk-3Y6HV2OL.js.map} +0 -0
  44. /package/dist/{chunk-5RR76RKH.js.map → chunk-OWO27YBK.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -54,6 +54,7 @@ __export(src_exports, {
54
54
  remoteProvider: () => remoteProvider,
55
55
  shortAddress: () => shortAddress,
56
56
  slippageClassOf: () => slippageClassOf,
57
+ sourceAssetOf: () => sourceAssetOf,
57
58
  suggestSlippage: () => suggestSlippage,
58
59
  swap2ChainId: () => swap2ChainId,
59
60
  validQuote: () => validQuote
@@ -158,6 +159,7 @@ function validQuote(q, params, provider) {
158
159
  }
159
160
 
160
161
  // src/util.ts
162
+ var import_swap2_assets = require("@anywalletkit/swap2-assets");
161
163
  function namespace(chain) {
162
164
  return chain.split(":")[0] ?? "";
163
165
  }
@@ -190,6 +192,10 @@ function anySignal(signals) {
190
192
  function redactUrls(text) {
191
193
  return text.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi, "<url>");
192
194
  }
195
+ function sourceAssetOf(params) {
196
+ const decimals = params.sourceDecimals;
197
+ return (0, import_swap2_assets.resolveAsset)(params.sourceChain, params.sourceToken, decimals === void 0 ? {} : { allowUnlisted: true, decimals });
198
+ }
193
199
 
194
200
  // src/remote.ts
195
201
  function remoteProvider(opts) {
@@ -268,7 +274,7 @@ function remoteProvider(opts) {
268
274
  var import_core = require("@anywalletkit/core");
269
275
  var import_viem = require("viem");
270
276
  var import_tx_preview_evm = require("@anywalletkit/tx-preview-evm");
271
- var import_swap2_assets = require("@anywalletkit/swap2-assets");
277
+ var import_swap2_assets2 = require("@anywalletkit/swap2-assets");
272
278
  var ZERO = "0x0000000000000000000000000000000000000000";
273
279
  var ERC20_TRANSFER_ABI = [
274
280
  {
@@ -375,6 +381,10 @@ function normalizeTypedDomain(domain) {
375
381
  return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
376
382
  }
377
383
  var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
384
+ function reviewedIdentity(plan) {
385
+ const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
386
+ return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
387
+ }
378
388
  var SWAP2_ID_PREFIX = "awk1";
379
389
  function encodeId(provider, intentId, sourceTxHash) {
380
390
  const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
@@ -444,15 +454,16 @@ function toCaip2(chain, fallback) {
444
454
  return swap2ChainId(chain);
445
455
  }
446
456
  function toSymbol(caip2, ref) {
447
- const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets.ccAssetByToken)(caip2, ref) : void 0;
457
+ const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) : void 0;
448
458
  if (byToken) return byToken.symbol;
449
- if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
459
+ if ((0, import_swap2_assets2.ccAsset)(caip2, ref)) return ref;
450
460
  return ref;
451
461
  }
452
- function toDestAddress(caip2, ref) {
462
+ function toDestAddress(caip2, ref, allowUnlisted = false) {
453
463
  if (ref.startsWith("0x")) return ref;
454
- const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
464
+ const asset = (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets2.ccAsset)(caip2, ref);
455
465
  if (!asset) {
466
+ if (allowUnlisted && (0, import_swap2_assets2.isTokenAddress)(caip2, ref)) return ref;
456
467
  throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
457
468
  }
458
469
  return asset.contract ?? ZERO;
@@ -534,6 +545,15 @@ function mapStatus(s) {
534
545
  const status = s.state === "completed" ? "completed" : s.state === "action-required" ? "action-required" : s.state === "refund-pending" ? "refund-pending" : s.state === "partial" ? "partial" : s.state === "failed" ? "failed" : s.state === "refunded" ? "refunded" : s.state === "cancelled" ? "cancelled" : s.state === "expired" ? "expired" : "pending";
535
546
  return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
536
547
  }
548
+ function blankToUndefined(v) {
549
+ return typeof v === "string" && v.trim() === "" ? void 0 : v;
550
+ }
551
+ function withoutBlankAddresses(o) {
552
+ const { recipient, refundAddress, ...rest } = o;
553
+ const r = blankToUndefined(recipient);
554
+ const f = blankToUndefined(refundAddress);
555
+ return { ...rest, ...r !== void 0 ? { recipient: r } : {}, ...f !== void 0 ? { refundAddress: f } : {} };
556
+ }
537
557
  var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
538
558
  fromChain;
539
559
  constructor(account, config) {
@@ -566,19 +586,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
566
586
  pairSupported(fromChain, toChain) {
567
587
  return this.chainSupported(fromChain) && this.chainSupported(toChain);
568
588
  }
589
+ unlistedAllowed() {
590
+ return this._config.allowUnlistedTokens === true;
591
+ }
592
+ /** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
593
+ sourceAsset(options) {
594
+ const decimals = options.fromTokenDecimals;
595
+ return (0, import_swap2_assets2.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
596
+ allowUnlisted: this.unlistedAllowed(),
597
+ ...decimals !== void 0 ? { decimals } : {}
598
+ });
599
+ }
600
+ unlistedDecimals = /* @__PURE__ */ new Map();
601
+ /**
602
+ * Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
603
+ * read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
604
+ * disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
605
+ */
606
+ async prepareOptions(options) {
607
+ if (!this.unlistedAllowed()) return options;
608
+ const ref = toSymbol(this.fromChain, options.fromToken);
609
+ if ((0, import_swap2_assets2.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets2.isTokenAddress)(this.fromChain, ref)) return options;
610
+ const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
611
+ let decimals = this.unlistedDecimals.get(key);
612
+ if (decimals === void 0) {
613
+ const reader = this._account;
614
+ if (typeof reader?.getTokenInfo !== "function") {
615
+ if (options.fromTokenDecimals === void 0) {
616
+ throw new Swap2ValidationError(`swap2: '${ref}' is not in the catalog \u2014 pass fromTokenDecimals, or use an account that can read token info`);
617
+ }
618
+ return options;
619
+ }
620
+ decimals = (await reader.getTokenInfo(ref)).decimals;
621
+ this.unlistedDecimals.set(key, decimals);
622
+ }
623
+ if (options.fromTokenDecimals !== void 0 && options.fromTokenDecimals !== decimals) {
624
+ throw new Swap2ValidationError(`swap2: fromTokenDecimals ${options.fromTokenDecimals} does not match the token's ${decimals}`);
625
+ }
626
+ return snapshotOptions({ ...options, fromTokenDecimals: decimals });
627
+ }
569
628
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
570
- toParams(options, dry) {
629
+ toParams(given, dry) {
630
+ const options = withoutBlankAddresses(given);
571
631
  if (options.fromTokenAmount == null) {
572
632
  throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
573
633
  }
574
634
  const destChain = toCaip2(options.toChain, this.fromChain);
575
635
  const constraints = callerConstraints(options);
576
636
  const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
637
+ const source = this.sourceAsset(options);
577
638
  const params = {
578
639
  sourceChain: this.fromChain,
579
- sourceToken: toSymbol(this.fromChain, options.fromToken),
640
+ sourceToken: (0, import_swap2_assets2.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
641
+ ...(0, import_swap2_assets2.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
580
642
  destChain,
581
- destToken: toDestAddress(destChain, options.toToken),
643
+ destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
582
644
  amountIn: amountIn.toString(),
583
645
  destAddress: options.recipient ?? "",
584
646
  dry,
@@ -634,7 +696,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
634
696
  }
635
697
  }
636
698
  assertQuoteFeeCaps(q, options, config) {
637
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken));
699
+ const source = this.sourceAsset(options);
638
700
  if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
639
701
  const token = source.contract ?? source.assetId;
640
702
  const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
@@ -679,7 +741,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
679
741
  * gave one.
680
742
  */
681
743
  async quoteSwap2(options) {
682
- const snapshot = snapshotOptions(options);
744
+ const snapshot = await this.prepareOptions(snapshotOptions(options));
683
745
  const q = await this.resolveQuote(this.toParams(snapshot, true));
684
746
  if (!q) {
685
747
  const detail = this.noRouteDetail();
@@ -721,9 +783,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
721
783
  this.assertFeeConfig(configSnapshot);
722
784
  await this.assertAccountChain(signer);
723
785
  const selfAddress = await signer.getAddress();
724
- const recipient = snapshot.recipient ?? selfAddress;
725
- const refundAddress = snapshot.refundAddress ?? selfAddress;
726
- const executionOptions = snapshotOptions({ ...snapshot, recipient, refundAddress });
786
+ const recipient = blankToUndefined(snapshot.recipient) ?? selfAddress;
787
+ const refundAddress = blankToUndefined(snapshot.refundAddress) ?? selfAddress;
788
+ const executionOptions = await this.prepareOptions(snapshotOptions({ ...snapshot, recipient, refundAddress }));
727
789
  const params = this.toParams(executionOptions, false);
728
790
  const resolved = await this.resolveQuote(params);
729
791
  if (!resolved) {
@@ -804,7 +866,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
804
866
  }
805
867
  let tokenId;
806
868
  if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
807
- const src = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
869
+ const src = this.sourceAsset(executionOptions);
808
870
  if (src?.contract) {
809
871
  if (this.fromChain.startsWith("eip155:")) {
810
872
  unsigned = {
@@ -830,7 +892,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
830
892
  if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
831
893
  throw new Error("swap2: token deposit with memo/cell payload is not implemented");
832
894
  }
833
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
895
+ const source = this.sourceAsset(executionOptions);
834
896
  if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
835
897
  const executionValue = BigInt(unsigned.value);
836
898
  if (serialized) {
@@ -886,7 +948,8 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
886
948
  approvalAmounts = current === approvalAmount ? [] : [...usdtMainnet && current !== 0n ? [0n] : [], approvalAmount];
887
949
  }
888
950
  const review = this._config.reviewExecution;
889
- if (review && !carried) {
951
+ let reviewed;
952
+ if (review && (!carried || carried.reviewed)) {
890
953
  const chainId = unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {};
891
954
  const approvals = approvalAmounts.map((amount) => ({ token: source.contract, spender: unsigned.approvalAddress, amount, ...chainId }));
892
955
  const plan = {
@@ -914,12 +977,28 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
914
977
  ...cellBody !== void 0 ? { cellBody: true } : {}
915
978
  }
916
979
  };
917
- await review(deepFreeze(structuredCloneSafe(plan)));
918
- if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
919
- throw new Swap2QuoteError("swap2: quote expired during review \u2014 nothing was signed");
980
+ const previous = carried?.reviewed;
981
+ if (!previous || reviewedIdentity(previous) !== reviewedIdentity(plan) || plan.destAmount < previous.destAmount) {
982
+ try {
983
+ reviewed = deepFreeze(structuredCloneSafe(previous ? { ...plan, requoted: true } : plan));
984
+ await review(reviewed);
985
+ if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
986
+ throw new Swap2QuoteError(`swap2: quote expired during review \u2014 ${previous ? "the allowance was revoked" : "nothing was signed"}`);
987
+ }
988
+ } catch (error) {
989
+ if (previous) await revokeApproval?.().catch(() => {
990
+ });
991
+ throw error;
992
+ }
920
993
  }
921
994
  }
922
- await this.assertNativeGas(signer, unsigned, approvalAmounts, source.contract ?? void 0, !!unsigned.typedData || serialized || !!tokenId);
995
+ try {
996
+ await this.assertNativeGas(signer, unsigned, approvalAmounts, source.contract ?? void 0, !!unsigned.typedData || serialized || !!tokenId);
997
+ } catch (error) {
998
+ if (carried) await revokeApproval?.().catch(() => {
999
+ });
1000
+ throw error;
1001
+ }
923
1002
  if (approvalAmounts.length) {
924
1003
  const approve = async (amount) => {
925
1004
  const sent2 = await signer.sendTransaction({
@@ -948,7 +1027,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
948
1027
  if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
949
1028
  if (!carried && approvalAmounts.length && revokeApproval) {
950
1029
  this.emitPhase("requoting");
951
- const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false };
1030
+ const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false, ...reviewed ? { reviewed } : {} };
952
1031
  try {
953
1032
  return await this.executeSwap2(signer, snapshot, config, handoff);
954
1033
  } catch (error) {
@@ -1209,7 +1288,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1209
1288
  /** Source chains this protocol can swap from. */
1210
1289
  async getSupportedChains() {
1211
1290
  if (!this.protocolEnabled()) return [];
1212
- return import_swap2_assets.CC_SOURCE_CHAINS.filter((c) => this.chainSupported(c.caip2)).map((c) => ({
1291
+ return import_swap2_assets2.CC_SOURCE_CHAINS.filter((c) => this.chainSupported(c.caip2)).map((c) => ({
1213
1292
  id: c.caip2,
1214
1293
  name: c.label,
1215
1294
  type: chainType(c.caip2),
@@ -1222,7 +1301,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1222
1301
  const wantTo = options?.toChain != null ? toCaip2(options.toChain, "") : void 0;
1223
1302
  if (!this.protocolEnabled() || wantFrom && wantTo && !this.pairSupported(wantFrom, wantTo)) return [];
1224
1303
  const out = [];
1225
- for (const c of import_swap2_assets.CC_SOURCE_CHAINS) {
1304
+ for (const c of import_swap2_assets2.CC_SOURCE_CHAINS) {
1226
1305
  if (!this.chainSupported(c.caip2)) continue;
1227
1306
  if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
1228
1307
  for (const a of c.assets) {
@@ -1309,7 +1388,7 @@ var SingleProviderSwap2 = class extends BaseSwap2Protocol {
1309
1388
 
1310
1389
  // src/activity.ts
1311
1390
  var import_core2 = require("@anywalletkit/core");
1312
- var import_swap2_assets2 = require("@anywalletkit/swap2-assets");
1391
+ var import_swap2_assets3 = require("@anywalletkit/swap2-assets");
1313
1392
  function calledContract(tx) {
1314
1393
  const to = tx.call?.to ?? tx.to;
1315
1394
  if (!to) return null;
@@ -1346,7 +1425,7 @@ function matchesCall(tx, contracts, selectors) {
1346
1425
  return sel !== void 0 && want.has(sel);
1347
1426
  }
1348
1427
  function chainLabel(caip2) {
1349
- return (0, import_swap2_assets2.ccChain)(caip2)?.label ?? caip2;
1428
+ return (0, import_swap2_assets3.ccChain)(caip2)?.label ?? caip2;
1350
1429
  }
1351
1430
  function shortAddress(a) {
1352
1431
  return a.length > 14 ? `${a.slice(0, 6)}\u2026${a.slice(-4)}` : a;
@@ -1365,7 +1444,7 @@ function assetMetaFor(tx) {
1365
1444
  const key = token === null ? null : token.startsWith("0x") ? token.toLowerCase() : token;
1366
1445
  const known = fromTransfers.get(key);
1367
1446
  if (known?.symbol !== void 0 && known.decimals !== void 0) return known;
1368
- const cat = (0, import_swap2_assets2.ccAssetByToken)(tx.chain, token ?? "");
1447
+ const cat = (0, import_swap2_assets3.ccAssetByToken)(tx.chain, token ?? "");
1369
1448
  if (!cat) return known;
1370
1449
  const meta = { symbol: known?.symbol ?? cat.symbol, decimals: known?.decimals ?? cat.decimals };
1371
1450
  return meta;
@@ -1373,7 +1452,7 @@ function assetMetaFor(tx) {
1373
1452
  }
1374
1453
  function assetLabel(tx, a) {
1375
1454
  if (a.symbol) return a.symbol;
1376
- if (a.token === null) return (0, import_swap2_assets2.ccAssetByToken)(tx.chain, "")?.symbol ?? "native";
1455
+ if (a.token === null) return (0, import_swap2_assets3.ccAssetByToken)(tx.chain, "")?.symbol ?? "native";
1377
1456
  return shortAddress(a.token);
1378
1457
  }
1379
1458
  function ownerAssets(tx, ctx) {