@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.
package/dist/provider.cjs CHANGED
@@ -429,6 +429,38 @@ function sameJetton(a, b) {
429
429
  }
430
430
  }
431
431
 
432
+ // src/validators/settlement.ts
433
+ var AUDITED_OMNISTON_SETTLEMENT_CONTRACTS = {
434
+ "ton:-239": ["EQAbWJ3Y1HgIIvcMq1prG1anlDC0T3cZlAU7luPT6LmTpmrZ"],
435
+ "eip155:1": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
436
+ "eip155:56": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
437
+ "eip155:137": ["0x41A392062A4307a6961d48e30FF20bF6823715B8"],
438
+ "eip155:4663": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
439
+ "eip155:8453": ["0xACa3E2F9b61a44892926bC6c3e104192E7D1c3cd"],
440
+ "eip155:42161": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
441
+ "eip155:43114": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
442
+ // 2026-10-20: one factory address on X Layer, HyperEVM and Plasma. Runtime
443
+ // bytecode equals Ethereum's 0xafCF… byte for byte except the immutable slots
444
+ // (escrow implementations, its order protocol, domain hash); it references
445
+ // the order protocol trusted below on the same chain.
446
+ "eip155:196": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
447
+ "eip155:999": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"],
448
+ "eip155:9745": ["0xd602079AFa5c38F550510a2a988CbFa698Fa0189"]
449
+ };
450
+ var AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS = {
451
+ "eip155:1": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
452
+ "eip155:56": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
453
+ "eip155:137": ["0xafCF681272AF10959736f45616eeCBC22D7095E2"],
454
+ "eip155:8453": ["0xe7405fAcDEDB2512e91F2886BDff13231343aF5E"],
455
+ "eip155:42161": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
456
+ "eip155:43114": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
457
+ // 2026-10-20: bytecode equals Ethereum's 0xb581… except immutables (wrapped
458
+ // native, own address, chain id 196 / 9745 / 999 at the chain-id slot).
459
+ "eip155:196": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
460
+ "eip155:9745": ["0xb5810aCfD45D85Eaa938bF90a5857295513DA7E9"],
461
+ "eip155:999": ["0xD84778bbeEC5D2a8d17c6424249C438EC0b1ABE3"]
462
+ };
463
+
432
464
  // src/provider.ts
433
465
  var import_core4 = require("@anywalletkit/core");
434
466
  var EVM_CASE = {
@@ -507,18 +539,36 @@ function toChainAddress(caip2, address) {
507
539
  if (!$case) return null;
508
540
  return { chain: { $case, value: (0, import_viem.getAddress)(address) } };
509
541
  }
510
- function encodeIntent(quoteId, trader, dest, srcChain, kind = "order") {
511
- 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("|");
512
558
  }
513
559
  var QUOTE_ID = /^[0-9a-fA-F]{48,64}$/;
514
560
  function decodeIntent(intentId) {
515
561
  try {
516
562
  const parts = intentId.split("|");
517
563
  if (parts.length !== 5 && parts.length !== 6 || parts[0] !== INTENT_PREFIX) return null;
518
- 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;
519
569
  if (!QUOTE_ID.test(quoteId) || !trader || !dest || !srcChain) return null;
520
570
  if (parts.length === 6 && kindPart !== "swap") return null;
521
- 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 } : {} };
522
572
  } catch {
523
573
  return null;
524
574
  }
@@ -639,9 +689,9 @@ function validEvmAddress(value) {
639
689
  }
640
690
  }
641
691
  var EVM_DEST_PROTOCOL_FEE_RECIPIENTS = /* @__PURE__ */ new Set(["0x076268146b4ec6d21fc49c27586cfa91b9079a00"]);
