@anywalletkit/protocol-swap2-omniston 0.4.0 → 0.5.1

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.
package/dist/index.cjs CHANGED
@@ -548,18 +548,36 @@ function toChainAddress(caip2, address) {
548
548
  if (!$case) return null;
549
549
  return { chain: { $case, value: (0, import_viem.getAddress)(address) } };
550
550
  }
551
- function encodeIntent(quoteId, trader, dest, srcChain, kind = "order") {
552
- return [INTENT_PREFIX, quoteId, trader, dest, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
551
+ var QUOTE_DIGEST_SKIP = /* @__PURE__ */ new Set(["rfqId", "resolverName", "estimatedSettlementDuration", "estimatedGasConsumption"]);
552
+ function canonicalJson(value) {
553
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
554
+ if (value && typeof value === "object") {
555
+ return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
556
+ }
557
+ return JSON.stringify(value) ?? "null";
558
+ }
559
+ function quoteDigest(q) {
560
+ const terms = JSON.parse(JSON.stringify(q));
561
+ for (const key of QUOTE_DIGEST_SKIP) delete terms[key];
562
+ return (0, import_viem.sha256)(new TextEncoder().encode(canonicalJson(terms))).slice(2, 34);
563
+ }
564
+ var QUOTE_DIGEST = /^[0-9a-f]{32}$/;
565
+ function encodeIntent(quoteId, trader, dest, srcChain, kind, digest) {
566
+ return [INTENT_PREFIX, quoteId, trader, `${dest}#${digest}`, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
553
567
  }
554
568
  var QUOTE_ID = /^[0-9a-fA-F]{48,64}$/;
555
569
  function decodeIntent(intentId) {
556
570
  try {
557
571
  const parts = intentId.split("|");
558
572
  if (parts.length !== 5 && parts.length !== 6 || parts[0] !== INTENT_PREFIX) return null;
559
- const [, quoteId = "", trader = "", dest = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
573
+ const [, quoteId = "", trader = "", destPart = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
574
+ const mark = destPart.lastIndexOf("#");
575
+ const dest = mark < 0 ? destPart : destPart.slice(0, mark);
576
+ const digest = mark < 0 ? void 0 : destPart.slice(mark + 1);
577
+ if (digest !== void 0 && !QUOTE_DIGEST.test(digest)) return null;
560
578
  if (!QUOTE_ID.test(quoteId) || !trader || !dest || !srcChain) return null;
561
579
  if (parts.length === 6 && kindPart !== "swap") return null;
562
- return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order" };
580
+ return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order", ...digest ? { digest } : {} };
563
581
  } catch {
564
582
  return null;
565
583
  }
@@ -1435,16 +1453,15 @@ function omnistonVerifyBuild(hooks) {
1435
1453
  } catch {
1436
1454
  return "the swap target could not be checked";
1437
1455
  }
1438
- if (hooks.validateTonSwapLeg) {
1439
- for (const leg of checked.legs) {
1440
- let ok = false;
1441
- try {
1442
- ok = await hooks.validateTonSwapLeg(leg);
1443
- } catch {
1444
- ok = false;
1445
- }
1446
- if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1456
+ if (!hooks.validateTonSwapLeg) return "a TON swap body needs validateTonSwapLeg (createOmnistonTonValidators)";
1457
+ for (const leg of checked.legs) {
1458
+ let ok = false;
1459
+ try {
1460
+ ok = await hooks.validateTonSwapLeg(leg);
1461
+ } catch {
1462
+ ok = false;
1447
1463
  }
1464
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1448
1465
  }
1449
1466
  return null;
1450
1467
  };
@@ -1581,7 +1598,7 @@ function omnistonProvider(opts = {}) {
1581
1598
  return true;
1582
1599
  }
1583
1600
  const a = existing.bound, b = entry.bound;
1584
- const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest);
1601
+ const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest) && quoteDigest(existing.quote) === quoteDigest(entry.quote);
1585
1602
  if (!same) return false;
1586
1603
  await quotes.save(quoteId, json);
1587
1604
  return true;
@@ -1634,10 +1651,19 @@ function omnistonProvider(opts = {}) {
1634
1651
  throw new Error(`omniston: intent id does not match the quote it was issued for (${bound.field})`);
1635
1652
  }
1636
1653
  if (bound.legacy) trace("legacy-intent", { where });
1654
+ if (!decoded.digest) {
1655
+ trace("intent-mismatch", { where, field: "digest-missing" });
1656
+ throw new Error("omniston: this quote was issued before an update \u2014 request a fresh quote");
1657
+ }
1658
+ if (decoded.digest !== quoteDigest(entry.quote)) {
1659
+ trace("intent-mismatch", { where, field: "digest" });
1660
+ throw new Error("omniston: quote was replaced \u2014 request a fresh quote");
1661
+ }
1637
1662
  return bound.ref;
1638
1663
  }
1639
1664
  const bindKey = (quoteId) => `${quoteId}#bind`;
1640
1665
  const disclosedKey = (quoteId) => `${quoteId}#disclosed`;
1666
+ const disclosedIndicesKey = (quoteId) => `${quoteId}#disclosed-indices`;
1641
1667
  function orderBinding(ref, q) {
1642
1668
  return (0, import_viem.sha256)(new TextEncoder().encode(JSON.stringify([
1643
1669
  ref.quoteId,
@@ -1654,6 +1680,7 @@ function omnistonProvider(opts = {}) {
1654
1680
  await store.clear(quoteId);
1655
1681
  await store.clear(bindKey(quoteId));
1656
1682
  await store.clear(disclosedKey(quoteId));
1683
+ await store.clear(disclosedIndicesKey(quoteId));
1657
1684
  }
1658
1685
  async function secretsFor(ref, q, count) {
1659
1686
  const quoteId = ref.quoteId;
@@ -1682,7 +1709,7 @@ function omnistonProvider(opts = {}) {
1682
1709
  return !!existing && existing.length > 0;
1683
1710
  }
1684
1711
  const SWAP_SLIPPAGE_PIPS_DEFAULT = 5e3;
1685
- const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function";
1712
+ const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function" && (typeof opts.validateTonSwapLeg === "function" || opts.tonSwapPayloadCheck === "report");
1686
1713
  const tonSourceReady = (from, to) => omniston.supports(from, to) && trustedContracts.has(from) && trustedContracts.has(to) && typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonEscrowPayload === "function" && typeof opts.validateEvmOutputPosition === "function";
1687
1714
  const evmSourceReady = (from, to) => omniston.supports(from, to) && trustedOrderProtocols.has(from) && trustedContracts.has(from) && trustedContracts.has(to) && typeof opts.validateEvmInputPosition === "function" && typeof opts.validateTonOutputPosition === "function";
1688
1715
  const evmToEvmReady = (from, to) => omniston.supports(from, to) && trustedOrderProtocols.has(from) && trustedContracts.has(from) && trustedContracts.has(to) && typeof opts.validateEvmInputPosition === "function" && typeof opts.validateEvmOutputPosition === "function";
@@ -1761,7 +1788,7 @@ function omnistonProvider(opts = {}) {
1761
1788
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
1762
1789
  return {
1763
1790
  provider: "omniston",
1764
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap"),
1791
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap", quoteDigest(q)),
1765
1792
  depositAddress: "",
1766
1793
  // swap settlement — the built message pays the DEX directly
1767
1794
  depositMemo: null,
@@ -1794,7 +1821,7 @@ function omnistonProvider(opts = {}) {
1794
1821
  if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
1795
1822
  throw new Error("omniston: swap quote is not a TON same-chain swap");
1796
1823
  }
1797
- if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet and swap-target validators");
1824
+ if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet, swap-target and swap-leg validators");
1798
1825
  const owner = { chain: { $case: "ton", value: ref.trader } };
1799
1826
  const receiver = { chain: { $case: "ton", value: ref.dest } };
1800
1827
  const omni = await getClient();
@@ -1893,7 +1920,7 @@ function omnistonProvider(opts = {}) {
1893
1920
  }
1894
1921
  if (checked) {
1895
1922
  if (typeof opts.validateTonSwapLeg !== "function") {
1896
- trace("ton-swap-payload", { reason: "legs-unverified", detail: "no validateTonSwapLeg hook", mode, legs: checked.legs.length });
1923
+ payloadRefusal("legs-unverified", `no validateTonSwapLeg hook for ${checked.legs.length} leg(s)`);
1897
1924
  } else {
1898
1925
  for (const leg of checked.legs) {
1899
1926
  let ok = false;
@@ -2173,13 +2200,13 @@ function omnistonProvider(opts = {}) {
2173
2200
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
2174
2201
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
2175
2202
  if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2176
- return refuse2("the platform reused a quote id that already holds another user's quote");
2203
+ return refuse2("the platform reused a quote id that already holds another quote");
2177
2204
  }
2178
2205
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
2179
2206
  const feeUsd = feeUnits / 10 ** dest.decimals;
2180
2207
  return {
2181
2208
  provider: "omniston",
2182
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain),
2209
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "order", quoteDigest(q)),
2183
2210
  depositAddress: "",
2184
2211
  // order settlement — no static deposit address
2185
2212
  depositMemo: null,
@@ -2351,6 +2378,7 @@ function omnistonProvider(opts = {}) {
2351
2378
  );
2352
2379
  if (!order) return { state: "pending" };
2353
2380
  let secretsHex = await store.load(decoded.quoteId);
2381
+ const storedSecrets = secretsHex?.length ?? 0;
2354
2382
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2355
2383
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2356
2384
  const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
@@ -2359,6 +2387,7 @@ function omnistonProvider(opts = {}) {
2359
2387
  secretsHex = void 0;
2360
2388
  }
2361
2389
  }
2390
+ const disclosedNow = /* @__PURE__ */ new Set();
2362
2391
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2363
2392
  const q = cached.quote;
2364
2393
  const settlement = q.settlementData.value;
@@ -2457,11 +2486,15 @@ function omnistonProvider(opts = {}) {
2457
2486
  }
2458
2487
  await omni.orderDiscloseHtlcSecret({ quoteId: decoded.quoteId, executionIndex: i, secret: secretBytes }).then(async () => {
2459
2488
  trace("secret-disclosed", { index: i });
2489
+ disclosedNow.add(i);
2460
2490
  await store.save(disclosedKey(decoded.quoteId), ["1"]);
2461
2491
  }, (error) => trace("disclose-failed", { index: i, error: error instanceof Error ? error.message : String(error) }));
2462
2492
  })
2463
2493
  );
2464
2494
  }
2495
+ const disclosedBefore = (await store.load(disclosedIndicesKey(decoded.quoteId)) ?? []).map(Number).filter(Number.isSafeInteger);
2496
+ const disclosedAll = /* @__PURE__ */ new Set([...disclosedBefore, ...disclosedNow]);
2497
+ if (disclosedNow.size > 0) await store.save(disclosedIndicesKey(decoded.quoteId), [...disclosedAll].sort((a, b) => a - b).map(String));
2465
2498
  const status = mapOrderStatus(order);
2466
2499
  const delivered = order.executions[0]?.outputPositionAddress;
2467
2500
  const outCase = cached?.quote.outputAsset.chain.$case;
@@ -2486,6 +2519,14 @@ function omnistonProvider(opts = {}) {
2486
2519
  }).catch(() => null);
2487
2520
  if (hash) status.destTxHash = hash;
2488
2521
  }
2522
+ if (status.state === "completed" && storedSecrets > 0 && !status.destTxHash) {
2523
+ const legacyDisclosed = disclosedBefore.length === 0 && storedSecrets === 1 && (await store.load(disclosedKey(decoded.quoteId)) ?? []).length > 0;
2524
+ const allDisclosed = legacyDisclosed || (order.executions.length > 0 ? order.executions.every((execution) => disclosedAll.has(execution.index)) : disclosedAll.size > 0);
2525
+ if (!allDisclosed) {
2526
+ trace("completion-unproven", { feedStatus: order.status, executions: order.executions.length, disclosed: disclosedAll.size });
2527
+ return { state: "pending", depositReceived: order.executions.length > 0 || disclosedAll.size > 0 };
2528
+ }
2529
+ }
2489
2530
  if (status.state === "completed") {
2490
2531
  await clearSecrets(decoded.quoteId);
2491
2532
  await quotes.clear(decoded.quoteId);