@anywalletkit/protocol-swap2-omniston 0.3.1 → 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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  omnistonProvider
3
- } from "./chunk-HOTFZYSW.js";
3
+ } from "./chunk-2DGCZD5K.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-RW4XZWNC.js.map
27
+ //# sourceMappingURL=chunk-4MKR2SUZ.js.map
@@ -0,0 +1,37 @@
1
+ // src/validators/settlement.ts
2
+ var AUDITED_OMNISTON_SETTLEMENT_CONTRACTS = {
3
+ "ton:-239": ["EQAbWJ3Y1HgIIvcMq1prG1anlDC0T3cZlAU7luPT6LmTpmrZ"],
4
+ "eip155:1": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
5
+ "eip155:56": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
6
+ "eip155:137": ["0x41A392062A4307a6961d48e30FF20bF6823715B8"],
7
+ "eip155:4663": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
8
+ "eip155:8453": ["0xACa3E2F9b61a44892926bC6c3e104192E7D1c3cd"],
9
+ "eip155:42161": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
10
+ "eip155:43114": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
11
+ // 2026-10-20: one factory address on X Layer, HyperEVM and Plasma. Runtime
12
+ // bytecode equals Ethereum's 0xafCF… byte for byte except the immutable slots
13
+ // (escrow implementations, its order protocol, domain hash); it references
14
+ // the order protocol trusted below on the same chain.
15
+ "eip155:196": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
16
+ "eip155:999": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
17
+ "eip155:9745": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"]
18
+ };
19
+ var AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS = {
20
+ "eip155:1": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
21
+ "eip155:56": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
22
+ "eip155:137": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
23
+ "eip155:8453": ["0xe7405fAcDEDB2512e91F2886BDff13231343aF5E"],
24
+ "eip155:42161": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
25
+ "eip155:43114": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
26
+ // 2026-10-20: bytecode equals Ethereum's 0xb581… except immutables (wrapped
27
+ // native, own address, chain id 196 / 9745 / 999 at the chain-id slot).
28
+ "eip155:196": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
29
+ "eip155:9745": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
30
+ "eip155:999": ["0xD84778bbeEC5D2a8d17c6424249C438EC0b1ABE3"]
31
+ };
32
+
33
+ export {
34
+ AUDITED_OMNISTON_SETTLEMENT_CONTRACTS,
35
+ AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS
36
+ };
37
+ //# sourceMappingURL=chunk-JWYDIW4V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validators/settlement.ts"],"sourcesContent":["/**\n * Settlement contracts this deployment trusts for Omniston, per CAIP-2 chain.\n * Audited public addresses — no secret — so both the server wiring and the\n * browser-side provider import them from here.\n * See how-to-project/OMNISTON-NETWORK-ONBOARDING.md before adding a network.\n */\nexport const AUDITED_OMNISTON_SETTLEMENT_CONTRACTS: Record<string, string[]> = {\n \"ton:-239\": [\"EQAbWJ3Y1HgIIvcMq1prG1anlDC0T3cZlAU7luPT6LmTpmrZ\"],\n \"eip155:1\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n \"eip155:56\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n \"eip155:137\": [\"0x41A392062A4307a6961d48e30FF20bF6823715B8\"],\n \"eip155:4663\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n \"eip155:8453\": [\"0xACa3E2F9b61a44892926bC6c3e104192E7D1c3cd\"],\n \"eip155:42161\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n \"eip155:43114\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n // 2026-10-20: one factory address on X Layer, HyperEVM and Plasma. Runtime\n // bytecode equals Ethereum's 0xafCF… byte for byte except the immutable slots\n // (escrow implementations, its order protocol, domain hash); it references\n // the order protocol trusted below on the same chain.\n \"eip155:196\": [\"0xd602079AFa5c38F550510a2a988CbFa698Fa0189\"],\n \"eip155:999\": [\"0xd602079AFa5c38F550510a2a988CbFa698Fa0189\"],\n \"eip155:9745\": [\"0xd602079AFa5c38F550510a2a988CbFa698Fa0189\"],\n};\n\n/**\n * EVM order protocols (Omniston's fork of the 1inch Limit Order Protocol v4 —\n * NOT the canonical 1inch deployment) an EVM→TON order is signed against. The\n * wallet approves this contract for the input amount and signs an EIP-712\n * order whose domain names it; on fill it moves the input into the escrow the\n * factory above deploys. One address, different roles per chain (0xafCF… is\n * the factory elsewhere and the order protocol on Polygon), so this table is\n * separate from the factory table and CAIP-scoped. Runtime code hashes are in\n * how-to-project/OMNISTON-NETWORK-ONBOARDING.md (snapshot 2026-10-16).\n */\nexport const AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS: Record<string, string[]> = {\n \"eip155:1\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n \"eip155:56\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n \"eip155:137\": [\"0xafCF681272AF10959736f45616eeCBC22D7095E2\"],\n \"eip155:8453\": [\"0xe7405fAcDEDB2512e91F2886BDff13231343aF5E\"],\n \"eip155:42161\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n \"eip155:43114\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n // 2026-10-20: bytecode equals Ethereum's 0xb581… except immutables (wrapped\n // native, own address, chain id 196 / 9745 / 999 at the chain-id slot).\n \"eip155:196\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n \"eip155:9745\": [\"0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9\"],\n \"eip155:999\": [\"0xD84778bbeEC5D2a8d17c6424249C438EC0b1ABE3\"],\n};\n"],"mappings":";AAMO,IAAM,wCAAkE;AAAA,EAC7E,YAAY,CAAC,kDAAkD;AAAA,EAC/D,YAAY,CAAC,4CAA4C;AAAA,EACzD,aAAa,CAAC,4CAA4C;AAAA,EAC1D,cAAc,CAAC,4CAA4C;AAAA,EAC3D,eAAe,CAAC,4CAA4C;AAAA,EAC5D,eAAe,CAAC,4CAA4C;AAAA,EAC5D,gBAAgB,CAAC,4CAA4C;AAAA,EAC7D,gBAAgB,CAAC,4CAA4C;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7D,cAAc,CAAC,4CAA4C;AAAA,EAC3D,cAAc,CAAC,4CAA4C;AAAA,EAC3D,eAAe,CAAC,4CAA4C;AAC9D;AAYO,IAAM,uCAAiE;AAAA,EAC5E,YAAY,CAAC,4CAA4C;AAAA,EACzD,aAAa,CAAC,4CAA4C;AAAA,EAC1D,cAAc,CAAC,4CAA4C;AAAA,EAC3D,eAAe,CAAC,4CAA4C;AAAA,EAC5D,gBAAgB,CAAC,4CAA4C;AAAA,EAC7D,gBAAgB,CAAC,4CAA4C;AAAA;AAAA;AAAA,EAG7D,cAAc,CAAC,4CAA4C;AAAA,EAC3D,eAAe,CAAC,4CAA4C;AAAA,EAC5D,cAAc,CAAC,4CAA4C;AAC7D;","names":[]}
package/dist/index.cjs CHANGED
@@ -438,6 +438,38 @@ function sameJetton(a, b) {
438
438
  }