642
- async function validateEvmToTonOrderExtension(extension, expected) {
643
- const evmDest = expected.dstChainId !== void 0;
644
- if (evmDest && (!Number.isSafeInteger(expected.dstChainId) || expected.dstChainId <= 0)) throw new Error("omniston: invalid EVM destination chain id");
692
+ var EVM_AUCTION_POINTS = [[800000n, 100n], [700000n, 100n], [600000n, 100n], [500000n, 100n], [400000n, 100n]];
693
+ var EVM_TIMELOCKS = [1n, 120n, 240n, 300n, 1n, 120n, 180n, 0n].reduce((packed, value, index) => packed | value << BigInt(index * 32), 0n);
694
+ function readEvmOrderExtension(extension) {
645
695
  if (extension.length < 32) throw new Error("omniston: malformed EVM order extension");
646
696
  const view = new DataView(extension.buffer, extension.byteOffset, extension.byteLength);
647
697
  const fields = [];
@@ -661,54 +711,48 @@ async function validateEvmToTonOrderExtension(extension, expected) {
661
711
  }
662
712
  let target;
663
713
  for (const index of [2, 3, 7]) {
664
- const field = fields[index];
665
- if (field.length <= 20) throw new Error("omniston: incomplete EVM order extension program");
666
- const address = (0, import_viem.getAddress)(`0x${bytesToHex(field.slice(0, 20))}`);
667
- if (!expected.trustedTarget(address) || target !== void 0 && (0, import_viem.getAddress)(target) !== address) {
714
+ const field2 = fields[index];
715
+ if (field2.length <= 20) throw new Error("omniston: incomplete EVM order extension program");
716
+ const address = (0, import_viem.getAddress)(`0x${bytesToHex(field2.slice(0, 20))}`);
717
+ if (target !== void 0 && (0, import_viem.getAddress)(target) !== address) {
668
718
  throw new Error("omniston: EVM order extension target is not independently trusted");
669
719
  }
670
720
  target = address;
671
721
  }
672
- const getter = fields[2];
722
+ const field = fields[2];
673
723
  let cursor = 20;
674
- const gasBump = readBigEndian(getter, cursor, 3);
724
+ const gasBump = readBigEndian(field, cursor, 3);
675
725
  cursor += 3;
676
- const gasPrice = readBigEndian(getter, cursor, 4);
726
+ const gasPrice = readBigEndian(field, cursor, 4);
677
727
  cursor += 4;
678
- const auctionStart = readBigEndian(getter, cursor, 4);
728
+ const auctionStart = readBigEndian(field, cursor, 4);
679
729
  cursor += 4;
680
- const duration = readBigEndian(getter, cursor, 3);
730
+ const duration = readBigEndian(field, cursor, 3);
681
731
  cursor += 3;
682
- const initialRateBump = readBigEndian(getter, cursor, 3);
732
+ const initialRateBump = readBigEndian(field, cursor, 3);
683
733
  cursor += 3;
684
- const pointCount = (0, import_core4.bigintToNumber)(readBigEndian(getter, cursor, 1));
734
+ const pointCount = (0, import_core4.bigintToNumber)(readBigEndian(field, cursor, 1));
685
735
  cursor += 1;
686
736
  if (pointCount !== 5) throw new Error("omniston: unsupported EVM auction profile");
687
737
  const points = [];
688
738
  for (let index = 0; index < pointCount; index++) {
689
- points.push([readBigEndian(getter, cursor, 3), readBigEndian(getter, cursor + 3, 2)]);
739
+ points.push([readBigEndian(field, cursor, 3), readBigEndian(field, cursor + 3, 2)]);
690
740
  cursor += 5;
691
741
  }
692
- const integratorFee = readBigEndian(getter, cursor, 2);
742
+ const integratorFee = readBigEndian(field, cursor, 2);
693
743
  cursor += 2;
694
- const integratorShare = readBigEndian(getter, cursor, 1);
744
+ const integratorShare = readBigEndian(field, cursor, 1);
695
745
  cursor += 1;
696
- const resolverFee = readBigEndian(getter, cursor, 2);
746
+ const resolverFee = readBigEndian(field, cursor, 2);
697
747
  cursor += 2;
698
- const whitelistDiscount = readBigEndian(getter, cursor, 1);
748
+ const whitelistDiscount = readBigEndian(field, cursor, 1);
699
749
  cursor += 1;
700
- const getterWhitelistCount = (0, import_core4.bigintToNumber)(readBigEndian(getter, cursor, 1));
750
+ const getterWhitelistCount = (0, import_core4.bigintToNumber)(readBigEndian(field, cursor, 1));
701
751
  cursor += 1;
702
- if (getterWhitelistCount !== 1 || cursor + getterWhitelistCount * 10 !== getter.length) {
752
+ if (getterWhitelistCount !== 1 || cursor + getterWhitelistCount * 10 !== field.length) {
703
753
  throw new Error("omniston: malformed EVM amount-getter whitelist");
704
754
  }
705
- const getterResolver = readBigEndian(getter, cursor, 10);
706
- const expectedPoints = [[800000n, 100n], [700000n, 100n], [600000n, 100n], [500000n, 100n], [400000n, 100n]];
707
- const quoteResolverFee = expected.protocolFeePips % 10 === 0 ? BigInt(expected.protocolFeePips / 10) : -1n;
708
- const resolverLow80 = BigInt((0, import_viem.getAddress)(expected.initialResolverAddress)) & (1n << 80n) - 1n;
709
- 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) {
710
- throw new Error("omniston: EVM amount-getter terms do not match the pinned v1beta8 profile");
711
- }
755
+ const getter = { gasBump, gasPrice, auctionStart, duration, initialRateBump, points, integratorFee, integratorShare, resolverFee, whitelistDiscount, resolver: readBigEndian(field, cursor, 10) };
712
756
  const post = fields[7];
713
757
  cursor = 20;
714
758
  const integratorRecipient = post.slice(cursor, cursor + 20);
@@ -734,28 +778,51 @@ async function validateEvmToTonOrderExtension(extension, expected) {
734
778
  cursor += 10;
735
779
  const resolverTimeDelta = readBigEndian(post, cursor, 2);
736
780
  cursor += 2;
737
- 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) {
738
- throw new Error("omniston: EVM post-interaction terms do not match the pinned v1beta8 profile");
739
- }
740
781
  const extra = Array.from({ length: 5 }, () => {
741
782
  const value = readBigEndian(post, cursor, 32);
742
783
  cursor += 32;
743
784
  return value;
744
785
  });
786
+ const customData = extension.slice(32 + begin);
787
+ if (customData.length !== 131) throw new Error("omniston: malformed EVM order extension custom data");
788
+ return {
789
+ target,
790
+ getter,
791
+ post: { integratorRecipient, protocolRecipient, integratorFee: postIntegratorFee, integratorShare: postIntegratorShare, resolverFee: postResolverFee, discount: postDiscount, allowedTime, resolver: postResolver, resolverTimeDelta },
792
+ extra,
793
+ customData
794
+ };
795
+ }
796
+ async function validateEvmToTonOrderExtension(extension, expected) {
797
+ const evmDest = expected.dstChainId !== void 0;
798
+ if (evmDest && (!Number.isSafeInteger(expected.dstChainId) || expected.dstChainId <= 0)) throw new Error("omniston: invalid EVM destination chain id");
799
+ const { target, getter, post, extra, customData } = readEvmOrderExtension(extension);
800
+ if (!expected.trustedTarget(target)) throw new Error("omniston: EVM order extension target is not independently trusted");
801
+ const quoteResolverFee = expected.protocolFeePips % 10 === 0 ? BigInt(expected.protocolFeePips / 10) : -1n;
802
+ const resolverLow80 = BigInt((0, import_viem.getAddress)(expected.initialResolverAddress)) & (1n << 80n) - 1n;
803
+ 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) {
804
+ throw new Error("omniston: EVM amount-getter terms do not match the pinned v1beta8 profile");
805
+ }
806
+ 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) {
807
+ throw new Error("omniston: EVM post-interaction terms do not match the pinned v1beta8 profile");
808
+ }
745
809
  const hashlock2 = readBigEndian(expected.secretHash, 0, expected.secretHash.length);
746
810
  const dstToken = evmDest ? BigInt((0, import_viem.getAddress)(expected.outputTokenAddress)) : BigInt((0, import_viem.getAddress)(expected.takerAsset));
747
811
  const deposits = BigInt(expected.srcSecurityDepositUnits) << 128n | BigInt(expected.dstSecurityDepositUnits);
748
- const timelocks = [1n, 120n, 240n, 300n, 1n, 120n, 180n, 0n].reduce((packed, value, index) => packed | value << BigInt(index * 32), 0n);
749
- if (expected.secretHash.length !== 32 || extra[0] !== hashlock2 || extra[1] !== (evmDest ? BigInt(expected.dstChainId) : 0n) || extra[2] !== dstToken || extra[3] !== deposits || extra[4] !== timelocks) {
812
+ 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) {
750
813
  throw new Error("omniston: signed EVM HTLC terms do not match the validated order details");
751
814
  }
752
- const customData = extension.slice(32 + begin);
753
- if (customData.length !== 131) throw new Error("omniston: malformed EVM order extension custom data");
754
- if (evmDest) {
815
+ await checkOrderCustomData(customData, { evmDest, outputToken: expected.outputTokenAddress, traderDst: expected.traderDstAddress, quoteId: expected.quoteId });
816
+ if (expected.tradeStartDeadline < expected.quoteTimestamp) {
817
+ throw new Error("omniston: invalid EVM order quote deadline");
818
+ }
819
+ }
820
+ async function checkOrderCustomData(customData, expected) {
821
+ if (expected.evmDest) {
755
822
  if (!zeroBytes(customData.slice(0, 66))) throw new Error("omniston: unexpected TON addresses in an EVM-destination order");
756
823
  } else {
757
824
  const { Address: Address3 } = await import("@ton/core");
758
- const expectedTon = [expected.outputTokenAddress, expected.traderDstAddress].map((value) => Address3.parse(value));
825
+ const expectedTon = [expected.outputToken, expected.traderDst].map((value) => Address3.parse(value));
759
826
  for (let index = 0; index < expectedTon.length; index++) {
760
827
  const offset = index * 33;
761
828
  const workchain = customData[offset] === 255 ? -1 : customData[offset];
@@ -771,9 +838,6 @@ async function validateEvmToTonOrderExtension(extension, expected) {
771
838
  if (!QUOTE_ID.test(quoteId) || !sliceEqual(customData.slice(99), hexToBytes(quoteId.padStart(64, "0")))) {
772
839
  throw new Error("omniston: EVM order extension custom data is not bound to the quote id");
773
840
  }
774
- if (expected.tradeStartDeadline < expected.quoteTimestamp) {
775
- throw new Error("omniston: invalid EVM order quote deadline");
776
- }
777
841
  }
778
842
  function validateEvmTypedOrder(td, expected) {
779
843
  const value = td;
@@ -1225,8 +1289,101 @@ function omnistonPins(trustedEvmOrderProtocols) {
1225
1289
  const byChain = new Map(Object.entries(trustedEvmOrderProtocols).map(([chain, list]) => [chain, list.map((a) => (0, import_viem.getAddress)(a))]));
1226
1290
  return Object.freeze({ callTargets: () => [], spenders: (chain) => byChain.get(chain) ?? [] });
1227
1291
  }
1292
+ var MAX_EVM_ORDER_LIFETIME_S = 900;
1293
+ async function checkRemoteEvmOrder(quote, tx, params, trusted) {
1294
+ const chainId = evmRef2(params.sourceChain);
1295
+ if (chainId === null || !params.sourceChain.startsWith("eip155:")) return "a typed order needs an EVM source";
1296
+ const intent = decodeIntent(quote.intentId.replace(/~[0-9a-f]{32}$/, ""));
1297
+ if (!intent || intent.kind !== "order" || intent.srcChain !== params.sourceChain) return "the quote's intent is not an order from this chain";
1298
+ if (!tx.orderExtension || !/^0x(?:[0-9a-fA-F]{2})+$/.test(tx.orderExtension)) return "the build carries no order extension to check";
1299
+ const source = (0, import_swap2_assets.ccAsset)(params.sourceChain, params.sourceToken);
1300
+ if (!source?.contract) return "unknown source token";
1301
+ const tonDest = isTon(params.destChain);
1302
+ const destChainId = tonDest ? null : evmRef2(params.destChain);
1303
+ if (!tonDest && (destChainId === null || !params.destChain.startsWith("eip155:"))) return "the destination is neither TON nor an EVM chain";
1304
+ const outputToken = (0, import_swap2_assets.ccAssetByToken)(params.destChain, params.destToken)?.contract ?? params.destToken;
1305
+ 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));
1306
+ let td;
1307
+ try {
1308
+ td = JSON.parse(tx.typedData ?? "");
1309
+ } catch {
1310
+ return "the typed data is not JSON";
1311
+ }
1312
+ const domain = td.domain ?? {};
1313
+ const contract = domain.verifyingContract;
1314
+ if (domain.name !== "1inch Limit Order Protocol" || domain.version !== "4") return "the domain is not the 1inch Limit Order Protocol v4";
1315
+ if (!listed(trusted.orderProtocols, params.sourceChain, contract) || !tx.approvalAddress || (0, import_viem.getAddress)(tx.approvalAddress) !== (0, import_viem.getAddress)(String(contract))) {
1316
+ return `${String(contract)} is not a trusted order protocol on ${params.sourceChain}`;
1317
+ }
1318
+ if (Number(domain.chainId) !== chainId || tx.chainId !== void 0 && tx.chainId !== chainId) return "the domain chainId is not the source chain";
1319
+ const order = td.types?.Order;
1320
+ const message = td.message;
1321
+ 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]))) {
1322
+ return "the typed data is not exactly a v4 Order";
1323
+ }
1324
+ if (!validEvmAddress(intent.trader) || (0, import_viem.getAddress)(String(message.maker)) !== (0, import_viem.getAddress)(intent.trader)) return "the maker is not the trader";
1325
+ if ((0, import_viem.getAddress)(String(message.makerAsset)) !== (0, import_viem.getAddress)(source.contract)) return "the makerAsset is not the source token";
1326
+ if (BigInt(message.makingAmount) !== BigInt(quote.sourceAmount)) return "the makingAmount is not the input";
1327
+ if ((0, import_viem.getAddress)(String(message.takerAsset)) === (0, import_viem.getAddress)(String(message.makerAsset))) return "the takerAsset is the makerAsset";
1328
+ 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);
1329
+ if (floor <= 0n || BigInt(message.takingAmount) < floor) return `takingAmount ${String(message.takingAmount)} is below the quote's minimum ${floor}`;
1330
+ const traits = BigInt(message.makerTraits);
1331
+ const flags = traits & (1n << 56n) - 1n << 200n;
1332
+ const baseFlags = 1n << 251n | 1n << 249n;
1333
+ if (flags !== baseFlags && flags !== (baseFlags | 1n << 255n)) return "the makerTraits flags are not a single-fill order with its extension";
1334
+ if ((traits & (1n << 80n) - 1n) !== 0n || (traits >> 160n & (1n << 40n) - 1n) !== 0n) return "the makerTraits restrict the taker or the series";
1335
+ const now = BigInt(Math.floor(Date.now() / 1e3));
1336
+ const expiration = traits >> 80n & (1n << 40n) - 1n;
1337
+ if (expiration <= now || expiration > now + BigInt(MAX_EVM_ORDER_LIFETIME_S)) return "the order's expiry is not minutes away";
1338
+ const extension = (0, import_viem.hexToBytes)(tx.orderExtension);
1339
+ const mask = (1n << 160n) - 1n;
1340
+ if ((BigInt(message.salt) & mask) !== (BigInt((0, import_viem.keccak256)(extension)) & mask)) return "the salt does not commit the order extension";
1341
+ let parsed;
1342
+ try {
1343
+ parsed = readEvmOrderExtension(extension);
1344
+ } catch (e) {
1345
+ return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
1346
+ }
1347
+ const { target, getter, post, extra, customData } = parsed;
1348
+ if (!listed(trusted.factories, params.sourceChain, target)) return `the extension targets ${target}, not a trusted escrow factory on ${params.sourceChain}`;
1349
+ const receiverLow80 = BigInt((0, import_viem.getAddress)(String(message.receiver))) & (1n << 80n) - 1n;
1350
+ if (getter.resolver !== receiverLow80 || post.resolver !== receiverLow80) return "the order's receiver is not the resolver its extension whitelists";
1351
+ 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)) {
1352
+ return "the amount-getter terms do not match the pinned v1beta8 profile";
1353
+ }
1354
+ 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)) {
1355
+ return "the post-interaction terms do not match the pinned v1beta8 profile";
1356
+ }
1357
+ let dstToken;
1358
+ try {
1359
+ dstToken = tonDest ? BigInt((0, import_viem.getAddress)(String(message.takerAsset))) : BigInt((0, import_viem.getAddress)(outputToken));
1360
+ } catch {
1361
+ return "the destination token is not an EVM address";
1362
+ }
1363
+ if (extra[1] !== (tonDest ? 0n : BigInt(destChainId)) || extra[2] !== dstToken || extra[4] !== EVM_TIMELOCKS) {
1364
+ return "the signed HTLC terms name another destination chain or token, or other timelocks";
1365
+ }
1366
+ try {
1367
+ await checkOrderCustomData(customData, { evmDest: !tonDest, outputToken, traderDst: params.destAddress, quoteId: intent.quoteId });
1368
+ } catch (e) {
1369
+ return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
1370
+ }
1371
+ return null;
1372
+ }
1228
1373
  function omnistonVerifyBuild(hooks) {
1374
+ const trusted = {
1375
+ orderProtocols: hooks.trustedEvmOrderProtocols ?? AUDITED_OMNISTON_EVM_ORDER_PROTOCOLS,
1376
+ factories: hooks.trustedSettlementContracts ?? AUDITED_OMNISTON_SETTLEMENT_CONTRACTS
1377
+ };
1229
1378
  return async (quote, tx, params) => {
1379
+ if (tx.typedData !== void 0) {
1380
+ if (!params) return "the request this quote answered is unknown";
1381
+ try {
1382
+ return await checkRemoteEvmOrder(quote, tx, params, trusted);
1383
+ } catch {
1384
+ return "the typed order could not be checked";
1385
+ }
1386
+ }
1230
1387
  if (tx.bodyBoc === void 0) return null;
1231
1388
  if (!params) return "the request this quote answered is unknown";
1232
1389
  if (quote.providerData?.settlement !== "swap") {
@@ -1287,16 +1444,15 @@ function omnistonVerifyBuild(hooks) {
1287
1444
  } catch {
1288
1445
  return "the swap target could not be checked";
1289
1446
  }
1290
- if (hooks.validateTonSwapLeg) {
1291
- for (const leg of checked.legs) {
1292
- let ok = false;
1293
- try {
1294
- ok = await hooks.validateTonSwapLeg(leg);
1295
- } catch {
1296
- ok = false;
1297
- }
1298
- 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;
1299
1454
  }
1455
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1300
1456
  }
1301
1457
  return null;
1302
1458
  };
@@ -1433,7 +1589,7 @@ function omnistonProvider(opts = {}) {
1433
1589
  return true;
1434
1590
  }
1435
1591
  const a = existing.bound, b = entry.bound;
1436
- 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);
1437
1593
  if (!same) return false;
1438
1594
  await quotes.save(quoteId, json);
1439
1595
  return true;
@@ -1486,10 +1642,19 @@ function omnistonProvider(opts = {}) {
1486
1642
  throw new Error(`omniston: intent id does not match the quote it was issued for (${bound.field})`);
1487
1643
  }
1488
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
+ }
1489
1653
  return bound.ref;
