@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.
@@ -1,7 +1,7 @@
1
1
  import * as _anywalletkit_protocol_swap2_core_types from '@anywalletkit/protocol-swap2-core/types';
2
2
  import { SingleProviderSwap2 } from '@anywalletkit/protocol-swap2-core/swap2/single';
3
3
  import { Swap2EngineConfig, Swap2Account } from '@anywalletkit/protocol-swap2-core/swap2/base';
4
- import { b as OmnistonProviderOptions } from './provider-D6RXRgf-.cjs';
4
+ import { b as OmnistonProviderOptions } from './provider-CcCZB-Cj.cjs';
5
5
 
6
6
  interface OmnistonSwap2Config extends Swap2EngineConfig, OmnistonProviderOptions {
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import * as _anywalletkit_protocol_swap2_core_types from '@anywalletkit/protocol-swap2-core/types';
2
2
  import { SingleProviderSwap2 } from '@anywalletkit/protocol-swap2-core/swap2/single';
3
3
  import { Swap2EngineConfig, Swap2Account } from '@anywalletkit/protocol-swap2-core/swap2/base';
4
- import { b as OmnistonProviderOptions } from './provider-D6RXRgf-.js';
4
+ import { b as OmnistonProviderOptions } from './provider-CcCZB-Cj.js';
5
5
 
6
6
  interface OmnistonSwap2Config extends Swap2EngineConfig, OmnistonProviderOptions {
7
7
  }
package/dist/protocol.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  OmnistonSwap2Protocol
3
- } from "./chunk-WL7BSEBR.js";
4
- import "./chunk-R6B2CV5Z.js";
3
+ } from "./chunk-2E6CVWD6.js";
4
+ import "./chunk-4JOTKQOT.js";
5
5
  import "./chunk-QDXFC6BC.js";
6
6
  import "./chunk-AIWAM7BS.js";
7
7
  import "./chunk-JWYDIW4V.js";
