@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/README.md CHANGED
@@ -25,20 +25,23 @@ TRON needs its own order path and is not wired yet.
25
25
  Every route is fail-closed until its allowlists and validators are configured:
26
26
 
27
27
  - **TON → TON** (DEX swap): `trustedSettlementContracts["ton:-239"]`,
28
- `validateTonJettonWallet`, `validateTonSwapTarget`. The built message's DEX
28
+ `validateTonJettonWallet`, `validateTonSwapTarget`, `validateTonSwapLeg`. The built message's DEX
29
29
  call is decoded in the package (STON.fi v1/v2 incl. pTON and cross-swap,
30
30
  DeDust v1, DeDust CPMM v2): receiver = destination, refunds/excess = trader,
31
31
  on-chain minimum ≥ the quote's, no foreign referral; unknown layouts and
32
32
  split/multi-route quotes are refused by name. `validateTonSwapLeg`
33
33
  (supplied by `createOmnistonTonValidators`) confirms the routers' ask
34
34
  wallets, pTON wallet ownership and DeDust pool pairs on chain.
35
- `tonSwapPayloadCheck: "report"` traces `ton-swap-payload <reason>` instead
36
- of refusing (default `"enforce"`).
35
+ Without it the decoder cannot tell which token the DEX pays out, so the
36
+ route is not advertised. `tonSwapPayloadCheck: "report"` traces
37
+ `ton-swap-payload <reason>` (incl. `legs-unverified`) instead of refusing
38
+ (default `"enforce"`) — an explicit rollout opt-in.
37
39
  Behind `remoteProvider`, the app checks the built body itself:
38
40
  `remoteProvider({name: "omniston", baseUrl, verifyBuild: omnistonVerifyBuild({...createOmnistonTonValidators()})})`.
39
41
  A TON → TON swap body is opened with `checkTonSwapPayload` against the app's own request (receiver,
40
42
  refund, on-chain minimum, input; the quote carries `routeSteps`) and the DEX it pays is confirmed with
41
- the app's validators. A TON → EVM escrow body goes to `verifyTonEscrowBuild`: the jetton amount, the
43
+ the app's validators — `validateTonSwapTarget` and `validateTonSwapLeg`; without the leg check a TON swap body
44
+ is refused. A TON → EVM escrow body goes to `verifyTonEscrowBuild`: the jetton amount, the
42
45
  trader as owner, the destination chain, address and token and the quote's minimum delivery come from
43
46
  the app, the protocol contract from `trustedProtocolAddresses`, and the escrow code, authority key and
44
47
  timelocks are the audited ones. Without `verifyBuild` the engine refuses remote cell bodies.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  omnistonProvider
3
- } from "./chunk-R6B2CV5Z.js";
3
+ } from "./chunk-4JOTKQOT.js";
4
4
  import {
5
5
  omniston
6
6
  } from "./chunk-QDXFC6BC.js";