439
439
  }
440
440
 
441
+ // src/validators/settlement.ts
442
+ var AUDITED_OMNISTON_SETTLEMENT_CONTRACTS = {
443
+ "ton:-239": ["EQAbWJ3Y1HgIIvcMq1prG1anlDC0T3cZlAU7luPT6LmTpmrZ"],
444
+ "eip155:1": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
445
+ "eip155:56": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
446
+ "eip155:137": ["0x41A392062A4307a6961d48e30FF20bF6823715B8"],
447
+ "eip155:4663": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
448
+ "eip155:8453": ["0xACa3E2F9b61a44892926bC6c3e104192E7D1c3cd"],
449
+ "eip155:42161": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
450
+ "eip155:43114": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
451
+ // 2026-10-20: one factory address on X Layer, HyperEVM and Plasma. Runtime
452
+ // bytecode equals Ethereum's 0xafCF… byte for byte except the immutable slots
453
+ // (escrow implementations, its order protocol, domain hash); it references
454
+ // the order protocol trusted below on the same chain.
455
+ "eip155:196": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
456
+ "eip155:999": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
457
+ "eip155:9745": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"]
458
+ };
459
+ var AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS = {
460
+ "eip155:1": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
461
+ "eip155:56": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
462
+ "eip155:137": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
463
+ "eip155:8453": ["0xe7405fAcDEDB2512e91F2886BDff13231343aF5E"],
464
+ "eip155:42161": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
465
+ "eip155:43114": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
466
+ // 2026-10-20: bytecode equals Ethereum's 0xb581… except immutables (wrapped
467
+ // native, own address, chain id 196 / 9745 / 999 at the chain-id slot).
468
+ "eip155:196": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
469
+ "eip155:9745": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
470
+ "eip155:999": ["0xD84778bbeEC5D2a8d17c6424249C438EC0b1ABE3"]
471
+ };
472
+
441
473
  // src/provider.ts
442
474
  var import_core4 = require("@anywalletkit/core");
443
475
  var EVM_CASE = {
@@ -516,18 +548,36 @@ function toChainAddress(caip2, address) {
516
548
  if (!$case) return null;
517
549
  return { chain: { $case, value: (0, import_viem.getAddress)(address) } };
518
550
  }
519
- function encodeIntent(quoteId, trader, dest, srcChain, kind = "order") {
520
- 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("|");
521
567
  }
522
568
  var QUOTE_ID = /^[0-9a-fA-F]{48,64}$/;
523
569
  function decodeIntent(intentId) {
524
570
  try {
525
571
  const parts = intentId.split("|");
526
572
  if (parts.length !== 5 && parts.length !== 6 || parts[0] !== INTENT_PREFIX) return null;
527
- 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;
528
578
  if (!QUOTE_ID.test(quoteId) || !trader || !dest || !srcChain) return null;
529
579
  if (parts.length === 6 && kindPart !== "swap") return null;
530
- 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 } : {} };
531
581
  } catch {
532
582
  return null;
533
583
  }
@@ -648,9 +698,9 @@ function validEvmAddress(value) {
648
698
  }