@@ -530,10 +530,12 @@ interface OmnistonProviderOptions {
530
530
  * swap body depends on — that a STON.fi hop's ask wallet is its router's
531
531
  * wallet of the quoted output (and, for native input, that the pTON wallet
532
532
  * the message pays belongs to that router), or that a DeDust pool trades the
533
- * quoted pair. `createOmnistonTonValidators` supplies it. When absent the
534
- * decoded-body rules still apply and each skipped leg is traced as
535
- * `ton-swap-payload legs-unverified`; STON.fi v1 native input (pTON v1) is
536
- * refused without it.
533
+ * quoted pair. `createOmnistonTonValidators` supplies it. Required, with
534
+ * `validateTonSwapTarget`, to advertise same-chain TON swaps (scan
535
+ * 2026-10-30 F8: without it a genuine DEX body can pay out a token other
536
+ * than the quoted one). Only `tonSwapPayloadCheck: "report"` lets a host run
537
+ * without it — each unverified leg is then traced as `ton-swap-payload
538
+ * legs-unverified`; STON.fi v1 native input (pTON v1) is refused regardless.
537
539
  */
538
540
  validateTonSwapLeg?: (leg: TonSwapLeg) => Promise<boolean> | boolean;
539
541
  /**
@@ -761,6 +763,7 @@ declare function omnistonVerifyBuild(hooks: {
761
763
  address: string;
762
764
  protocols: readonly string[];
763
765
  }) => Promise<boolean> | boolean;
766
+ /** The DEX pays out the quoted token (`createOmnistonTonValidators().validateTonSwapLeg`). Without it a TON swap body is refused. */
764
767
  validateTonSwapLeg?: (leg: TonSwapLeg) => Promise<boolean> | boolean;
765
768
  /**
766
769
  * The app's check of a TON → EVM escrow body (`createOmnistonTonValidators().verifyTonEscrowBuild`). Without it a
@@ -530,10 +530,12 @@ interface OmnistonProviderOptions {
530
530
  * swap body depends on — that a STON.fi hop's ask wallet is its router's
531
531
  * wallet of the quoted output (and, for native input, that the pTON wallet
532
532
  * the message pays belongs to that router), or that a DeDust pool trades the
533
- * quoted pair. `createOmnistonTonValidators` supplies it. When absent the
534
- * decoded-body rules still apply and each skipped leg is traced as
535
- * `ton-swap-payload legs-unverified`; STON.fi v1 native input (pTON v1) is
536
- * refused without it.
533
+ * quoted pair. `createOmnistonTonValidators` supplies it. Required, with
534
+ * `validateTonSwapTarget`, to advertise same-chain TON swaps (scan
535
+ * 2026-10-30 F8: without it a genuine DEX body can pay out a token other
536
+ * than the quoted one). Only `tonSwapPayloadCheck: "report"` lets a host run
537
+ * without it — each unverified leg is then traced as `ton-swap-payload
538
+ * legs-unverified`; STON.fi v1 native input (pTON v1) is refused regardless.
537
539
  */
538
540
  validateTonSwapLeg?: (leg: TonSwapLeg) => Promise<boolean> | boolean;
539
541
  /**
@@ -761,6 +763,7 @@ declare function omnistonVerifyBuild(hooks: {
761
763
  address: string;
762
764
  protocols: readonly string[];
763
765
  }) => Promise<boolean> | boolean;
766
+ /** The DEX pays out the quoted token (`createOmnistonTonValidators().validateTonSwapLeg`). Without it a TON swap body is refused. */
764
767
  validateTonSwapLeg?: (leg: TonSwapLeg) => Promise<boolean> | boolean;
765
768
  /**
766
769
  * The app's check of a TON → EVM escrow body (`createOmnistonTonValidators().verifyTonEscrowBuild`). Without it a
package/dist/provider.cjs CHANGED
@@ -539,18 +539,36 @@ function toChainAddress(caip2, address) {
539
539
  if (!$case) return null;
540
540
  return { chain: { $case, value: (0, import_viem.getAddress)(address) } };
541
541
  }
542
- function encodeIntent(quoteId, trader, dest, srcChain, kind = "order") {
543
- return [INTENT_PREFIX, quoteId, trader, dest, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
542
+ var QUOTE_DIGEST_SKIP = /* @__PURE__ */ new Set(["rfqId", "resolverName", "estimatedSettlementDuration", "estimatedGasConsumption"]);
543
+ function canonicalJson(value) {
544
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
545
+ if (value && typeof value === "object") {
546
+ return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
547
+ }
548
+ return JSON.stringify(value) ?? "null";
549
+ }
550
+ function quoteDigest(q) {
551
+ const terms = JSON.parse(JSON.stringify(q));
552
+ for (const key of QUOTE_DIGEST_SKIP) delete terms[key];
553
+ return (0, import_viem.sha256)(new TextEncoder().encode(canonicalJson(terms))).slice(2, 34);
554
+ }
555
+ var QUOTE_DIGEST = /^[0-9a-f]{32}$/;
556
+ function encodeIntent(quoteId, trader, dest, srcChain, kind, digest) {
557
+ return [INTENT_PREFIX, quoteId, trader, `${dest}#${digest}`, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
544
558
  }
545
559
  var QUOTE_ID = /^[0-9a-fA-F]{48,64}$/;
546
560
  function decodeIntent(intentId) {
547
561
  try {
548
562
  const parts = intentId.split("|");
549
563
  if (parts.length !== 5 && parts.length !== 6 || parts[0] !== INTENT_PREFIX) return null;
550
- const [, quoteId = "", trader = "", dest = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
564
+ const [, quoteId = "", trader = "", destPart = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
565
+ const mark = destPart.lastIndexOf("#");
566
+ const dest = mark < 0 ? destPart : destPart.slice(0, mark);
567
+ const digest = mark < 0 ? void 0 : destPart.slice(mark + 1);
568
+ if (digest !== void 0 && !QUOTE_DIGEST.test(digest)) return null;
551
569
  if (!QUOTE_ID.test(quoteId) || !trader || !dest || !srcChain) return null;
552
570
  if (parts.length === 6 && kindPart !== "swap") return null;
553
- return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order" };
571
+ return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order", ...digest ? { digest } : {} };
554
572
  } catch {
555
573
  return null;
556
574
  }
@@ -1426,16 +1444,15 @@ function omnistonVerifyBuild(hooks) {
1426
1444
  } catch {
1427
1445
  return "the swap target could not be checked";
1428
1446
  }
1429
- if (hooks.validateTonSwapLeg) {
1430
- for (const leg of checked.legs) {
1431
- let ok = false;
1432
- try {
1433
- ok = await hooks.validateTonSwapLeg(leg);
1434
- } catch {
1435
- ok = false;
1436
- }
1437
- if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1447
+ if (!hooks.validateTonSwapLeg) return "a TON swap body needs validateTonSwapLeg (createOmnistonTonValidators)";
1448
+ for (const leg of checked.legs) {
1449
+ let ok = false;
1450
+ try {
1451
+ ok = await hooks.validateTonSwapLeg(leg);
1452
+ } catch {
1453
+ ok = false;
1438
1454
  }
1455
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1439
1456
  }
1440
1457
  return null;
1441
1458
  };
@@ -1572,7 +1589,7 @@ function omnistonProvider(opts = {}) {
1572
1589
  return true;
1573
1590
  }
1574
1591
  const a = existing.bound, b = entry.bound;
1575
- const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest);
1592
+ 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);
1576
1593
  if (!same) return false;
1577
1594
  await quotes.save(quoteId, json);
1578
1595
  return true;
@@ -1625,10 +1642,19 @@ function omnistonProvider(opts = {}) {
1625
1642
  throw new Error(`omniston: intent id does not match the quote it was issued for (${bound.field})`);
1626
1643
  }
1627
1644
  if (bound.legacy) trace("legacy-intent", { where });
1645
+ if (!decoded.digest) {
1646
+ trace("intent-mismatch", { where, field: "digest-missing" });
1647
+ throw new Error("omniston: this quote was issued before an update \u2014 request a fresh quote");
1648
+ }
1649
+ if (decoded.digest !== quoteDigest(entry.quote)) {
1650
+ trace("intent-mismatch", { where, field: "digest" });
1651
+ throw new Error("omniston: quote was replaced \u2014 request a fresh quote");
1652
+ }
1628
1653
  return bound.ref;
1629
1654
  }
1630
1655
  const bindKey = (quoteId) => `${quoteId}#bind`;
1631
1656
  const disclosedKey = (quoteId) => `${quoteId}#disclosed`;
1657
+ const disclosedIndicesKey = (quoteId) => `${quoteId}#disclosed-indices`;
1632
1658
  function orderBinding(ref, q) {
1633
1659
  return (0, import_viem.sha256)(new TextEncoder().encode(JSON.stringify([
1634
1660
  ref.quoteId,
@@ -1645,6 +1671,7 @@ function omnistonProvider(opts = {}) {
1645
1671
  await store.clear(quoteId);
1646
1672
  await store.clear(bindKey(quoteId));
1647
1673
  await store.clear(disclosedKey(quoteId));
1674
+ await store.clear(disclosedIndicesKey(quoteId));
1648
1675
  }
1649
1676
  async function secretsFor(ref, q, count) {
1650
1677
  const quoteId = ref.quoteId;
@@ -1673,7 +1700,7 @@ function omnistonProvider(opts = {}) {
1673
1700
  return !!existing && existing.length > 0;
1674
1701
  }
1675
1702
  const SWAP_SLIPPAGE_PIPS_DEFAULT = 5e3;
1676
- const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function";
1703
+ const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function" && (typeof opts.validateTonSwapLeg === "function" || opts.tonSwapPayloadCheck === "report");
1677
1704
  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";
1678
1705
  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";
1679
1706
  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";
@@ -1752,7 +1779,7 @@ function omnistonProvider(opts = {}) {
1752
1779
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
1753
1780
  return {
1754
1781
  provider: "omniston",
1755
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap"),
1782
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap", quoteDigest(q)),
1756
1783
  depositAddress: "",
1757
1784
  // swap settlement — the built message pays the DEX directly
1758
1785
  depositMemo: null,
@@ -1785,7 +1812,7 @@ function omnistonProvider(opts = {}) {
1785
1812
  if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
1786
1813
  throw new Error("omniston: swap quote is not a TON same-chain swap");
1787
1814
  }
1788
- if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet and swap-target validators");
1815
+ if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet, swap-target and swap-leg validators");
1789
1816
  const owner = { chain: { $case: "ton", value: ref.trader } };
1790
1817
  const receiver = { chain: { $case: "ton", value: ref.dest } };
1791
1818
  const omni = await getClient();
@@ -1884,7 +1911,7 @@ function omnistonProvider(opts = {}) {
1884
1911
  }
1885
1912
  if (checked) {
1886
1913
  if (typeof opts.validateTonSwapLeg !== "function") {
1887
- trace("ton-swap-payload", { reason: "legs-unverified", detail: "no validateTonSwapLeg hook", mode, legs: checked.legs.length });
1914
+ payloadRefusal("legs-unverified", `no validateTonSwapLeg hook for ${checked.legs.length} leg(s)`);
1888
1915
  } else {
1889
1916
  for (const leg of checked.legs) {
1890
1917
  let ok = false;
@@ -2164,13 +2191,13 @@ function omnistonProvider(opts = {}) {
2164
2191
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
2165
2192
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
2166
2193
  if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2167
- return refuse2("the platform reused a quote id that already holds another user's quote");
2194
+ return refuse2("the platform reused a quote id that already holds another quote");
2168
2195
  }
2169
2196
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
2170
2197
  const feeUsd = feeUnits / 10 ** dest.decimals;
2171
2198
  return {
2172
2199
  provider: "omniston",
2173
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain),
2200
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "order", quoteDigest(q)),
2174
2201
  depositAddress: "",
2175
2202
  // order settlement — no static deposit address
2176
2203
  depositMemo: null,
@@ -2342,6 +2369,7 @@ function omnistonProvider(opts = {}) {
2342
2369
  );
2343
2370
  if (!order) return { state: "pending" };
2344
2371
  let secretsHex = await store.load(decoded.quoteId);
2372
+ const storedSecrets = secretsHex?.length ?? 0;
2345
2373
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2346
2374
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2347
2375
  const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
@@ -2350,6 +2378,7 @@ function omnistonProvider(opts = {}) {
2350
2378
  secretsHex = void 0;
2351
2379
  }
2352
2380
  }
2381
+ const disclosedNow = /* @__PURE__ */ new Set();
2353
2382
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2354
2383
  const q = cached.quote;
2355
2384
  const settlement = q.settlementData.value;
@@ -2448,11 +2477,15 @@ function omnistonProvider(opts = {}) {
2448
2477
  }
2449
2478
  await omni.orderDiscloseHtlcSecret({ quoteId: decoded.quoteId, executionIndex: i, secret: secretBytes }).then(async () => {
2450
2479
  trace("secret-disclosed", { index: i });
2480
+ disclosedNow.add(i);
2451
2481
  await store.save(disclosedKey(decoded.quoteId), ["1"]);
2452
2482
  }, (error) => trace("disclose-failed", { index: i, error: error instanceof Error ? error.message : String(error) }));
2453
2483
  })
2454
2484
  );
2455
2485
  }
2486
+ const disclosedBefore = (await store.load(disclosedIndicesKey(decoded.quoteId)) ?? []).map(Number).filter(Number.isSafeInteger);
2487
+ const disclosedAll = /* @__PURE__ */ new Set([...disclosedBefore, ...disclosedNow]);
2488
+ if (disclosedNow.size > 0) await store.save(disclosedIndicesKey(decoded.quoteId), [...disclosedAll].sort((a, b) => a - b).map(String));
2456
2489
  const status = mapOrderStatus(order);
2457
2490
  const delivered = order.executions[0]?.outputPositionAddress;
2458
2491
  const outCase = cached?.quote.outputAsset.chain.$case;
@@ -2477,6 +2510,14 @@ function omnistonProvider(opts = {}) {
2477
2510
  }).catch(() => null);
2478
2511
  if (hash) status.destTxHash = hash;
2479
2512
  }
2513
+ if (status.state === "completed" && storedSecrets > 0 && !status.destTxHash) {
2514
+ const legacyDisclosed = disclosedBefore.length === 0 && storedSecrets === 1 && (await store.load(disclosedKey(decoded.quoteId)) ?? []).length > 0;
2515
+ const allDisclosed = legacyDisclosed || (order.executions.length > 0 ? order.executions.every((execution) => disclosedAll.has(execution.index)) : disclosedAll.size > 0);
2516
+ if (!allDisclosed) {
2517
+ trace("completion-unproven", { feedStatus: order.status, executions: order.executions.length, disclosed: disclosedAll.size });
2518
+ return { state: "pending", depositReceived: order.executions.length > 0 || disclosedAll.size > 0 };
2519
+ }
2520
+ }
2480
2521
  if (status.state === "completed") {
2481
2522
  await clearSecrets(decoded.quoteId);
2482
2523
  await quotes.clear(decoded.quoteId);