@@ -24,4 +24,4 @@ var OmnistonSwap2Protocol = class extends SingleProviderSwap2 {
24
24
  export {
25
25
  OmnistonSwap2Protocol
26
26
  };
27
- //# sourceMappingURL=chunk-WL7BSEBR.js.map
27
+ //# sourceMappingURL=chunk-2E6CVWD6.js.map
@@ -394,18 +394,36 @@ function toChainAddress(caip2, address) {
394
394
  if (!$case) return null;
395
395
  return { chain: { $case, value: getAddress(address) } };
396
396
  }
397
- function encodeIntent(quoteId, trader, dest, srcChain, kind = "order") {
398
- return [INTENT_PREFIX, quoteId, trader, dest, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
397
+ var QUOTE_DIGEST_SKIP = /* @__PURE__ */ new Set(["rfqId", "resolverName", "estimatedSettlementDuration", "estimatedGasConsumption"]);
398
+ function canonicalJson(value) {
399
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
400
+ if (value && typeof value === "object") {
401
+ return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
402
+ }
403
+ return JSON.stringify(value) ?? "null";
404
+ }
405
+ function quoteDigest(q) {
406
+ const terms = JSON.parse(JSON.stringify(q));
407
+ for (const key of QUOTE_DIGEST_SKIP) delete terms[key];
408
+ return sha256(new TextEncoder().encode(canonicalJson(terms))).slice(2, 34);
409
+ }
410
+ var QUOTE_DIGEST = /^[0-9a-f]{32}$/;
411
+ function encodeIntent(quoteId, trader, dest, srcChain, kind, digest) {
412
+ return [INTENT_PREFIX, quoteId, trader, `${dest}#${digest}`, srcChain, ...kind === "swap" ? ["swap"] : []].map(encodeURIComponent).join("|");
399
413
  }
400
414
  var QUOTE_ID = /^[0-9a-fA-F]{48,64}$/;
401
415
  function decodeIntent(intentId) {
402
416
  try {
403
417
  const parts = intentId.split("|");
404
418
  if (parts.length !== 5 && parts.length !== 6 || parts[0] !== INTENT_PREFIX) return null;
405
- const [, quoteId = "", trader = "", dest = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
419
+ const [, quoteId = "", trader = "", destPart = "", srcChain = "", kindPart = ""] = parts.map(decodeURIComponent);
420
+ const mark = destPart.lastIndexOf("#");
421
+ const dest = mark < 0 ? destPart : destPart.slice(0, mark);
422
+ const digest = mark < 0 ? void 0 : destPart.slice(mark + 1);
423
+ if (digest !== void 0 && !QUOTE_DIGEST.test(digest)) return null;
406
424
  if (!QUOTE_ID.test(quoteId) || !trader || !dest || !srcChain) return null;
407
425
  if (parts.length === 6 && kindPart !== "swap") return null;
408
- return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order" };
426
+ return { quoteId, trader, dest, srcChain, kind: parts.length === 6 ? "swap" : "order", ...digest ? { digest } : {} };
409
427
  } catch {
410
428
  return null;
411
429
  }
@@ -1281,16 +1299,15 @@ function omnistonVerifyBuild(hooks) {
1281
1299
  } catch {
1282
1300
  return "the swap target could not be checked";
1283
1301
  }
1284
- if (hooks.validateTonSwapLeg) {
1285
- for (const leg of checked.legs) {
1286
- let ok = false;
1287
- try {
1288
- ok = await hooks.validateTonSwapLeg(leg);
1289
- } catch {
1290
- ok = false;
1291
- }
1292
- if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1302
+ if (!hooks.validateTonSwapLeg) return "a TON swap body needs validateTonSwapLeg (createOmnistonTonValidators)";
1303
+ for (const leg of checked.legs) {
1304
+ let ok = false;
1305
+ try {
1306
+ ok = await hooks.validateTonSwapLeg(leg);
1307
+ } catch {
1308
+ ok = false;
1293
1309
  }
1310
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1294
1311
  }
1295
1312
  return null;
1296
1313
  };
@@ -1427,7 +1444,7 @@ function omnistonProvider(opts = {}) {
1427
1444
  return true;
1428
1445
  }
1429
1446
  const a = existing.bound, b = entry.bound;
1430
- const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest);
1447
+ 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);
1431
1448
  if (!same) return false;
1432
1449
  await quotes.save(quoteId, json);
1433
1450
  return true;
@@ -1480,10 +1497,19 @@ function omnistonProvider(opts = {}) {
1480
1497
  throw new Error(`omniston: intent id does not match the quote it was issued for (${bound.field})`);
1481
1498
  }
1482
1499
  if (bound.legacy) trace("legacy-intent", { where });
1500
+ if (!decoded.digest) {
1501
+ trace("intent-mismatch", { where, field: "digest-missing" });
1502
+ throw new Error("omniston: this quote was issued before an update \u2014 request a fresh quote");
1503
+ }
1504
+ if (decoded.digest !== quoteDigest(entry.quote)) {
1505
+ trace("intent-mismatch", { where, field: "digest" });
1506
+ throw new Error("omniston: quote was replaced \u2014 request a fresh quote");
1507
+ }
1483
1508
  return bound.ref;
1484
1509
  }
1485
1510
  const bindKey = (quoteId) => `${quoteId}#bind`;
1486
1511
  const disclosedKey = (quoteId) => `${quoteId}#disclosed`;
1512
+ const disclosedIndicesKey = (quoteId) => `${quoteId}#disclosed-indices`;
1487
1513
  function orderBinding(ref, q) {
1488
1514
  return sha256(new TextEncoder().encode(JSON.stringify([
1489
1515
  ref.quoteId,
@@ -1500,6 +1526,7 @@ function omnistonProvider(opts = {}) {
1500
1526
  await store.clear(quoteId);
1501
1527
  await store.clear(bindKey(quoteId));
1502
1528
  await store.clear(disclosedKey(quoteId));
1529
+ await store.clear(disclosedIndicesKey(quoteId));
1503
1530
  }
1504
1531
  async function secretsFor(ref, q, count) {
1505
1532
  const quoteId = ref.quoteId;
@@ -1528,7 +1555,7 @@ function omnistonProvider(opts = {}) {
1528
1555
  return !!existing && existing.length > 0;
1529
1556
  }
1530
1557
  const SWAP_SLIPPAGE_PIPS_DEFAULT = 5e3;
1531
- const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function";
1558
+ const sameChainTonReady = () => typeof opts.validateTonJettonWallet === "function" && typeof opts.validateTonSwapTarget === "function" && (typeof opts.validateTonSwapLeg === "function" || opts.tonSwapPayloadCheck === "report");
1532
1559
  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";
1533
1560
  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";
1534
1561
  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";
@@ -1607,7 +1634,7 @@ function omnistonProvider(opts = {}) {
1607
1634
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
1608
1635
  return {
1609
1636
  provider: "omniston",
1610
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap"),
1637
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap", quoteDigest(q)),
1611
1638
  depositAddress: "",
1612
1639
  // swap settlement — the built message pays the DEX directly
1613
1640
  depositMemo: null,
@@ -1640,7 +1667,7 @@ function omnistonProvider(opts = {}) {
1640
1667
  if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
1641
1668
  throw new Error("omniston: swap quote is not a TON same-chain swap");
1642
1669
  }
1643
- if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet and swap-target validators");
1670
+ if (!sameChainTonReady()) throw new Error("omniston: TON same-chain swaps need independent jetton-wallet, swap-target and swap-leg validators");
1644
1671
  const owner = { chain: { $case: "ton", value: ref.trader } };
1645
1672
  const receiver = { chain: { $case: "ton", value: ref.dest } };
1646
1673
  const omni = await getClient();
@@ -1739,7 +1766,7 @@ function omnistonProvider(opts = {}) {
1739
1766
  }
1740
1767
  if (checked) {
1741
1768
  if (typeof opts.validateTonSwapLeg !== "function") {
1742
- trace("ton-swap-payload", { reason: "legs-unverified", detail: "no validateTonSwapLeg hook", mode, legs: checked.legs.length });
1769
+ payloadRefusal("legs-unverified", `no validateTonSwapLeg hook for ${checked.legs.length} leg(s)`);
1743
1770
  } else {
1744
1771
  for (const leg of checked.legs) {
1745
1772
  let ok = false;
@@ -2019,13 +2046,13 @@ function omnistonProvider(opts = {}) {
2019
2046
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
2020
2047
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
2021
2048
  if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2022
- return refuse2("the platform reused a quote id that already holds another user's quote");
2049
+ return refuse2("the platform reused a quote id that already holds another quote");
2023
2050
  }
2024
2051
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
2025
2052
  const feeUsd = feeUnits / 10 ** dest.decimals;
2026
2053
  return {
2027
2054
  provider: "omniston",
2028
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain),
2055
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "order", quoteDigest(q)),
2029
2056
  depositAddress: "",
2030
2057
  // order settlement — no static deposit address
2031
2058
  depositMemo: null,
@@ -2197,6 +2224,7 @@ function omnistonProvider(opts = {}) {
2197
2224
  );
2198
2225
  if (!order) return { state: "pending" };
2199
2226
  let secretsHex = await store.load(decoded.quoteId);
2227
+ const storedSecrets = secretsHex?.length ?? 0;
2200
2228
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2201
2229
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2202
2230
  const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
@@ -2205,6 +2233,7 @@ function omnistonProvider(opts = {}) {
2205
2233
  secretsHex = void 0;
2206
2234
  }
2207
2235
  }
2236
+ const disclosedNow = /* @__PURE__ */ new Set();
2208
2237
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2209
2238
  const q = cached.quote;
2210
2239
  const settlement = q.settlementData.value;
@@ -2303,11 +2332,15 @@ function omnistonProvider(opts = {}) {
2303
2332
  }
2304
2333
  await omni.orderDiscloseHtlcSecret({ quoteId: decoded.quoteId, executionIndex: i, secret: secretBytes }).then(async () => {
2305
2334
  trace("secret-disclosed", { index: i });
2335
+ disclosedNow.add(i);
2306
2336
  await store.save(disclosedKey(decoded.quoteId), ["1"]);
2307
2337
  }, (error) => trace("disclose-failed", { index: i, error: error instanceof Error ? error.message : String(error) }));
2308
2338
  })
2309
2339
  );
2310
2340
  }
2341
+ const disclosedBefore = (await store.load(disclosedIndicesKey(decoded.quoteId)) ?? []).map(Number).filter(Number.isSafeInteger);
2342
+ const disclosedAll = /* @__PURE__ */ new Set([...disclosedBefore, ...disclosedNow]);
2343
+ if (disclosedNow.size > 0) await store.save(disclosedIndicesKey(decoded.quoteId), [...disclosedAll].sort((a, b) => a - b).map(String));
2311
2344
  const status = mapOrderStatus(order);
2312
2345
  const delivered = order.executions[0]?.outputPositionAddress;
2313
2346
  const outCase = cached?.quote.outputAsset.chain.$case;
@@ -2332,6 +2365,14 @@ function omnistonProvider(opts = {}) {
2332
2365
  }).catch(() => null);
2333
2366
  if (hash) status.destTxHash = hash;
2334
2367
  }
2368
+ if (status.state === "completed" && storedSecrets > 0 && !status.destTxHash) {
2369
+ const legacyDisclosed = disclosedBefore.length === 0 && storedSecrets === 1 && (await store.load(disclosedKey(decoded.quoteId)) ?? []).length > 0;
2370
+ const allDisclosed = legacyDisclosed || (order.executions.length > 0 ? order.executions.every((execution) => disclosedAll.has(execution.index)) : disclosedAll.size > 0);
2371
+ if (!allDisclosed) {
2372
+ trace("completion-unproven", { feedStatus: order.status, executions: order.executions.length, disclosed: disclosedAll.size });
2373
+ return { state: "pending", depositReceived: order.executions.length > 0 || disclosedAll.size > 0 };
2374
+ }
2375
+ }
2335
2376
  if (status.state === "completed") {
2336
2377
  await clearSecrets(decoded.quoteId);
2337
2378
  await quotes.clear(decoded.quoteId);
@@ -2384,4 +2425,4 @@ export {
2384
2425
  omnistonVerifyBuild,
2385
2426
  omnistonProvider
2386
2427
  };
2387
- //# sourceMappingURL=chunk-R6B2CV5Z.js.map
2428
+ //# sourceMappingURL=chunk-4JOTKQOT.js.map