1490
1654
  }
1491
1655
  const bindKey = (quoteId) => `${quoteId}#bind`;
1492
1656
  const disclosedKey = (quoteId) => `${quoteId}#disclosed`;
1657
+ const disclosedIndicesKey = (quoteId) => `${quoteId}#disclosed-indices`;
1493
1658
  function orderBinding(ref, q) {
1494
1659
  return (0, import_viem.sha256)(new TextEncoder().encode(JSON.stringify([
1495
1660
  ref.quoteId,
@@ -1506,6 +1671,7 @@ function omnistonProvider(opts = {}) {
1506
1671
  await store.clear(quoteId);
1507
1672
  await store.clear(bindKey(quoteId));
1508
1673
  await store.clear(disclosedKey(quoteId));
1674
+ await store.clear(disclosedIndicesKey(quoteId));
1509
1675
  }
1510
1676
  async function secretsFor(ref, q, count) {
1511
1677
  const quoteId = ref.quoteId;
@@ -1534,7 +1700,7 @@ function omnistonProvider(opts = {}) {
1534
1700
  return !!existing && existing.length > 0;
1535
1701
  }
1536
1702
  const SWAP_SLIPPAGE_PIPS_DEFAULT = 5e3;
1537
- 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");
1538
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";
1539
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";
1540
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";
@@ -1613,7 +1779,7 @@ function omnistonProvider(opts = {}) {
1613
1779
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
1614
1780
  return {
1615
1781
  provider: "omniston",
1616
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap"),
1782
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "swap", quoteDigest(q)),
1617
1783
  depositAddress: "",
1618
1784
  // swap settlement — the built message pays the DEX directly
1619
1785
  depositMemo: null,
@@ -1646,7 +1812,7 @@ function omnistonProvider(opts = {}) {
1646
1812
  if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
1647
1813
  throw new Error("omniston: swap quote is not a TON same-chain swap");
1648
1814
  }
1649
- 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");
1650
1816
  const owner = { chain: { $case: "ton", value: ref.trader } };
1651
1817
  const receiver = { chain: { $case: "ton", value: ref.dest } };
1652
1818
  const omni = await getClient();
@@ -1745,7 +1911,7 @@ function omnistonProvider(opts = {}) {
1745
1911
  }
1746
1912
  if (checked) {
1747
1913
  if (typeof opts.validateTonSwapLeg !== "function") {
1748
- 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)`);
1749
1915
  } else {
1750
1916
  for (const leg of checked.legs) {
1751
1917
  let ok = false;
@@ -1916,7 +2082,7 @@ function omnistonProvider(opts = {}) {
1916
2082
  }
1917
2083
  });
1918
2084
  const orderProtocol = (0, import_viem.getAddress)(protocol.chain.value);
1919
- return { chain: srcChain, to: orderProtocol, value: "0", chainId, approvalAddress: orderProtocol, typedData };
2085
+ return { chain: srcChain, to: orderProtocol, value: "0", chainId, approvalAddress: orderProtocol, typedData, orderExtension: `0x${bytesToHex(payload.orderExtension)}` };
1920
2086
  }
1921
2087
  return {
1922
2088
  name: "omniston",
@@ -2025,13 +2191,13 @@ function omnistonProvider(opts = {}) {
2025
2191
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
2026
2192
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
2027
2193
  if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2028
- 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");
2029
2195
  }
2030
2196
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
2031
2197
  const feeUsd = feeUnits / 10 ** dest.decimals;
2032
2198
  return {
2033
2199
  provider: "omniston",
2034
- intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain),
2200
+ intentId: encodeIntent(q.quoteId, trader, params.destAddress, params.sourceChain, "order", quoteDigest(q)),
2035
2201
  depositAddress: "",
2036
2202
  // order settlement — no static deposit address
2037
2203
  depositMemo: null,
@@ -2203,6 +2369,7 @@ function omnistonProvider(opts = {}) {
2203
2369
  );
2204
2370
  if (!order) return { state: "pending" };
2205
2371
  let secretsHex = await store.load(decoded.quoteId);
2372
+ const storedSecrets = secretsHex?.length ?? 0;
2206
2373
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2207
2374
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2208
2375
  const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
@@ -2211,6 +2378,7 @@ function omnistonProvider(opts = {}) {
2211
2378
  secretsHex = void 0;
2212
2379
  }
2213
2380
  }
2381
+ const disclosedNow = /* @__PURE__ */ new Set();
2214
2382
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2215
2383
  const q = cached.quote;
2216
2384
  const settlement = q.settlementData.value;
@@ -2309,11 +2477,15 @@ function omnistonProvider(opts = {}) {
2309
2477
  }
2310
2478
  await omni.orderDiscloseHtlcSecret({ quoteId: decoded.quoteId, executionIndex: i, secret: secretBytes }).then(async () => {
2311
2479
  trace("secret-disclosed", { index: i });
2480
+ disclosedNow.add(i);
2312
2481
  await store.save(disclosedKey(decoded.quoteId), ["1"]);
2313
2482
  }, (error) => trace("disclose-failed", { index: i, error: error instanceof Error ? error.message : String(error) }));
2314
2483
  })
2315
2484
  );
2316
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));
2317
2489
  const status = mapOrderStatus(order);
2318
2490
  const delivered = order.executions[0]?.outputPositionAddress;
2319
2491
  const outCase = cached?.quote.outputAsset.chain.$case;
@@ -2338,6 +2510,14 @@ function omnistonProvider(opts = {}) {
2338
2510
  }).catch(() => null);
2339
2511
  if (hash) status.destTxHash = hash;
2340
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
+ }
2341
2521
  if (status.state === "completed") {
2342
2522
  await clearSecrets(decoded.quoteId);
2343
2523
  await quotes.clear(decoded.quoteId);