649
699
  }
650
700
  var EVM_DEST_PROTOCOL_FEE_RECIPIENTS = /* @__PURE__ */ new Set(["0x076268146b4ec6d21fc49c27586cfa91b9079a00"]);
651
- async function validateEvmToTonOrderExtension(extension, expected) {
652
- const evmDest = expected.dstChainId !== void 0;
653
- if (evmDest && (!Number.isSafeInteger(expected.dstChainId) || expected.dstChainId <= 0)) throw new Error("omniston: invalid EVM destination chain id");
701
+ var EVM_AUCTION_POINTS = [[800000n, 100n], [700000n, 100n], [600000n, 100n], [500000n, 100n], [400000n, 100n]];
702
+ var EVM_TIMELOCKS = [1n, 120n, 240n, 300n, 1n, 120n, 180n, 0n].reduce((packed, value, index) => packed | value << BigInt(index * 32), 0n);
703
+ function readEvmOrderExtension(extension) {
654
704
  if (extension.length < 32) throw new Error("omniston: malformed EVM order extension");
655
705
  const view = new DataView(extension.buffer, extension.byteOffset, extension.byteLength);
656
706
  const fields = [];
@@ -670,54 +720,48 @@ async function validateEvmToTonOrderExtension(extension, expected) {
670
720
  }
671
721
  let target;
672
722
  for (const index of [2, 3, 7]) {
673
- const field = fields[index];
674
- if (field.length <= 20) throw new Error("omniston: incomplete EVM order extension program");
675
- const address = (0, import_viem.getAddress)(`0x${bytesToHex(field.slice(0, 20))}`);
676
- if (!expected.trustedTarget(address) || target !== void 0 && (0, import_viem.getAddress)(target) !== address) {
723
+ const field2 = fields[index];
724
+ if (field2.length <= 20) throw new Error("omniston: incomplete EVM order extension program");
725
+ const address = (0, import_viem.getAddress)(`0x${bytesToHex(field2.slice(0, 20))}`);
726
+ if (target !== void 0 && (0, import_viem.getAddress)(target) !== address) {
677
727
  throw new Error("omniston: EVM order extension target is not independently trusted");
678
728
  }
679
729
  target = address;
680
730
  }
681
- const getter = fields[2];
731
+ const field = fields[2];
682
732
  let cursor = 20;
683
- const gasBump = readBigEndian(getter, cursor, 3);
733
+ const gasBump = readBigEndian(field, cursor, 3);
684
734
  cursor += 3;
685
- const gasPrice = readBigEndian(getter, cursor, 4);
735
+ const gasPrice = readBigEndian(field, cursor, 4);
686
736
  cursor += 4;
687
- const auctionStart = readBigEndian(getter, cursor, 4);
737
+ const auctionStart = readBigEndian(field, cursor, 4);
688
738
  cursor += 4;
689
- const duration = readBigEndian(getter, cursor, 3);
739
+ const duration = readBigEndian(field, cursor, 3);
690
740
  cursor += 3;
691
- const initialRateBump = readBigEndian(getter, cursor, 3);
741
+ const initialRateBump = readBigEndian(field, cursor, 3);
692
742
  cursor += 3;
693
- const pointCount = (0, import_core4.bigintToNumber)(readBigEndian(getter, cursor, 1));
743
+ const pointCount = (0, import_core4.bigintToNumber)(readBigEndian(field, cursor, 1));
694
744
  cursor += 1;
695
745
  if (pointCount !== 5) throw new Error("omniston: unsupported EVM auction profile");
696
746
  const points = [];
697
747
  for (let index = 0; index < pointCount; index++) {
698
- points.push([readBigEndian(getter, cursor, 3), readBigEndian(getter, cursor + 3, 2)]);
748
+ points.push([readBigEndian(field, cursor, 3), readBigEndian(field, cursor + 3, 2)]);
699
749
  cursor += 5;
700
750
  }
701
- const integratorFee = readBigEndian(getter, cursor, 2);
751
+ const integratorFee = readBigEndian(field, cursor, 2);
702
752
  cursor += 2;
703
- const integratorShare = readBigEndian(getter, cursor, 1);
753
+ const integratorShare = readBigEndian(field, cursor, 1);
704
754
  cursor += 1;
705
- const resolverFee = readBigEndian(getter, cursor, 2);
755
+ const resolverFee = readBigEndian(field, cursor, 2);
706
756
  cursor += 2;
707
- const whitelistDiscount = readBigEndian(getter, cursor, 1);
757
+ const whitelistDiscount = readBigEndian(field, cursor, 1);
708
758
  cursor += 1;
709
- const getterWhitelistCount = (0, import_core4.bigintToNumber)(readBigEndian(getter, cursor, 1));
759
+ const getterWhitelistCount = (0, import_core4.bigintToNumber)(readBigEndian(field, cursor, 1));
710
760
  cursor += 1;
711
- if (getterWhitelistCount !== 1 || cursor + getterWhitelistCount * 10 !== getter.length) {
761
+ if (getterWhitelistCount !== 1 || cursor + getterWhitelistCount * 10 !== field.length) {
712
762
  throw new Error("omniston: malformed EVM amount-getter whitelist");
713
763
  }
714
- const getterResolver = readBigEndian(getter, cursor, 10);
715
- const expectedPoints = [[800000n, 100n], [700000n, 100n], [600000n, 100n], [500000n, 100n], [400000n, 100n]];
716
- const quoteResolverFee = expected.protocolFeePips % 10 === 0 ? BigInt(expected.protocolFeePips / 10) : -1n;
717
- const resolverLow80 = BigInt((0, import_viem.getAddress)(expected.initialResolverAddress)) & (1n << 80n) - 1n;
718
- if (gasBump !== 0n || gasPrice !== 0n || !within(auctionStart, expected.quoteTimestamp, 5, 5) || duration !== 1800n || initialRateBump !== 900000n || points.some((point, index) => point[0] !== expectedPoints[index][0] || point[1] !== expectedPoints[index][1]) || expected.integratorFeePips !== 0 || integratorFee !== 0n || integratorShare !== 100n || quoteResolverFee < 0n || resolverFee !== quoteResolverFee || whitelistDiscount !== 50n || getterResolver !== resolverLow80) {
719
- throw new Error("omniston: EVM amount-getter terms do not match the pinned v1beta8 profile");
720
- }
764
+ const getter = { gasBump, gasPrice, auctionStart, duration, initialRateBump, points, integratorFee, integratorShare, resolverFee, whitelistDiscount, resolver: readBigEndian(field, cursor, 10) };
721
765
  const post = fields[7];
722
766
  cursor = 20;
723
767
  const integratorRecipient = post.slice(cursor, cursor + 20);
@@ -743,28 +787,51 @@ async function validateEvmToTonOrderExtension(extension, expected) {
743
787
  cursor += 10;
744
788
  const resolverTimeDelta = readBigEndian(post, cursor, 2);
745
789
  cursor += 2;
746
- if (!zeroBytes(integratorRecipient) || !(zeroBytes(protocolRecipient) || evmDest && EVM_DEST_PROTOCOL_FEE_RECIPIENTS.has(`0x${bytesToHex(protocolRecipient)}`)) || postIntegratorFee !== 0n || postIntegratorShare !== 100n || postResolverFee !== quoteResolverFee || postDiscount !== 50n || !within(allowedTime, expected.quoteTimestamp, 60, 5) || postResolver !== resolverLow80 || resolverTimeDelta !== 0n) {
747
- throw new Error("omniston: EVM post-interaction terms do not match the pinned v1beta8 profile");
748
- }
749
790
  const extra = Array.from({ length: 5 }, () => {
750
791
  const value = readBigEndian(post, cursor, 32);
751
792
  cursor += 32;
752
793
  return value;
753
794
  });
795
+ const customData = extension.slice(32 + begin);
796
+ if (customData.length !== 131) throw new Error("omniston: malformed EVM order extension custom data");
797
+ return {
798
+ target,
799
+ getter,
800
+ post: { integratorRecipient, protocolRecipient, integratorFee: postIntegratorFee, integratorShare: postIntegratorShare, resolverFee: postResolverFee, discount: postDiscount, allowedTime, resolver: postResolver, resolverTimeDelta },
801
+ extra,
802
+ customData
803
+ };
804
+ }
805
+ async function validateEvmToTonOrderExtension(extension, expected) {
806
+ const evmDest = expected.dstChainId !== void 0;
807
+ if (evmDest && (!Number.isSafeInteger(expected.dstChainId) || expected.dstChainId <= 0)) throw new Error("omniston: invalid EVM destination chain id");
808
+ const { target, getter, post, extra, customData } = readEvmOrderExtension(extension);
809
+ if (!expected.trustedTarget(target)) throw new Error("omniston: EVM order extension target is not independently trusted");
810
+ const quoteResolverFee = expected.protocolFeePips % 10 === 0 ? BigInt(expected.protocolFeePips / 10) : -1n;
811
+ const resolverLow80 = BigInt((0, import_viem.getAddress)(expected.initialResolverAddress)) & (1n << 80n) - 1n;
812
+ if (getter.gasBump !== 0n || getter.gasPrice !== 0n || !within(getter.auctionStart, expected.quoteTimestamp, 5, 5) || getter.duration !== 1800n || getter.initialRateBump !== 900000n || getter.points.some((point, index) => point[0] !== EVM_AUCTION_POINTS[index][0] || point[1] !== EVM_AUCTION_POINTS[index][1]) || expected.integratorFeePips !== 0 || getter.integratorFee !== 0n || getter.integratorShare !== 100n || quoteResolverFee < 0n || getter.resolverFee !== quoteResolverFee || getter.whitelistDiscount !== 50n || getter.resolver !== resolverLow80) {
813
+ throw new Error("omniston: EVM amount-getter terms do not match the pinned v1beta8 profile");
814
+ }
815
+ if (!zeroBytes(post.integratorRecipient) || !(zeroBytes(post.protocolRecipient) || evmDest && EVM_DEST_PROTOCOL_FEE_RECIPIENTS.has(`0x${bytesToHex(post.protocolRecipient)}`)) || post.integratorFee !== 0n || post.integratorShare !== 100n || post.resolverFee !== quoteResolverFee || post.discount !== 50n || !within(post.allowedTime, expected.quoteTimestamp, 60, 5) || post.resolver !== resolverLow80 || post.resolverTimeDelta !== 0n) {
816
+ throw new Error("omniston: EVM post-interaction terms do not match the pinned v1beta8 profile");
817
+ }
754
818
  const hashlock2 = readBigEndian(expected.secretHash, 0, expected.secretHash.length);
755
819
  const dstToken = evmDest ? BigInt((0, import_viem.getAddress)(expected.outputTokenAddress)) : BigInt((0, import_viem.getAddress)(expected.takerAsset));
756
820
  const deposits = BigInt(expected.srcSecurityDepositUnits) << 128n | BigInt(expected.dstSecurityDepositUnits);
757
- const timelocks = [1n, 120n, 240n, 300n, 1n, 120n, 180n, 0n].reduce((packed, value, index) => packed | value << BigInt(index * 32), 0n);
758
- if (expected.secretHash.length !== 32 || extra[0] !== hashlock2 || extra[1] !== (evmDest ? BigInt(expected.dstChainId) : 0n) || extra[2] !== dstToken || extra[3] !== deposits || extra[4] !== timelocks) {
821
+ if (expected.secretHash.length !== 32 || extra[0] !== hashlock2 || extra[1] !== (evmDest ? BigInt(expected.dstChainId) : 0n) || extra[2] !== dstToken || extra[3] !== deposits || extra[4] !== EVM_TIMELOCKS) {
759
822
  throw new Error("omniston: signed EVM HTLC terms do not match the validated order details");
760
823
  }
761
- const customData = extension.slice(32 + begin);
762
- if (customData.length !== 131) throw new Error("omniston: malformed EVM order extension custom data");
763
- if (evmDest) {
824
+ await checkOrderCustomData(customData, { evmDest, outputToken: expected.outputTokenAddress, traderDst: expected.traderDstAddress, quoteId: expected.quoteId });
825
+ if (expected.tradeStartDeadline < expected.quoteTimestamp) {
826
+ throw new Error("omniston: invalid EVM order quote deadline");
827
+ }
828
+ }
829
+ async function checkOrderCustomData(customData, expected) {
830
+ if (expected.evmDest) {
764
831
  if (!zeroBytes(customData.slice(0, 66))) throw new Error("omniston: unexpected TON addresses in an EVM-destination order");
765
832
  } else {
766
833
  const { Address: Address3 } = await import("@ton/core");
767
- const expectedTon = [expected.outputTokenAddress, expected.traderDstAddress].map((value) => Address3.parse(value));
834
+ const expectedTon = [expected.outputToken, expected.traderDst].map((value) => Address3.parse(value));
768
835
  for (let index = 0; index < expectedTon.length; index++) {
769
836
  const offset = index * 33;
770
837
  const workchain = customData[offset] === 255 ? -1 : customData[offset];
@@ -780,9 +847,6 @@ async function validateEvmToTonOrderExtension(extension, expected) {
780
847
  if (!QUOTE_ID.test(quoteId) || !sliceEqual(customData.slice(99), hexToBytes(quoteId.padStart(64, "0")))) {
781
848
  throw new Error("omniston: EVM order extension custom data is not bound to the quote id");
782
849
  }
783
- if (expected.tradeStartDeadline < expected.quoteTimestamp) {
784
- throw new Error("omniston: invalid EVM order quote deadline");
785
- }
786
850
  }
787
851
  function validateEvmTypedOrder(td, expected) {
788
852
  const value = td;
@@ -1234,8 +1298,101 @@ function omnistonPins(trustedEvmOrderProtocols) {
1234
1298
  const byChain = new Map(Object.entries(trustedEvmOrderProtocols).map(([chain, list]) => [chain, list.map((a) => (0, import_viem.getAddress)(a))]));
1235
1299
  return Object.freeze({ callTargets: () => [], spenders: (chain) => byChain.get(chain) ?? [] });
1236
1300
  }
1301
+ var MAX_EVM_ORDER_LIFETIME_S = 900;
1302
+ async function checkRemoteEvmOrder(quote, tx, params, trusted) {
1303
+ const chainId = evmRef2(params.sourceChain);
1304
+ if (chainId === null || !params.sourceChain.startsWith("eip155:")) return "a typed order needs an EVM source";
1305
+ const intent = decodeIntent(quote.intentId.replace(/~[0-9a-f]{32}$/, ""));
1306
+ if (!intent || intent.kind !== "order" || intent.srcChain !== params.sourceChain) return "the quote's intent is not an order from this chain";
1307
+ if (!tx.orderExtension || !/^0x(?:[0-9a-fA-F]{2})+$/.test(tx.orderExtension)) return "the build carries no order extension to check";
1308
+ const source = (0, import_swap2_assets.ccAsset)(params.sourceChain, params.sourceToken);
1309
+ if (!source?.contract) return "unknown source token";
1310
+ const tonDest = isTon(params.destChain);
1311
+ const destChainId = tonDest ? null : evmRef2(params.destChain);
1312
+ if (!tonDest && (destChainId === null || !params.destChain.startsWith("eip155:"))) return "the destination is neither TON nor an EVM chain";
1313
+ const outputToken = (0, import_swap2_assets.ccAssetByToken)(params.destChain, params.destToken)?.contract ?? params.destToken;
1314
+ const listed = (table, chain, address) => typeof address === "string" && validEvmAddress(address) && (table[chain] ?? []).some((a) => validEvmAddress(a) && (0, import_viem.getAddress)(a) === (0, import_viem.getAddress)(address));
1315
+ let td;
1316
+ try {
1317
+ td = JSON.parse(tx.typedData ?? "");
1318
+ } catch {
1319
+ return "the typed data is not JSON";
1320
+ }
1321
+ const domain = td.domain ?? {};
1322
+ const contract = domain.verifyingContract;
1323
+ if (domain.name !== "1inch Limit Order Protocol" || domain.version !== "4") return "the domain is not the 1inch Limit Order Protocol v4";
1324
+ if (!listed(trusted.orderProtocols, params.sourceChain, contract) || !tx.approvalAddress || (0, import_viem.getAddress)(tx.approvalAddress) !== (0, import_viem.getAddress)(String(contract))) {
1325
+ return `${String(contract)} is not a trusted order protocol on ${params.sourceChain}`;
1326
+ }
1327
+ if (Number(domain.chainId) !== chainId || tx.chainId !== void 0 && tx.chainId !== chainId) return "the domain chainId is not the source chain";
1328
+ const order = td.types?.Order;
1329
+ const message = td.message;
1330
+ if (td.primaryType !== "Order" || !Array.isArray(order) || order.length !== ORDER_FIELDS.length || !order.every((field, i) => field.name === ORDER_FIELDS[i][0] && field.type === ORDER_FIELDS[i][1]) || !message || Object.keys(message).length !== ORDER_FIELDS.length || !ORDER_FIELDS.every(([name]) => Object.hasOwn(message, name)) || !ORDER_FIELDS.every(([name, type]) => type === "address" ? validEvmAddress(message[name]) : validUint256(message[name]))) {
1331
+ return "the typed data is not exactly a v4 Order";
1332
+ }
1333
+ if (!validEvmAddress(intent.trader) || (0, import_viem.getAddress)(String(message.maker)) !== (0, import_viem.getAddress)(intent.trader)) return "the maker is not the trader";
1334
+ if ((0, import_viem.getAddress)(String(message.makerAsset)) !== (0, import_viem.getAddress)(source.contract)) return "the makerAsset is not the source token";
1335
+ if (BigInt(message.makingAmount) !== BigInt(quote.sourceAmount)) return "the makingAmount is not the input";
1336
+ if ((0, import_viem.getAddress)(String(message.takerAsset)) === (0, import_viem.getAddress)(String(message.makerAsset))) return "the takerAsset is the makerAsset";
1337
+ const floor = [quote.minDestAmount, params.minAmountOut ?? "0"].filter((v) => /^\d{1,80}$/.test(v)).map((v) => BigInt(v)).reduce((a, b) => a > b ? a : b, 0n);
1338
+ if (floor <= 0n || BigInt(message.takingAmount) < floor) return `takingAmount ${String(message.takingAmount)} is below the quote's minimum ${floor}`;
1339
+ const traits = BigInt(message.makerTraits);
1340
+ const flags = traits & (1n << 56n) - 1n << 200n;
1341
+ const baseFlags = 1n << 251n | 1n << 249n;
1342
+ if (flags !== baseFlags && flags !== (baseFlags | 1n << 255n)) return "the makerTraits flags are not a single-fill order with its extension";
1343
+ if ((traits & (1n << 80n) - 1n) !== 0n || (traits >> 160n & (1n << 40n) - 1n) !== 0n) return "the makerTraits restrict the taker or the series";
1344
+ const now = BigInt(Math.floor(Date.now() / 1e3));
1345
+ const expiration = traits >> 80n & (1n << 40n) - 1n;
1346
+ if (expiration <= now || expiration > now + BigInt(MAX_EVM_ORDER_LIFETIME_S)) return "the order's expiry is not minutes away";
1347
+ const extension = (0, import_viem.hexToBytes)(tx.orderExtension);
1348
+ const mask = (1n << 160n) - 1n;
1349
+ if ((BigInt(message.salt) & mask) !== (BigInt((0, import_viem.keccak256)(extension)) & mask)) return "the salt does not commit the order extension";
1350
+ let parsed;
1351
+ try {
1352
+ parsed = readEvmOrderExtension(extension);
1353
+ } catch (e) {
1354
+ return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
1355
+ }
1356
+ const { target, getter, post, extra, customData } = parsed;
1357
+ if (!listed(trusted.factories, params.sourceChain, target)) return `the extension targets ${target}, not a trusted escrow factory on ${params.sourceChain}`;
1358
+ const receiverLow80 = BigInt((0, import_viem.getAddress)(String(message.receiver))) & (1n << 80n) - 1n;
1359
+ if (getter.resolver !== receiverLow80 || post.resolver !== receiverLow80) return "the order's receiver is not the resolver its extension whitelists";
1360
+ if (getter.gasBump !== 0n || getter.gasPrice !== 0n || getter.duration !== 1800n || getter.initialRateBump !== 900000n || getter.points.some((point, index) => point[0] !== EVM_AUCTION_POINTS[index][0] || point[1] !== EVM_AUCTION_POINTS[index][1]) || getter.integratorFee !== 0n || getter.integratorShare !== 100n || getter.whitelistDiscount !== 50n || !within(getter.auctionStart, (0, import_core4.bigintToNumber)(now), MAX_EVM_ORDER_LIFETIME_S, 60)) {
1361
+ return "the amount-getter terms do not match the pinned v1beta8 profile";
1362
+ }
1363
+ if (!zeroBytes(post.integratorRecipient) || !(zeroBytes(post.protocolRecipient) || !tonDest && EVM_DEST_PROTOCOL_FEE_RECIPIENTS.has(`0x${bytesToHex(post.protocolRecipient)}`)) || post.integratorFee !== 0n || post.integratorShare !== 100n || post.resolverFee !== getter.resolverFee || post.discount !== 50n || post.resolverTimeDelta !== 0n || !within(post.allowedTime, (0, import_core4.bigintToNumber)(getter.auctionStart), 60, 5)) {
1364
+ return "the post-interaction terms do not match the pinned v1beta8 profile";
1365
+ }
1366
+ let dstToken;
1367
+ try {
1368
+ dstToken = tonDest ? BigInt((0, import_viem.getAddress)(String(message.takerAsset))) : BigInt((0, import_viem.getAddress)(outputToken));
1369
+ } catch {
1370
+ return "the destination token is not an EVM address";
1371
+ }
1372
+ if (extra[1] !== (tonDest ? 0n : BigInt(destChainId)) || extra[2] !== dstToken || extra[4] !== EVM_TIMELOCKS) {
1373
+ return "the signed HTLC terms name another destination chain or token, or other timelocks";
1374
+ }
1375
+ try {
1376
+ await checkOrderCustomData(customData, { evmDest: !tonDest, outputToken, traderDst: params.destAddress, quoteId: intent.quoteId });
1377
+ } catch (e) {
1378
+ return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
1379
+ }
1380
+ return null;
1381
+ }
1237
1382
  function omnistonVerifyBuild(hooks) {
1383
+ const trusted = {
1384
+ orderProtocols: hooks.trustedEvmOrderProtocols ?? AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS,
1385
+ factories: hooks.trustedSettlementContracts ?? AUDITED_OMNISTON_SETTLEMENT_CONTRACTS
1386
+ };
1238
1387
  return async (quote, tx, params) => {
1388
+ if (tx.typedData !== void 0) {
1389
+ if (!params) return "the request this quote answered is unknown";
1390
+ try {
1391
+ return await checkRemoteEvmOrder(quote, tx, params, trusted);
1392
+ } catch {
1393
+ return "the typed order could not be checked";
1394
+ }
1395
+ }
1239
1396
  if (tx.bodyBoc === void 0) return null;
1240
1397
  if (!params) return "the request this quote answered is unknown";
1241
1398
  if (quote.providerData?.settlement !== "swap") {
@@ -1296,16 +1453,15 @@ function omnistonVerifyBuild(hooks) {
1296
1453
  } catch {
1297
1454
  return "the swap target could not be checked";
1298
1455
  }
1299
- if (hooks.validateTonSwapLeg) {
1300
- for (const leg of checked.legs) {
1301
- let ok = false;
1302
- try {
1303
- ok = await hooks.validateTonSwapLeg(leg);
1304
- } catch {
1305
- ok = false;
1306
- }
1307
- 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;
1308
1463
  }
1464
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1309
1465
  }
1310
1466
  return null;
1311
1467
  };
@@ -1442,7 +1598,7 @@ function omnistonProvider(opts = {}) {
1442
1598
  return true;
1443
1599
  }
1444
1600
  const a = existing.bound, b = entry.bound;
1445
- 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);
1446
1602
  if (!same) return false;
1447
1603
  await quotes.save(quoteId, json);
1448
1604
  return true;
@@ -1495,10 +1651,19 @@ function omnistonProvider(opts = {}) {
1495
1651
  throw new Error(`omniston: intent id does not match the quote it was issued for (${bound.field})`);
1496
1652
  }
1497
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
+ }
1498
1662
  return bound.ref;
1499
1663
  }
1500
1664
  const bindKey = (quoteId) => `${quoteId}#bind`;
1501
1665
  const disclosedKey = (quoteId) => `${quoteId}#disclosed`;
1666
+ const disclosedIndicesKey = (quoteId) => `${quoteId}#disclosed-indices`;
1502
1667
  function orderBinding(ref, q) {
1503
1668
  return (0, import_viem.sha256)(new TextEncoder().encode(JSON.stringify([
1504
1669
  ref.quoteId,
@@ -1515,6 +1680,7 @@ function omnistonProvider(opts = {}) {
1515
1680
  await store.clear(quoteId);
1516
1681
  await store.clear(bindKey(quoteId));
1517
1682
  await store.clear(disclosedKey(quoteId));
1683
+ await store.clear(disclosedIndicesKey(quoteId));
1518
1684
  }
1519
1685
  async function secretsFor(ref, q, count) {
1520
1686
  const quoteId = ref.quoteId;
@@ -1543,7 +1709,7 @@ function omnistonProvider(opts = {}) {
1543
1709
  return !!existing && existing.length > 0;
1544
1710
  }
1545
1711
  const SWAP_SLIPPAGE_PIPS_DEFAULT = 5e3;
1546
- 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");
1547
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";
1548
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";
1549
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";
@@ -1622,7 +1788,7 @@ function omnistonProvider(opts = {}) {
1622
1788
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
1623
1789
  return {
1624
1790
  provider: "omniston",
1625
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap"),
1791
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap", quoteDigest(q)),
1626
1792
  depositAddress: "",
1627
1793
  // swap settlement — the built message pays the DEX directly
1628
1794
  depositMemo: null,
@@ -1655,7 +1821,7 @@ function omnistonProvider(opts = {}) {
1655
1821
  if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
1656
1822
  throw new Error("omniston: swap quote is not a TON same-chain swap");
1657
1823
  }
1658
- 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");
1659
1825
  const owner = { chain: { $case: "ton", value: ref.trader } };
1660
1826
  const receiver = { chain: { $case: "ton", value: ref.dest } };
1661
1827
  const omni = await getClient();
@@ -1754,7 +1920,7 @@ function omnistonProvider(opts = {}) {
1754
1920
  }
1755
1921
  if (checked) {
1756
1922
  if (typeof opts.validateTonSwapLeg !== "function") {
1757
- 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)`);
1758
1924
  } else {
1759
1925
  for (const leg of checked.legs) {
1760
1926
  let ok = false;
@@ -1925,7 +2091,7 @@ function omnistonProvider(opts = {}) {
1925
2091
  }
1926
2092
  });
1927
2093
  const orderProtocol = (0, import_viem.getAddress)(protocol.chain.value);
1928
- return { chain: srcChain, to: orderProtocol, value: "0", chainId, approvalAddress: orderProtocol, typedData };
2094
+ return { chain: srcChain, to: orderProtocol, value: "0", chainId, approvalAddress: orderProtocol, typedData, orderExtension: `0x${bytesToHex(payload.orderExtension)}` };
1929
2095
  }
1930
2096
  return {
1931
2097
  name: "omniston",
@@ -2034,13 +2200,13 @@ function omnistonProvider(opts = {}) {
2034
2200
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
2035
2201
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
2036
2202
  if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2037
- 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");
2038
2204
  }
2039
2205
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
2040
2206
  const feeUsd = feeUnits / 10 ** dest.decimals;
2041
2207
  return {
2042
2208
  provider: "omniston",
2043
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain),
2209
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "order", quoteDigest(q)),
2044
2210
  depositAddress: "",
2045
2211
  // order settlement — no static deposit address
2046
2212
  depositMemo: null,
@@ -2212,6 +2378,7 @@ function omnistonProvider(opts = {}) {
2212
2378
  );
2213
2379
  if (!order) return { state: "pending" };
2214
2380
  let secretsHex = await store.load(decoded.quoteId);
2381
+ const storedSecrets = secretsHex?.length ?? 0;
2215
2382
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2216
2383
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2217
2384
  const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
@@ -2220,6 +2387,7 @@ function omnistonProvider(opts = {}) {
2220
2387
  secretsHex = void 0;
2221
2388
  }
2222
2389
  }
2390
+ const disclosedNow = /* @__PURE__ */ new Set();
2223
2391
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2224
2392
  const q = cached.quote;
2225
2393
  const settlement = q.settlementData.value;
@@ -2318,11 +2486,15 @@ function omnistonProvider(opts = {}) {
2318
2486
  }
2319
2487
  await omni.orderDiscloseHtlcSecret({ quoteId: decoded.quoteId, executionIndex: i, secret: secretBytes }).then(async () => {
2320
2488
  trace("secret-disclosed", { index: i });
2489
+ disclosedNow.add(i);
2321
2490
  await store.save(disclosedKey(decoded.quoteId), ["1"]);
2322
2491
  }, (error) => trace("disclose-failed", { index: i, error: error instanceof Error ? error.message : String(error) }));
2323
2492
  })
2324
2493
  );
2325
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));
2326
2498
  const status = mapOrderStatus(order);
2327
2499
  const delivered = order.executions[0]?.outputPositionAddress;
2328
2500
  const outCase = cached?.quote.outputAsset.chain.$case;
@@ -2347,6 +2519,14 @@ function omnistonProvider(opts = {}) {
2347
2519
  }).catch(() => null);
2348
2520
  if (hash) status.destTxHash = hash;
2349
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
+ }
2350
2530
  if (status.state === "completed") {
2351
2531
  await clearSecrets(decoded.quoteId);
2352
2532
  await quotes.clear(decoded.quoteId);