@anywalletkit/protocol-swap2-omniston 0.5.5 → 0.5.7
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 +3 -0
- package/dist/{chunk-FDSTD34Z.js → chunk-DMO24BFW.js} +282 -185
- package/dist/chunk-DMO24BFW.js.map +1 -0
- package/dist/{chunk-IX4NF2J7.js → chunk-NOIL6LQK.js} +2 -2
- package/dist/index.cjs +281 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +225 -139
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +281 -184
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-FDSTD34Z.js.map +0 -1
- /package/dist/{chunk-IX4NF2J7.js.map → chunk-NOIL6LQK.js.map} +0 -0
|
@@ -317,6 +317,7 @@ function sameJetton(a, b) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// src/provider.ts
|
|
320
|
+
import { refusal, refusalNotes, refusalOf, withReason } from "@anywalletkit/protocol-swap2-core/errors";
|
|
320
321
|
import { bigintToNumber } from "@anywalletkit/core";
|
|
321
322
|
var EVM_CASE = {
|
|
322
323
|
1: "ethereum",
|
|
@@ -454,6 +455,54 @@ function encodeTypedMessage(td) {
|
|
|
454
455
|
encodeAbiParameters([paramFor(td.primaryType)], [td.message])
|
|
455
456
|
);
|
|
456
457
|
}
|
|
458
|
+
function venueReason(error) {
|
|
459
|
+
const details = typeof error === "object" && error !== null ? error.details : void 0;
|
|
460
|
+
const info = typeof details === "object" && details !== null ? details.error_info : void 0;
|
|
461
|
+
if (typeof info !== "object" || info === null) return "upstream_unavailable";
|
|
462
|
+
const { reason, metadata } = info;
|
|
463
|
+
const meta = typeof metadata === "object" && metadata !== null ? metadata : {};
|
|
464
|
+
const type = typeof meta.type === "string" ? meta.type : "";
|
|
465
|
+
switch (reason) {
|
|
466
|
+
case "INVALID_ARGUMENT":
|
|
467
|
+
case "INVALID_PARAMETERS":
|
|
468
|
+
return void 0;
|
|
469
|
+
case "NOT_FOUND":
|
|
470
|
+
return typeof meta.object_type === "string" && meta.object_type.toLowerCase() === "quote" ? "quote_expired" : "upstream_unavailable";
|
|
471
|
+
case "UNSUPPORTED":
|
|
472
|
+
return "unsupported_pair";
|
|
473
|
+
case "SWAP_LIMIT_EXCEEDED":
|
|
474
|
+
return "amount_too_large";
|
|
475
|
+
case "QUOTE_VALIDATION_FAILED":
|
|
476
|
+
if (type === "EMULATION_UNSUPPORTED_TOKEN") return "unsupported_pair";
|
|
477
|
+
if (type === "EMULATION_WRONG_OUTPUT_AMOUNT" || type === "EMULATION_REFUND_SLIPPAGE") return "price_check_failed";
|
|
478
|
+
return type.startsWith("EMULATION_") ? "simulation_failed" : "upstream_unavailable";
|
|
479
|
+
default:
|
|
480
|
+
return "upstream_unavailable";
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function venueFailure(error) {
|
|
484
|
+
if (!(error instanceof Error)) return error;
|
|
485
|
+
const reason = venueReason(error);
|
|
486
|
+
return reason ? withReason(error, reason) : error;
|
|
487
|
+
}
|
|
488
|
+
function mismatchReason(field) {
|
|
489
|
+
return field === "trader" || field === "dest" || field === "owner" ? "recipient_mismatch" : "policy_refused";
|
|
490
|
+
}
|
|
491
|
+
function tonSwapPayloadReason(code) {
|
|
492
|
+
switch (code) {
|
|
493
|
+
case "receiver-not-destination":
|
|
494
|
+
case "refund-not-trader":
|
|
495
|
+
case "excess-not-trader":
|
|
496
|
+
return "recipient_mismatch";
|
|
497
|
+
case "min-out-zero":
|
|
498
|
+
case "min-out-below-quote":
|
|
499
|
+
return "price_check_failed";
|
|
500
|
+
case "leg-unverified":
|
|
501
|
+
return "vault_mismatch";
|
|
502
|
+
default:
|
|
503
|
+
return "policy_refused";
|
|
504
|
+
}
|
|
505
|
+
}
|
|
457
506
|
function generateSecret() {
|
|
458
507
|
return crypto.getRandomValues(new Uint8Array(32));
|
|
459
508
|
}
|
|
@@ -464,7 +513,7 @@ function hashlock(secret, hashingFunction) {
|
|
|
464
513
|
case "HASHING_FUNCTION_SHA256":
|
|
465
514
|
return sha256(secret, "bytes");
|
|
466
515
|
default:
|
|
467
|
-
throw
|
|
516
|
+
throw refusal("policy_refused", `omniston: unsupported HTLC hashing function ${String(hashingFunction)}`);
|
|
468
517
|
}
|
|
469
518
|
}
|
|
470
519
|
function sameAsset(a, b) {
|
|
@@ -502,7 +551,7 @@ function stepJetton(asset) {
|
|
|
502
551
|
}
|
|
503
552
|
function readBigEndian(bytes, offset, length) {
|
|
504
553
|
if (!Number.isInteger(offset) || !Number.isInteger(length) || length < 1 || offset < 0 || offset + length > bytes.length) {
|
|
505
|
-
throw
|
|
554
|
+
throw refusal("policy_refused", "omniston: truncated EVM order extension");
|
|
506
555
|
}
|
|
507
556
|
let value = 0n;
|
|
508
557
|
for (let index = offset; index < offset + length; index++) value = value << 8n | BigInt(bytes[index]);
|
|
@@ -547,30 +596,30 @@ var EVM_DEST_PROTOCOL_FEE_RECIPIENTS = /* @__PURE__ */ new Set(["0x076268146b4ec
|
|
|
547
596
|
var EVM_AUCTION_POINTS = [[800000n, 100n], [700000n, 100n], [600000n, 100n], [500000n, 100n], [400000n, 100n]];
|
|
548
597
|
var EVM_TIMELOCKS = [1n, 120n, 240n, 300n, 1n, 120n, 180n, 0n].reduce((packed, value, index) => packed | value << BigInt(index * 32), 0n);
|
|
549
598
|
function readEvmOrderExtension(extension) {
|
|
550
|
-
if (extension.length < 32) throw
|
|
599
|
+
if (extension.length < 32) throw refusal("policy_refused", "omniston: malformed EVM order extension");
|
|
551
600
|
const view = new DataView(extension.buffer, extension.byteOffset, extension.byteLength);
|
|
552
601
|
const fields = [];
|
|
553
602
|
let begin = 0;
|
|
554
603
|
const concatLength = extension.length - 32;
|
|
555
604
|
for (let index = 0; index < 8; index++) {
|
|
556
605
|
const end2 = view.getUint32(28 - index * 4, false);
|
|
557
|
-
if (end2 < begin || end2 > concatLength) throw
|
|
606
|
+
if (end2 < begin || end2 > concatLength) throw refusal("policy_refused", "omniston: malformed EVM order extension offsets");
|
|
558
607
|
fields.push(extension.slice(32 + begin, 32 + end2));
|
|
559
608
|
begin = end2;
|
|
560
609
|
}
|
|
561
610
|
for (const index of [0, 1, 4, 5, 6]) {
|
|
562
|
-
if (fields[index].length !== 0) throw
|
|
611
|
+
if (fields[index].length !== 0) throw refusal("policy_refused", "omniston: unsupported EVM order extension field");
|
|
563
612
|
}
|
|
564
613
|
if (!sliceEqual(fields[2], fields[3])) {
|
|
565
|
-
throw
|
|
614
|
+
throw refusal("policy_refused", "omniston: making/taking amount getters use different programs");
|
|
566
615
|
}
|
|
567
616
|
let target;
|
|
568
617
|
for (const index of [2, 3, 7]) {
|
|
569
618
|
const field2 = fields[index];
|
|
570
|
-
if (field2.length <= 20) throw
|
|
619
|
+
if (field2.length <= 20) throw refusal("policy_refused", "omniston: incomplete EVM order extension program");
|
|
571
620
|
const address = getAddress(`0x${bytesToHex(field2.slice(0, 20))}`);
|
|
572
621
|
if (target !== void 0 && getAddress(target) !== address) {
|
|
573
|
-
throw
|
|
622
|
+
throw refusal("vault_mismatch", "omniston: EVM order extension target is not independently trusted");
|
|
574
623
|
}
|
|
575
624
|
target = address;
|
|
576
625
|
}
|
|
@@ -588,7 +637,7 @@ function readEvmOrderExtension(extension) {
|
|
|
588
637
|
cursor += 3;
|
|
589
638
|
const pointCount = bigintToNumber(readBigEndian(field, cursor, 1));
|
|
590
639
|
cursor += 1;
|
|
591
|
-
if (pointCount !== 5) throw
|
|
640
|
+
if (pointCount !== 5) throw refusal("policy_refused", "omniston: unsupported EVM auction profile");
|
|
592
641
|
const points = [];
|
|
593
642
|
for (let index = 0; index < pointCount; index++) {
|
|
594
643
|
points.push([readBigEndian(field, cursor, 3), readBigEndian(field, cursor + 3, 2)]);
|
|
@@ -605,7 +654,7 @@ function readEvmOrderExtension(extension) {
|
|
|
605
654
|
const getterWhitelistCount = bigintToNumber(readBigEndian(field, cursor, 1));
|
|
606
655
|
cursor += 1;
|
|
607
656
|
if (getterWhitelistCount !== 1 || cursor + getterWhitelistCount * 10 !== field.length) {
|
|
608
|
-
throw
|
|
657
|
+
throw refusal("policy_refused", "omniston: malformed EVM amount-getter whitelist");
|
|
609
658
|
}
|
|
610
659
|
const getter = { gasBump, gasPrice, auctionStart, duration, initialRateBump, points, integratorFee, integratorShare, resolverFee, whitelistDiscount, resolver: readBigEndian(field, cursor, 10) };
|
|
611
660
|
const post = fields[7];
|
|
@@ -627,7 +676,7 @@ function readEvmOrderExtension(extension) {
|
|
|
627
676
|
const whitelistCount = bigintToNumber(readBigEndian(post, cursor, 1));
|
|
628
677
|
cursor += 1;
|
|
629
678
|
if (whitelistCount !== 1 || cursor + whitelistCount * 12 + 160 !== post.length) {
|
|
630
|
-
throw
|
|
679
|
+
throw refusal("policy_refused", "omniston: malformed EVM post-interaction whitelist");
|
|
631
680
|
}
|
|
632
681
|
const postResolver = readBigEndian(post, cursor, 10);
|
|
633
682
|
cursor += 10;
|
|
@@ -639,7 +688,7 @@ function readEvmOrderExtension(extension) {
|
|
|
639
688
|
return value;
|
|
640
689
|
});
|
|
641
690
|
const customData = extension.slice(32 + begin);
|
|
642
|
-
if (customData.length !== 131) throw
|
|
691
|
+
if (customData.length !== 131) throw refusal("policy_refused", "omniston: malformed EVM order extension custom data");
|
|
643
692
|
return {
|
|
644
693
|
target,
|
|
645
694
|
getter,
|
|
@@ -652,29 +701,29 @@ async function validateEvmToTonOrderExtension(extension, expected) {
|
|
|
652
701
|
const evmDest = expected.dstChainId !== void 0;
|
|
653
702
|
if (evmDest && (!Number.isSafeInteger(expected.dstChainId) || expected.dstChainId <= 0)) throw new Error("omniston: invalid EVM destination chain id");
|
|
654
703
|
const { target, getter, post, extra, customData } = readEvmOrderExtension(extension);
|
|
655
|
-
if (!expected.trustedTarget(target)) throw
|
|
704
|
+
if (!expected.trustedTarget(target)) throw refusal("vault_mismatch", "omniston: EVM order extension target is not independently trusted");
|
|
656
705
|
const quoteResolverFee = expected.protocolFeePips % 10 === 0 ? BigInt(expected.protocolFeePips / 10) : -1n;
|
|
657
706
|
const resolverLow80 = BigInt(getAddress(expected.initialResolverAddress)) & (1n << 80n) - 1n;
|
|
658
707
|
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) {
|
|
659
|
-
throw
|
|
708
|
+
throw refusal("policy_refused", "omniston: EVM amount-getter terms do not match the pinned v1beta8 profile");
|
|
660
709
|
}
|
|
661
710
|
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) {
|
|
662
|
-
throw
|
|
711
|
+
throw refusal("policy_refused", "omniston: EVM post-interaction terms do not match the pinned v1beta8 profile");
|
|
663
712
|
}
|
|
664
713
|
const hashlock2 = readBigEndian(expected.secretHash, 0, expected.secretHash.length);
|
|
665
714
|
const dstToken = evmDest ? BigInt(getAddress(expected.outputTokenAddress)) : BigInt(getAddress(expected.takerAsset));
|
|
666
715
|
const deposits = BigInt(expected.srcSecurityDepositUnits) << 128n | BigInt(expected.dstSecurityDepositUnits);
|
|
667
716
|
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) {
|
|
668
|
-
throw
|
|
717
|
+
throw refusal("policy_refused", "omniston: signed EVM HTLC terms do not match the validated order details");
|
|
669
718
|
}
|
|
670
719
|
await checkOrderCustomData(customData, { evmDest, outputToken: expected.outputTokenAddress, traderDst: expected.traderDstAddress, quoteId: expected.quoteId });
|
|
671
720
|
if (expected.tradeStartDeadline < expected.quoteTimestamp) {
|
|
672
|
-
throw
|
|
721
|
+
throw refusal("quote_expired", "omniston: invalid EVM order quote deadline");
|
|
673
722
|
}
|
|
674
723
|
}
|
|
675
724
|
async function checkOrderCustomData(customData, expected) {
|
|
676
725
|
if (expected.evmDest) {
|
|
677
|
-
if (!zeroBytes(customData.slice(0, 66))) throw
|
|
726
|
+
if (!zeroBytes(customData.slice(0, 66))) throw refusal("recipient_mismatch", "omniston: unexpected TON addresses in an EVM-destination order");
|
|
678
727
|
} else {
|
|
679
728
|
const { Address: Address2 } = await import("@ton/core");
|
|
680
729
|
const expectedTon = [expected.outputToken, expected.traderDst].map((value) => Address2.parse(value));
|
|
@@ -682,16 +731,16 @@ async function checkOrderCustomData(customData, expected) {
|
|
|
682
731
|
const offset = index * 33;
|
|
683
732
|
const workchain = customData[offset] === 255 ? -1 : customData[offset];
|
|
684
733
|
if (workchain !== expectedTon[index].workChain || !sliceEqual(customData.slice(offset + 1, offset + 33), expectedTon[index].hash)) {
|
|
685
|
-
throw
|
|
734
|
+
throw refusal("recipient_mismatch", "omniston: signed EVM\u2192TON address does not match the validated request");
|
|
686
735
|
}
|
|
687
736
|
}
|
|
688
737
|
}
|
|
689
738
|
if (!zeroBytes(customData.slice(66, 99))) {
|
|
690
|
-
throw
|
|
739
|
+
throw refusal("policy_refused", "omniston: unexpected private TON disclosure address");
|
|
691
740
|
}
|
|
692
741
|
const quoteId = expected.quoteId.replace(/^0x/, "");
|
|
693
742
|
if (!QUOTE_ID.test(quoteId) || !sliceEqual(customData.slice(99), hexToBytes(quoteId.padStart(64, "0")))) {
|
|
694
|
-
throw
|
|
743
|
+
throw refusal("policy_refused", "omniston: EVM order extension custom data is not bound to the quote id");
|
|
695
744
|
}
|
|
696
745
|
}
|
|
697
746
|
function validateEvmTypedOrder(td, expected) {
|
|
@@ -713,7 +762,7 @@ function validateEvmTypedOrder(td, expected) {
|
|
|
713
762
|
if (value.domain?.name !== "1inch Limit Order Protocol" || value.domain.version !== "4" || typeof value.domain.verifyingContract !== "string" || getAddress(value.domain.verifyingContract) !== getAddress(expected.settlementContract) || Number(value.domain.chainId) !== expected.chainId || value.primaryType !== "Order" || !exactSchema || !message || !exactMessage || getAddress(String(message.maker)) !== getAddress(expected.owner) || getAddress(String(message.receiver)) !== getAddress(expected.receiver) || getAddress(String(message.makerAsset)) !== getAddress(expected.makerAsset) || !validUint256(salt) || (BigInt(salt) & (1n << 160n) - 1n) !== (extensionHash & (1n << 160n) - 1n) || // The order expires at the quote's trade-start deadline (±15 s of clock
|
|
714
763
|
// skew between the quote and the order builder), never later.
|
|
715
764
|
traits < 0n || !flagsOk || allowedSender !== 0n || series !== 0n || !within(expiration, expected.tradeStartDeadline, 15, 15) || expiration <= BigInt(expected.quoteTimestamp) || !validEvmAddress(message.takerAsset) || getAddress(String(message.takerAsset)) === getAddress(expected.makerAsset) || !validUint256(message.makingAmount) || BigInt(message.makingAmount) !== BigInt(expected.makingAmount) || !validUint256(message.takingAmount) || BigInt(message.takingAmount) !== BigInt(expected.takingAmount)) {
|
|
716
|
-
throw
|
|
765
|
+
throw refusal("policy_refused", "omniston: EVM order fields do not match the validated quote/request");
|
|
717
766
|
}
|
|
718
767
|
}
|
|
719
768
|
async function validateTonEscrowPayload(message, expected, validateForwardPayload) {
|
|
@@ -723,9 +772,9 @@ async function validateTonEscrowPayload(message, expected, validateForwardPayloa
|
|
|
723
772
|
try {
|
|
724
773
|
roots = Cell2.fromBoc(Buffer2.from(boc));
|
|
725
774
|
} catch {
|
|
726
|
-
throw
|
|
775
|
+
throw refusal("policy_refused", "omniston: malformed TON escrow BoC");
|
|
727
776
|
}
|
|
728
|
-
if (roots.length !== 1) throw
|
|
777
|
+
if (roots.length !== 1) throw refusal("policy_refused", "omniston: TON escrow must contain exactly one root cell");
|
|
729
778
|
const slice = roots[0].beginParse();
|
|
730
779
|
const op = slice.loadUint(32);
|
|
731
780
|
slice.loadUintBig(64);
|
|
@@ -736,7 +785,7 @@ async function validateTonEscrowPayload(message, expected, validateForwardPayloa
|
|
|
736
785
|
const forwardAmount = slice.loadCoins();
|
|
737
786
|
const payloadByRef = slice.loadBit();
|
|
738
787
|
if (op !== 260734629 || amount !== BigInt(expected.inputUnits) || !(destination instanceof Address2) || !destination.equals(Address2.parse(expected.protocolAddress)) || response !== null && (!(response instanceof Address2) || !response.equals(Address2.parse(expected.ownerAddress))) || customPayload !== null || forwardAmount <= 0n || forwardAmount > BigInt(message.sendAmount) || !payloadByRef || slice.remainingBits !== 0 || slice.remainingRefs !== 1) {
|
|
739
|
-
throw
|
|
788
|
+
throw refusal("policy_refused", "omniston: TON jetton transfer does not match the validated quote/request");
|
|
740
789
|
}
|
|
741
790
|
const forwardPayload = slice.loadRef();
|
|
742
791
|
const verified = await validateForwardPayload({
|
|
@@ -756,7 +805,7 @@ async function validateTonEscrowPayload(message, expected, validateForwardPayloa
|
|
|
756
805
|
...expected.hashingFunction ? { hashingFunction: expected.hashingFunction } : {},
|
|
757
806
|
exclusivityTimeout: expected.exclusivityTimeout
|
|
758
807
|
});
|
|
759
|
-
if (!verified) throw
|
|
808
|
+
if (!verified) throw refusal("policy_refused", "omniston: TON escrow forward payload failed independent semantic validation");
|
|
760
809
|
}
|
|
761
810
|
function feeToken(asset) {
|
|
762
811
|
return asset.assetId || asset.contract || asset.symbol;
|
|
@@ -786,24 +835,24 @@ var ProtoReader = class {
|
|
|
786
835
|
const byte = this.bytes[this.pos++];
|
|
787
836
|
out += (byte & 127) * 2 ** shift;
|
|
788
837
|
if ((byte & 128) === 0) {
|
|
789
|
-
if (!Number.isSafeInteger(out)) throw
|
|
838
|
+
if (!Number.isSafeInteger(out)) throw refusal("policy_refused", "omniston: unsafe protobuf varint");
|
|
790
839
|
return out;
|
|
791
840
|
}
|
|
792
841
|
shift += 7;
|
|
793
842
|
}
|
|
794
|
-
throw
|
|
843
|
+
throw refusal("policy_refused", "omniston: malformed protobuf varint");
|
|
795
844
|
}
|
|
796
845
|
field() {
|
|
797
846
|
const tag = this.varint();
|
|
798
847
|
const number = Math.floor(tag / 8);
|
|
799
848
|
const wire = tag & 7;
|
|
800
|
-
if (number < 1) throw
|
|
849
|
+
if (number < 1) throw refusal("policy_refused", "omniston: malformed protobuf field");
|
|
801
850
|
return { number, wire };
|
|
802
851
|
}
|
|
803
852
|
lengthDelimited() {
|
|
804
853
|
const length = this.varint();
|
|
805
854
|
const end2 = this.pos + length;
|
|
806
|
-
if (length < 0 || end2 > this.bytes.length) throw
|
|
855
|
+
if (length < 0 || end2 > this.bytes.length) throw refusal("policy_refused", "omniston: truncated protobuf payload");
|
|
807
856
|
const value = this.bytes.slice(this.pos, end2);
|
|
808
857
|
this.pos = end2;
|
|
809
858
|
return value;
|
|
@@ -825,10 +874,10 @@ var ProtoReader = class {
|
|
|
825
874
|
this.advance(4);
|
|
826
875
|
return;
|
|
827
876
|
}
|
|
828
|
-
throw
|
|
877
|
+
throw refusal("policy_refused", "omniston: unsupported protobuf wire type");
|
|
829
878
|
}
|
|
830
879
|
advance(length) {
|
|
831
|
-
if (this.pos + length > this.bytes.length) throw
|
|
880
|
+
if (this.pos + length > this.bytes.length) throw refusal("policy_refused", "omniston: truncated protobuf payload");
|
|
832
881
|
this.pos += length;
|
|
833
882
|
}
|
|
834
883
|
};
|
|
@@ -855,10 +904,10 @@ function decodeChainAddress(bytes) {
|
|
|
855
904
|
while (!reader.done) {
|
|
856
905
|
const { number, wire } = reader.field();
|
|
857
906
|
const $case = PROTO_CASE_BY_FIELD[number];
|
|
858
|
-
if (!$case || wire !== 2 || address) throw
|
|
907
|
+
if (!$case || wire !== 2 || address) throw refusal("policy_refused", "omniston: malformed chain address in order details");
|
|
859
908
|
address = { chain: { $case, value: utf8.decode(reader.lengthDelimited()) } };
|
|
860
909
|
}
|
|
861
|
-
if (!address?.chain.value) throw
|
|
910
|
+
if (!address?.chain.value) throw refusal("policy_refused", "omniston: empty chain address in order details");
|
|
862
911
|
return address;
|
|
863
912
|
}
|
|
864
913
|
function decodeAssetId(bytes) {
|
|
@@ -867,27 +916,27 @@ function decodeAssetId(bytes) {
|
|
|
867
916
|
while (!reader.done) {
|
|
868
917
|
const { number, wire } = reader.field();
|
|
869
918
|
const $case = PROTO_CASE_BY_FIELD[number];
|
|
870
|
-
if (!$case || wire !== 2 || asset) throw
|
|
919
|
+
if (!$case || wire !== 2 || asset) throw refusal("policy_refused", "omniston: malformed asset id in order details");
|
|
871
920
|
const kindReader = new ProtoReader(reader.lengthDelimited());
|
|
872
921
|
const kindField = kindReader.field();
|
|
873
922
|
if (kindField.wire !== 2 || kindField.number !== 1 && kindField.number !== 2) {
|
|
874
|
-
throw
|
|
923
|
+
throw refusal("policy_refused", "omniston: unsupported asset kind in order details");
|
|
875
924
|
}
|
|
876
925
|
const kindCase = kindField.number === 1 ? "native" : $case === "ton" ? "jetton" : $case === "tron" ? "trc20" : "erc20";
|
|
877
926
|
const value = kindField.number === 1 ? {} : utf8.decode(kindReader.lengthDelimited());
|
|
878
|
-
if (!kindReader.done || kindField.number === 2 && !value) throw
|
|
927
|
+
if (!kindReader.done || kindField.number === 2 && !value) throw refusal("policy_refused", "omniston: malformed asset kind in order details");
|
|
879
928
|
asset = { chain: { $case, value: { kind: { $case: kindCase, value } } } };
|
|
880
929
|
}
|
|
881
|
-
if (!asset) throw
|
|
930
|
+
if (!asset) throw refusal("policy_refused", "omniston: empty asset id in order details");
|
|
882
931
|
return asset;
|
|
883
932
|
}
|
|
884
933
|
function validateSerializedOrderDetails(bytes, expected) {
|
|
885
|
-
if (!bytes?.length) throw
|
|
934
|
+
if (!bytes?.length) throw refusal("policy_refused", "omniston: EVM HTLC order payload has no serialized details");
|
|
886
935
|
const wrapper = new ProtoReader(bytes);
|
|
887
936
|
const outer = wrapper.field();
|
|
888
|
-
if (outer.number !== 1 || outer.wire !== 2) throw
|
|
937
|
+
if (outer.number !== 1 || outer.wire !== 2) throw refusal("policy_refused", "omniston: unsupported HTLC order-details version");
|
|
889
938
|
const detailsBytes = wrapper.lengthDelimited();
|
|
890
|
-
if (!wrapper.done) throw
|
|
939
|
+
if (!wrapper.done) throw refusal("policy_refused", "omniston: ambiguous HTLC order-details wrapper");
|
|
891
940
|
const reader = new ProtoReader(detailsBytes);
|
|
892
941
|
let quoteId;
|
|
893
942
|
let outputAsset;
|
|
@@ -895,16 +944,16 @@ function validateSerializedOrderDetails(bytes, expected) {
|
|
|
895
944
|
const hashes = [];
|
|
896
945
|
while (!reader.done) {
|
|
897
946
|
const { number, wire } = reader.field();
|
|
898
|
-
if (wire !== 2) throw
|
|
947
|
+
if (wire !== 2) throw refusal("policy_refused", "omniston: malformed HTLC order details");
|
|
899
948
|
const value = reader.lengthDelimited();
|
|
900
949
|
if (number === 1 && quoteId === void 0) quoteId = utf8.decode(value);
|
|
901
950
|
else if (number === 2 && outputAsset === void 0) outputAsset = decodeAssetId(value);
|
|
902
951
|
else if (number === 3 && traderDstAddress === void 0) traderDstAddress = decodeChainAddress(value);
|
|
903
952
|
else if (number === 5) hashes.push(value);
|
|
904
|
-
else throw
|
|
953
|
+
else throw refusal("policy_refused", "omniston: duplicate or unexpected HTLC order-details field");
|
|
905
954
|
}
|
|
906
955
|
if (quoteId !== expected.quoteId || !outputAsset || !sameAsset(outputAsset, expected.outputAsset) || !traderDstAddress || !sameChainAddress(traderDstAddress, expected.traderDstAddress) || hashes.length !== expected.secretHashes.length || hashes.some((hash, i) => !bytesEqual(hash, expected.secretHashes[i]))) {
|
|
907
|
-
throw
|
|
956
|
+
throw refusal("policy_refused", "omniston: resolver order payload does not match the validated quote/request");
|
|
908
957
|
}
|
|
909
958
|
}
|
|
910
959
|
function isPositiveInteger(value) {
|
|
@@ -935,7 +984,7 @@ function bytesToHex(bytes) {
|
|
|
935
984
|
function hexToBytes(hex) {
|
|
936
985
|
const clean = hex.replace(/^0x/, "");
|
|
937
986
|
if (!clean || !/^(?:[0-9a-fA-F]{2})+$/.test(clean)) {
|
|
938
|
-
throw
|
|
987
|
+
throw refusal("policy_refused", "omniston: malformed hex payload");
|
|
939
988
|
}
|
|
940
989
|
const out = new Uint8Array(clean.length / 2);
|
|
941
990
|
for (let i = 0; i < out.length; i++) out[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
@@ -969,6 +1018,14 @@ function swapProtocols(v) {
|
|
|
969
1018
|
for (const route of v.routes) for (const step of route.steps) for (const chunk of step.chunks) names.add(chunk.protocol);
|
|
970
1019
|
return [...names];
|
|
971
1020
|
}
|
|
1021
|
+
function rejectionReason(rejection) {
|
|
1022
|
+
return rejection === "quote timestamp out of range" ? "quote_expired" : "upstream_unavailable";
|
|
1023
|
+
}
|
|
1024
|
+
function rfqReason(outcome) {
|
|
1025
|
+
if (outcome.error) return outcome.errorReason;
|
|
1026
|
+
if (outcome.rejected) return rejectionReason(outcome.rejected);
|
|
1027
|
+
return outcome.answered ? "no_route" : "upstream_unavailable";
|
|
1028
|
+
}
|
|
972
1029
|
function quoteRejection(q, update, acknowledgedRfqId, request, settlement) {
|
|
973
1030
|
if (!q) return "empty quote";
|
|
974
1031
|
if (!acknowledgedRfqId || update.rfqId !== acknowledgedRfqId || q.rfqId !== acknowledgedRfqId) return "quote for another RFQ";
|
|
@@ -1008,6 +1065,7 @@ function awaitQuote(stream, request, signal, settleMs = QUOTE_SETTLE_MS, settlem
|
|
|
1008
1065
|
const max = setTimeout(() => finish(latest), QUOTE_MAX_MS);
|
|
1009
1066
|
sub = stream.subscribe({
|
|
1010
1067
|
next: (event) => {
|
|
1068
|
+
if (event.$case === "ack" || event.$case === "quoteUpdated" || event.$case === "noQuote") outcome.answered = true;
|
|
1011
1069
|
if (event.$case === "ack") {
|
|
1012
1070
|
acknowledgedRfqId = event.value.rfqId || null;
|
|
1013
1071
|
} else if (event.$case === "quoteUpdated") {
|
|
@@ -1027,6 +1085,7 @@ function awaitQuote(stream, request, signal, settleMs = QUOTE_SETTLE_MS, settlem
|
|
|
1027
1085
|
},
|
|
1028
1086
|
error: (err) => {
|
|
1029
1087
|
outcome.error = String(err?.message ?? err).slice(0, 200);
|
|
1088
|
+
outcome.errorReason = venueReason(err);
|
|
1030
1089
|
finish(null);
|
|
1031
1090
|
},
|
|
1032
1091
|
complete: () => finish(latest)
|
|
@@ -1068,9 +1127,9 @@ function awaitSwapProgress(stream) {
|
|
|
1068
1127
|
}
|
|
1069
1128
|
var JETTON_TRANSFER_OP = 260734629;
|
|
1070
1129
|
function parseSwapPayload(payloadHex) {
|
|
1071
|
-
if (!/^(?:[0-9a-fA-F]{2})+$/.test(payloadHex)) throw
|
|
1130
|
+
if (!/^(?:[0-9a-fA-F]{2})+$/.test(payloadHex)) throw refusal("policy_refused", "omniston: malformed swap payload hex");
|
|
1072
1131
|
const roots = TonCell.fromBoc(Buffer2.from(payloadHex, "hex"));
|
|
1073
|
-
if (roots.length !== 1) throw
|
|
1132
|
+
if (roots.length !== 1) throw refusal("policy_refused", "omniston: swap payload must have one root");
|
|
1074
1133
|
const s = roots[0].beginParse();
|
|
1075
1134
|
const op = s.loadUint(32);
|
|
1076
1135
|
if (op !== JETTON_TRANSFER_OP) return { op, jettonTransfer: null };
|
|
@@ -1142,86 +1201,108 @@ function mapOrderStatus(order) {
|
|
|
1142
1201
|
}
|
|
1143
1202
|
function omnistonPins(trustedEvmOrderProtocols) {
|
|
1144
1203
|
const byChain = new Map(Object.entries(trustedEvmOrderProtocols).map(([chain, list]) => [chain, list.map((a) => getAddress(a))]));
|
|
1145
|
-
return Object.freeze({
|
|
1204
|
+
return Object.freeze({
|
|
1205
|
+
callTargets: () => [],
|
|
1206
|
+
spenders: (chain) => byChain.get(chain) ?? [],
|
|
1207
|
+
// The reason behind a text omnistonVerifyBuild returned, so the remote stub's refusal carries it.
|
|
1208
|
+
reasonOf: verifyNotes.reasonOf
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
var VERIFY_NOTES = /* @__PURE__ */ Symbol.for("@anywalletkit/protocol-swap2-omniston/verify-notes@1");
|
|
1212
|
+
var verifyNotes = (() => {
|
|
1213
|
+
const shared = globalThis[VERIFY_NOTES];
|
|
1214
|
+
if (shared && typeof shared.note === "function" && typeof shared.reasonOf === "function") return shared;
|
|
1215
|
+
const notes = refusalNotes();
|
|
1216
|
+
try {
|
|
1217
|
+
Object.defineProperty(globalThis, VERIFY_NOTES, { value: notes });
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
return notes;
|
|
1221
|
+
})();
|
|
1222
|
+
var said = verifyNotes.note;
|
|
1223
|
+
function tonEscrowBuildReason(text) {
|
|
1224
|
+
if (text.startsWith("the escrow transfer pays ")) return "vault_mismatch";
|
|
1225
|
+
if (text.startsWith("the resolver is bound to deliver ")) return "price_check_failed";
|
|
1226
|
+
return "policy_refused";
|
|
1146
1227
|
}
|
|
1147
1228
|
var MAX_EVM_ORDER_LIFETIME_S = 900;
|
|
1148
1229
|
async function checkRemoteEvmOrder(quote, tx, params, trusted) {
|
|
1149
1230
|
const chainId = evmRef(params.sourceChain);
|
|
1150
|
-
if (chainId === null || !params.sourceChain.startsWith("eip155:")) return "a typed order needs an EVM source";
|
|
1231
|
+
if (chainId === null || !params.sourceChain.startsWith("eip155:")) return said("a typed order needs an EVM source", "policy_refused");
|
|
1151
1232
|
const intent = decodeIntent(quote.intentId.replace(/~[0-9a-f]{32}$/, ""));
|
|
1152
|
-
if (!intent || intent.kind !== "order" || intent.srcChain !== params.sourceChain) return "the quote's intent is not an order from this chain";
|
|
1153
|
-
if (!tx.orderExtension || !/^0x(?:[0-9a-fA-F]{2})+$/.test(tx.orderExtension)) return "the build carries no order extension to check";
|
|
1233
|
+
if (!intent || intent.kind !== "order" || intent.srcChain !== params.sourceChain) return said("the quote's intent is not an order from this chain", "policy_refused");
|
|
1234
|
+
if (!tx.orderExtension || !/^0x(?:[0-9a-fA-F]{2})+$/.test(tx.orderExtension)) return said("the build carries no order extension to check", "policy_refused");
|
|
1154
1235
|
const source = ccAsset(params.sourceChain, params.sourceToken);
|
|
1155
|
-
if (!source?.contract) return "unknown source token";
|
|
1236
|
+
if (!source?.contract) return said("unknown source token", "unsupported_pair");
|
|
1156
1237
|
const tonDest = isTon(params.destChain);
|
|
1157
1238
|
const destChainId = tonDest ? null : evmRef(params.destChain);
|
|
1158
|
-
if (!tonDest && (destChainId === null || !params.destChain.startsWith("eip155:"))) return "the destination is neither TON nor an EVM chain";
|
|
1239
|
+
if (!tonDest && (destChainId === null || !params.destChain.startsWith("eip155:"))) return said("the destination is neither TON nor an EVM chain", "unsupported_pair");
|
|
1159
1240
|
const outputToken = ccAssetByToken(params.destChain, params.destToken)?.contract ?? params.destToken;
|
|
1160
1241
|
const listed = (table, chain, address) => typeof address === "string" && validEvmAddress(address) && (table[chain] ?? []).some((a) => validEvmAddress(a) && getAddress(a) === getAddress(address));
|
|
1161
1242
|
let td;
|
|
1162
1243
|
try {
|
|
1163
1244
|
td = JSON.parse(tx.typedData ?? "");
|
|
1164
1245
|
} catch {
|
|
1165
|
-
return "the typed data is not JSON";
|
|
1246
|
+
return said("the typed data is not JSON", "policy_refused");
|
|
1166
1247
|
}
|
|
1167
1248
|
const domain = td.domain ?? {};
|
|
1168
1249
|
const contract = domain.verifyingContract;
|
|
1169
|
-
if (domain.name !== "1inch Limit Order Protocol" || domain.version !== "4") return "the domain is not the 1inch Limit Order Protocol v4";
|
|
1250
|
+
if (domain.name !== "1inch Limit Order Protocol" || domain.version !== "4") return said("the domain is not the 1inch Limit Order Protocol v4", "policy_refused");
|
|
1170
1251
|
if (!listed(trusted.orderProtocols, params.sourceChain, contract) || !tx.approvalAddress || getAddress(tx.approvalAddress) !== getAddress(String(contract))) {
|
|
1171
|
-
return `${String(contract)} is not a trusted order protocol on ${params.sourceChain}
|
|
1252
|
+
return said(`${String(contract)} is not a trusted order protocol on ${params.sourceChain}`, "vault_mismatch");
|
|
1172
1253
|
}
|
|
1173
|
-
if (Number(domain.chainId) !== chainId || tx.chainId !== void 0 && tx.chainId !== chainId) return "the domain chainId is not the source chain";
|
|
1254
|
+
if (Number(domain.chainId) !== chainId || tx.chainId !== void 0 && tx.chainId !== chainId) return said("the domain chainId is not the source chain", "policy_refused");
|
|
1174
1255
|
const order = td.types?.Order;
|
|
1175
1256
|
const message = td.message;
|
|
1176
1257
|
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]))) {
|
|
1177
|
-
return "the typed data is not exactly a v4 Order";
|
|
1258
|
+
return said("the typed data is not exactly a v4 Order", "policy_refused");
|
|
1178
1259
|
}
|
|
1179
|
-
if (!validEvmAddress(intent.trader) || getAddress(String(message.maker)) !== getAddress(intent.trader)) return "the maker is not the trader";
|
|
1180
|
-
if (getAddress(String(message.makerAsset)) !== getAddress(source.contract)) return "the makerAsset is not the source token";
|
|
1181
|
-
if (BigInt(message.makingAmount) !== BigInt(quote.sourceAmount)) return "the makingAmount is not the input";
|
|
1182
|
-
if (getAddress(String(message.takerAsset)) === getAddress(String(message.makerAsset))) return "the takerAsset is the makerAsset";
|
|
1260
|
+
if (!validEvmAddress(intent.trader) || getAddress(String(message.maker)) !== getAddress(intent.trader)) return said("the maker is not the trader", "policy_refused");
|
|
1261
|
+
if (getAddress(String(message.makerAsset)) !== getAddress(source.contract)) return said("the makerAsset is not the source token", "policy_refused");
|
|
1262
|
+
if (BigInt(message.makingAmount) !== BigInt(quote.sourceAmount)) return said("the makingAmount is not the input", "policy_refused");
|
|
1263
|
+
if (getAddress(String(message.takerAsset)) === getAddress(String(message.makerAsset))) return said("the takerAsset is the makerAsset", "policy_refused");
|
|
1183
1264
|
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);
|
|
1184
|
-
if (floor <= 0n || BigInt(message.takingAmount) < floor) return `takingAmount ${String(message.takingAmount)} is below the quote's minimum ${floor}
|
|
1265
|
+
if (floor <= 0n || BigInt(message.takingAmount) < floor) return said(`takingAmount ${String(message.takingAmount)} is below the quote's minimum ${floor}`, "price_check_failed");
|
|
1185
1266
|
const traits = BigInt(message.makerTraits);
|
|
1186
1267
|
const flags = traits & (1n << 56n) - 1n << 200n;
|
|
1187
1268
|
const baseFlags = 1n << 251n | 1n << 249n;
|
|
1188
|
-
if (flags !== baseFlags && flags !== (baseFlags | 1n << 255n)) return "the makerTraits flags are not a single-fill order with its extension";
|
|
1189
|
-
if ((traits & (1n << 80n) - 1n) !== 0n || (traits >> 160n & (1n << 40n) - 1n) !== 0n) return "the makerTraits restrict the taker or the series";
|
|
1269
|
+
if (flags !== baseFlags && flags !== (baseFlags | 1n << 255n)) return said("the makerTraits flags are not a single-fill order with its extension", "policy_refused");
|
|
1270
|
+
if ((traits & (1n << 80n) - 1n) !== 0n || (traits >> 160n & (1n << 40n) - 1n) !== 0n) return said("the makerTraits restrict the taker or the series", "policy_refused");
|
|
1190
1271
|
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
1191
1272
|
const expiration = traits >> 80n & (1n << 40n) - 1n;
|
|
1192
|
-
if (expiration <= now || expiration > now + BigInt(MAX_EVM_ORDER_LIFETIME_S)) return "the order's expiry is not minutes away";
|
|
1273
|
+
if (expiration <= now || expiration > now + BigInt(MAX_EVM_ORDER_LIFETIME_S)) return said("the order's expiry is not minutes away", expiration <= now ? "quote_expired" : "policy_refused");
|
|
1193
1274
|
const extension = viemHexToBytes(tx.orderExtension);
|
|
1194
1275
|
const mask = (1n << 160n) - 1n;
|
|
1195
|
-
if ((BigInt(message.salt) & mask) !== (BigInt(keccak256(extension)) & mask)) return "the salt does not commit the order extension";
|
|
1276
|
+
if ((BigInt(message.salt) & mask) !== (BigInt(keccak256(extension)) & mask)) return said("the salt does not commit the order extension", "policy_refused");
|
|
1196
1277
|
let parsed;
|
|
1197
1278
|
try {
|
|
1198
1279
|
parsed = readEvmOrderExtension(extension);
|
|
1199
1280
|
} catch (e) {
|
|
1200
|
-
return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
|
|
1281
|
+
return said(e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e), refusalOf(e) ?? "policy_refused");
|
|
1201
1282
|
}
|
|
1202
1283
|
const { target, getter, post, extra, customData } = parsed;
|
|
1203
|
-
if (!listed(trusted.factories, params.sourceChain, target)) return `the extension targets ${target}, not a trusted escrow factory on ${params.sourceChain}
|
|
1284
|
+
if (!listed(trusted.factories, params.sourceChain, target)) return said(`the extension targets ${target}, not a trusted escrow factory on ${params.sourceChain}`, "vault_mismatch");
|
|
1204
1285
|
const receiverLow80 = BigInt(getAddress(String(message.receiver))) & (1n << 80n) - 1n;
|
|
1205
|
-
if (getter.resolver !== receiverLow80 || post.resolver !== receiverLow80) return "the order's receiver is not the resolver its extension whitelists";
|
|
1286
|
+
if (getter.resolver !== receiverLow80 || post.resolver !== receiverLow80) return said("the order's receiver is not the resolver its extension whitelists", "recipient_mismatch");
|
|
1206
1287
|
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, bigintToNumber(now), MAX_EVM_ORDER_LIFETIME_S, 60)) {
|
|
1207
|
-
return "the amount-getter terms do not match the pinned v1beta8 profile";
|
|
1288
|
+
return said("the amount-getter terms do not match the pinned v1beta8 profile", "policy_refused");
|
|
1208
1289
|
}
|
|
1209
1290
|
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, bigintToNumber(getter.auctionStart), 60, 5)) {
|
|
1210
|
-
return "the post-interaction terms do not match the pinned v1beta8 profile";
|
|
1291
|
+
return said("the post-interaction terms do not match the pinned v1beta8 profile", "policy_refused");
|
|
1211
1292
|
}
|
|
1212
1293
|
let dstToken;
|
|
1213
1294
|
try {
|
|
1214
1295
|
dstToken = tonDest ? BigInt(getAddress(String(message.takerAsset))) : BigInt(getAddress(outputToken));
|
|
1215
1296
|
} catch {
|
|
1216
|
-
return "the destination token is not an EVM address";
|
|
1297
|
+
return said("the destination token is not an EVM address", "policy_refused");
|
|
1217
1298
|
}
|
|
1218
1299
|
if (extra[1] !== (tonDest ? 0n : BigInt(destChainId)) || extra[2] !== dstToken || extra[4] !== EVM_TIMELOCKS) {
|
|
1219
|
-
return "the signed HTLC terms name another destination chain or token, or other timelocks";
|
|
1300
|
+
return said("the signed HTLC terms name another destination chain or token, or other timelocks", "policy_refused");
|
|
1220
1301
|
}
|
|
1221
1302
|
try {
|
|
1222
1303
|
await checkOrderCustomData(customData, { evmDest: !tonDest, outputToken, traderDst: params.destAddress, quoteId: intent.quoteId });
|
|
1223
1304
|
} catch (e) {
|
|
1224
|
-
return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
|
|
1305
|
+
return said(e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e), refusalOf(e) ?? "policy_refused");
|
|
1225
1306
|
}
|
|
1226
1307
|
return null;
|
|
1227
1308
|
}
|
|
@@ -1232,22 +1313,22 @@ function omnistonVerifyBuild(hooks) {
|
|
|
1232
1313
|
};
|
|
1233
1314
|
return async (quote, tx, params) => {
|
|
1234
1315
|
if (tx.typedData !== void 0) {
|
|
1235
|
-
if (!params) return "the request this quote answered is unknown";
|
|
1316
|
+
if (!params) return said("the request this quote answered is unknown", "policy_refused");
|
|
1236
1317
|
try {
|
|
1237
1318
|
return await checkRemoteEvmOrder(quote, tx, params, trusted);
|
|
1238
1319
|
} catch {
|
|
1239
|
-
return "the typed order could not be checked";
|
|
1320
|
+
return said("the typed order could not be checked", "policy_refused");
|
|
1240
1321
|
}
|
|
1241
1322
|
}
|
|
1242
1323
|
if (tx.bodyBoc === void 0) return null;
|
|
1243
|
-
if (!params) return "the request this quote answered is unknown";
|
|
1324
|
+
if (!params) return said("the request this quote answered is unknown", "policy_refused");
|
|
1244
1325
|
if (quote.providerData?.settlement !== "swap") {
|
|
1245
1326
|
if (params.sourceChain.startsWith("ton:") && params.destChain.startsWith("eip155:")) {
|
|
1246
|
-
if (!hooks.verifyTonEscrowBuild) return "a TON \u2192 EVM escrow body needs verifyTonEscrowBuild (createOmnistonTonValidators)";
|
|
1247
|
-
if (!params.refundAddress) return "the trader's TON address (refundAddress) is unknown";
|
|
1327
|
+
if (!hooks.verifyTonEscrowBuild) return said("a TON \u2192 EVM escrow body needs verifyTonEscrowBuild (createOmnistonTonValidators)", "unsupported_pair");
|
|
1328
|
+
if (!params.refundAddress) return said("the trader's TON address (refundAddress) is unknown", "recipient_unverifiable");
|
|
1248
1329
|
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);
|
|
1249
1330
|
try {
|
|
1250
|
-
|
|
1331
|
+
const text = await hooks.verifyTonEscrowBuild({
|
|
1251
1332
|
payloadBocHex: Buffer2.from(tx.bodyBoc, "base64").toString("hex"),
|
|
1252
1333
|
ownerAddress: params.refundAddress,
|
|
1253
1334
|
inputUnits: quote.sourceAmount,
|
|
@@ -1256,22 +1337,23 @@ function omnistonVerifyBuild(hooks) {
|
|
|
1256
1337
|
outputTokenAddress: params.destToken,
|
|
1257
1338
|
minResolverSendsUnits: floor.toString()
|
|
1258
1339
|
});
|
|
1340
|
+
return text ? said(text, tonEscrowBuildReason(text)) : text;
|
|
1259
1341
|
} catch {
|
|
1260
|
-
return "the escrow body could not be checked";
|
|
1342
|
+
return said("the escrow body could not be checked", "policy_refused");
|
|
1261
1343
|
}
|
|
1262
1344
|
}
|
|
1263
|
-
return "only TON swap and TON \u2192 EVM escrow bodies can be checked on the client";
|
|
1345
|
+
return said("only TON swap and TON \u2192 EVM escrow bodies can be checked on the client", "unsupported_pair");
|
|
1264
1346
|
}
|
|
1265
1347
|
let steps;
|
|
1266
1348
|
try {
|
|
1267
1349
|
steps = JSON.parse(quote.providerData.routeSteps ?? "");
|
|
1268
1350
|
} catch {
|
|
1269
|
-
return "the quote carries no route steps";
|
|
1351
|
+
return said("the quote carries no route steps", "policy_refused");
|
|
1270
1352
|
}
|
|
1271
|
-
if (!Array.isArray(steps) || steps.length === 0) return "the quote carries no route steps";
|
|
1353
|
+
if (!Array.isArray(steps) || steps.length === 0) return said("the quote carries no route steps", "policy_refused");
|
|
1272
1354
|
const source = ccAsset(params.sourceChain, params.sourceToken);
|
|
1273
1355
|
const dest = ccAssetByToken(params.destChain, params.destToken);
|
|
1274
|
-
if (!source || !dest) return "unknown source or destination token";
|
|
1356
|
+
if (!source || !dest) return said("unknown source or destination token", "unsupported_pair");
|
|
1275
1357
|
const protocols = (quote.providerData.protocols ?? "").split(",").filter(Boolean);
|
|
1276
1358
|
let checked;
|
|
1277
1359
|
try {
|
|
@@ -1289,17 +1371,17 @@ function omnistonVerifyBuild(hooks) {
|
|
|
1289
1371
|
integratorAddress: hooks.integratorAddress ?? null
|
|
1290
1372
|
});
|
|
1291
1373
|
} catch (e) {
|
|
1292
|
-
return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
|
|
1374
|
+
return said(e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e), e instanceof TonSwapPayloadError ? tonSwapPayloadReason(e.reason) : "policy_refused");
|
|
1293
1375
|
}
|
|
1294
1376
|
const payload = parseSwapPayload(Buffer2.from(tx.bodyBoc, "base64").toString("hex"));
|
|
1295
1377
|
const target = source.contract ? payload.jettonTransfer?.destination : tx.to;
|
|
1296
|
-
if (!target) return "the body names no swap destination";
|
|
1378
|
+
if (!target) return said("the body names no swap destination", "policy_refused");
|
|
1297
1379
|
try {
|
|
1298
|
-
if (!await hooks.validateTonSwapTarget({ address: target, protocols })) return `${target} is not a recognized DEX contract for ${protocols.join("+")}
|
|
1380
|
+
if (!await hooks.validateTonSwapTarget({ address: target, protocols })) return said(`${target} is not a recognized DEX contract for ${protocols.join("+")}`, "vault_mismatch");
|
|
1299
1381
|
} catch {
|
|
1300
|
-
return "the swap target could not be checked";
|
|
1382
|
+
return said("the swap target could not be checked", "policy_refused");
|
|
1301
1383
|
}
|
|
1302
|
-
if (!hooks.validateTonSwapLeg) return "a TON swap body needs validateTonSwapLeg (createOmnistonTonValidators)";
|
|
1384
|
+
if (!hooks.validateTonSwapLeg) return said("a TON swap body needs validateTonSwapLeg (createOmnistonTonValidators)", "unsupported_pair");
|
|
1303
1385
|
for (const leg of checked.legs) {
|
|
1304
1386
|
let ok = false;
|
|
1305
1387
|
try {
|
|
@@ -1307,7 +1389,7 @@ function omnistonVerifyBuild(hooks) {
|
|
|
1307
1389
|
} catch {
|
|
1308
1390
|
ok = false;
|
|
1309
1391
|
}
|
|
1310
|
-
if (!ok) return `${leg.kind} v${leg.version} leg not verified
|
|
1392
|
+
if (!ok) return said(`${leg.kind} v${leg.version} leg not verified`, "vault_mismatch");
|
|
1311
1393
|
}
|
|
1312
1394
|
return null;
|
|
1313
1395
|
};
|
|
@@ -1491,19 +1573,19 @@ function omnistonProvider(opts = {}) {
|
|
|
1491
1573
|
if (!bound.ok) {
|
|
1492
1574
|
if (bound.reason === "legacy-expired") {
|
|
1493
1575
|
trace("legacy-intent-expired", { where });
|
|
1494
|
-
throw
|
|
1576
|
+
throw refusal("quote_expired", "omniston: quote expired \u2014 request a fresh quote");
|
|
1495
1577
|
}
|
|
1496
1578
|
trace("intent-mismatch", { where, field: bound.field });
|
|
1497
|
-
throw
|
|
1579
|
+
throw refusal(mismatchReason(bound.field), `omniston: intent id does not match the quote it was issued for (${bound.field})`);
|
|
1498
1580
|
}
|
|
1499
1581
|
if (bound.legacy) trace("legacy-intent", { where });
|
|
1500
1582
|
if (!decoded.digest) {
|
|
1501
1583
|
trace("intent-mismatch", { where, field: "digest-missing" });
|
|
1502
|
-
throw
|
|
1584
|
+
throw refusal("quote_expired", "omniston: this quote was issued before an update \u2014 request a fresh quote");
|
|
1503
1585
|
}
|
|
1504
1586
|
if (decoded.digest !== quoteDigest(entry.quote)) {
|
|
1505
1587
|
trace("intent-mismatch", { where, field: "digest" });
|
|
1506
|
-
throw
|
|
1588
|
+
throw refusal("policy_refused", "omniston: quote was replaced \u2014 request a fresh quote");
|
|
1507
1589
|
}
|
|
1508
1590
|
return bound.ref;
|
|
1509
1591
|
}
|
|
@@ -1537,10 +1619,10 @@ function omnistonProvider(opts = {}) {
|
|
|
1537
1619
|
const disclosed = (await store.load(disclosedKey(quoteId)) ?? []).length > 0;
|
|
1538
1620
|
if (boundTo !== binding || disclosed) {
|
|
1539
1621
|
traceFor(quoteId)("secret-reuse-refused", { disclosed, bound: boundTo !== void 0 });
|
|
1540
|
-
throw
|
|
1622
|
+
throw refusal("policy_refused", "omniston: this quote id already belongs to another order \u2014 request a fresh quote");
|
|
1541
1623
|
}
|
|
1542
1624
|
if (existing.length !== count || !existing.every((s) => /^[0-9a-fA-F]{64}$/.test(s))) {
|
|
1543
|
-
throw
|
|
1625
|
+
throw refusal("policy_refused", "omniston: stored HTLC secrets for this quote do not fit the order \u2014 request a fresh quote");
|
|
1544
1626
|
}
|
|
1545
1627
|
traceFor(quoteId)("secret-reused", { count });
|
|
1546
1628
|
return { secrets: existing.map(hexToBytes), created: false };
|
|
@@ -1599,7 +1681,10 @@ function omnistonProvider(opts = {}) {
|
|
|
1599
1681
|
let q = await rfq(slippagePips);
|
|
1600
1682
|
if (!q) {
|
|
1601
1683
|
if (!signal?.aborted) {
|
|
1602
|
-
|
|
1684
|
+
const text = `omniston: ${outcome.error ? `RFQ failed: ${outcome.error}` : outcome.rejected ? `swap quote rejected: ${outcome.rejected}` : "no TON DEX route quoted this pair/amount"}`;
|
|
1685
|
+
const reason = rfqReason(outcome);
|
|
1686
|
+
if (reason) params.onRefusal?.(text, { reason });
|
|
1687
|
+
else params.onRefusal?.(text);
|
|
1603
1688
|
}
|
|
1604
1689
|
return null;
|
|
1605
1690
|
}
|
|
@@ -1665,27 +1750,32 @@ function omnistonProvider(opts = {}) {
|
|
|
1665
1750
|
async function buildSwapDeposit(ref, q, swap) {
|
|
1666
1751
|
if (!ref.trader) throw new Error("omniston: quote was requested without a refund/source address");
|
|
1667
1752
|
if (!isTon(ref.srcChain) || q.inputAsset.chain.$case !== "ton" || q.outputAsset.chain.$case !== "ton") {
|
|
1668
|
-
throw
|
|
1753
|
+
throw refusal("policy_refused", "omniston: swap quote is not a TON same-chain swap");
|
|
1669
1754
|
}
|
|
1670
|
-
if (!sameChainTonReady()) throw
|
|
1755
|
+
if (!sameChainTonReady()) throw refusal("unsupported_pair", "omniston: TON same-chain swaps need independent jetton-wallet, swap-target and swap-leg validators");
|
|
1671
1756
|
const owner = { chain: { $case: "ton", value: ref.trader } };
|
|
1672
1757
|
const receiver = { chain: { $case: "ton", value: ref.dest } };
|
|
1673
1758
|
const omni = await getClient();
|
|
1674
|
-
if (typeof omni.tonBuildSwap !== "function") throw
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1759
|
+
if (typeof omni.tonBuildSwap !== "function") throw refusal("unsupported_pair", "omniston: client does not support swap settlement (tonBuildSwap)");
|
|
1760
|
+
let tx;
|
|
1761
|
+
try {
|
|
1762
|
+
tx = await omni.tonBuildSwap({
|
|
1763
|
+
quoteId: ref.quoteId,
|
|
1764
|
+
transferSrcAddress: owner,
|
|
1765
|
+
refundSrcAddress: owner,
|
|
1766
|
+
gasExcessAddress: owner,
|
|
1767
|
+
traderDstAddress: receiver,
|
|
1768
|
+
// Build at the slippage the quote was requested with: its
|
|
1769
|
+
// minOutputAmount is the minimum this module reported to the router.
|
|
1770
|
+
useRecommendedSlippage: false
|
|
1771
|
+
});
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
throw venueFailure(error);
|
|
1774
|
+
}
|
|
1685
1775
|
const [message, ...rest] = tx.messages;
|
|
1686
|
-
if (!message || rest.length > 0) throw
|
|
1687
|
-
if (message.jettonWalletStateInit) throw
|
|
1688
|
-
if (!isPositiveInteger(message.sendAmount)) throw
|
|
1776
|
+
if (!message || rest.length > 0) throw refusal("policy_refused", `omniston: expected exactly one swap message, got ${tx.messages.length}`);
|
|
1777
|
+
if (message.jettonWalletStateInit) throw refusal("policy_refused", "omniston: deploying a resolver-provided jetton wallet is not supported");
|
|
1778
|
+
if (!isPositiveInteger(message.sendAmount)) throw refusal("policy_refused", "omniston: malformed swap message value");
|
|
1689
1779
|
const sendAmount = BigInt(message.sendAmount);
|
|
1690
1780
|
const gasCap = BigInt(maxTonExecutionNano);
|
|
1691
1781
|
const gasBudget = q.gasBudget && isPositiveInteger(q.gasBudget) ? BigInt(q.gasBudget) : gasCap;
|
|
@@ -1697,53 +1787,53 @@ function omnistonProvider(opts = {}) {
|
|
|
1697
1787
|
const ptonV1Native = !inputJetton && !!payload.jettonTransfer && singleV1;
|
|
1698
1788
|
if (inputJetton) {
|
|
1699
1789
|
const transfer = payload.jettonTransfer;
|
|
1700
|
-
if (!transfer) throw
|
|
1701
|
-
if (sendAmount > gasCap || sendAmount > gasBudget) throw
|
|
1702
|
-
if (transfer.amount !== BigInt(q.inputUnits)) throw
|
|
1790
|
+
if (!transfer) throw refusal("policy_refused", "omniston: jetton-input swap message is not a jetton transfer");
|
|
1791
|
+
if (sendAmount > gasCap || sendAmount > gasBudget) throw refusal("policy_refused", `omniston: swap message attaches more TON than the quoted gas budget (${sendAmount} > ${gasBudget} nanoTON, cap ${gasCap}, ${protocols.join("+")})`);
|
|
1792
|
+
if (transfer.amount !== BigInt(q.inputUnits)) throw refusal("policy_refused", "omniston: swap message moves a different amount than quoted");
|
|
1703
1793
|
if (!transfer.responseDestination || !sameTonAddress(transfer.responseDestination, ref.trader)) {
|
|
1704
|
-
throw
|
|
1794
|
+
throw refusal("recipient_mismatch", "omniston: swap message returns excess to a wallet other than the trader");
|
|
1705
1795
|
}
|
|
1706
|
-
if (transfer.forwardTon >= sendAmount) throw
|
|
1796
|
+
if (transfer.forwardTon >= sendAmount) throw refusal("policy_refused", "omniston: swap message forwards more TON than it carries");
|
|
1707
1797
|
const walletValid = await opts.validateTonJettonWallet({
|
|
1708
1798
|
ownerAddress: ref.trader,
|
|
1709
1799
|
jettonMasterAddress: inputJetton,
|
|
1710
1800
|
walletAddress: message.targetAddress
|
|
1711
1801
|
});
|
|
1712
|
-
if (!walletValid) throw
|
|
1802
|
+
if (!walletValid) throw refusal("vault_mismatch", "omniston: TON jetton wallet was not independently validated");
|
|
1713
1803
|
const targetValid = await opts.validateTonSwapTarget({ address: transfer.destination, protocols });
|
|
1714
|
-
if (!targetValid) throw
|
|
1804
|
+
if (!targetValid) throw refusal("vault_mismatch", "omniston: swap destination is not a recognized DEX contract for the quoted protocol");
|
|
1715
1805
|
} else if (ptonV1Native) {
|
|
1716
1806
|
const transfer = payload.jettonTransfer;
|
|
1717
1807
|
const input = BigInt(q.inputUnits);
|
|
1718
|
-
if (transfer.amount !== input) throw
|
|
1719
|
-
if (sendAmount < input) throw
|
|
1808
|
+
if (transfer.amount !== input) throw refusal("policy_refused", "omniston: swap message moves a different amount than quoted");
|
|
1809
|
+
if (sendAmount < input) throw refusal("policy_refused", "omniston: swap message carries less than the quoted input");
|
|
1720
1810
|
const gas = sendAmount - input;
|
|
1721
|
-
if (gas > gasCap || gas > gasBudget) throw
|
|
1722
|
-
if (transfer.forwardTon > gas) throw
|
|
1811
|
+
if (gas > gasCap || gas > gasBudget) throw refusal("policy_refused", `omniston: swap message attaches more TON than the quoted gas budget (${gas} > ${gasBudget} nanoTON, cap ${gasCap}, ${protocols.join("+")})`);
|
|
1812
|
+
if (transfer.forwardTon > gas) throw refusal("policy_refused", "omniston: swap message forwards more TON than its gas");
|
|
1723
1813
|
if (!transfer.responseDestination || !sameTonAddress(transfer.responseDestination, ref.trader)) {
|
|
1724
|
-
throw
|
|
1814
|
+
throw refusal("recipient_mismatch", "omniston: swap message returns excess to a wallet other than the trader");
|
|
1725
1815
|
}
|
|
1726
|
-
if (typeof opts.validateTonSwapLeg !== "function") throw
|
|
1816
|
+
if (typeof opts.validateTonSwapLeg !== "function") throw refusal("unsupported_pair", "omniston: STON.fi v1 native input needs the validateTonSwapLeg validator (pTON v1 wallet ownership)");
|
|
1727
1817
|
const targetValid = await opts.validateTonSwapTarget({ address: message.targetAddress, protocols });
|
|
1728
|
-
if (!targetValid) throw
|
|
1818
|
+
if (!targetValid) throw refusal("vault_mismatch", "omniston: swap target is not a recognized DEX contract for the quoted protocol");
|
|
1729
1819
|
const routerValid = await opts.validateTonSwapTarget({ address: transfer.destination, protocols });
|
|
1730
|
-
if (!routerValid) throw
|
|
1820
|
+
if (!routerValid) throw refusal("vault_mismatch", "omniston: swap destination is not a recognized DEX contract for the quoted protocol");
|
|
1731
1821
|
} else {
|
|
1732
1822
|
if (payload.jettonTransfer) {
|
|
1733
|
-
throw
|
|
1823
|
+
throw refusal("policy_refused", `omniston: native-input swap message must not be a jetton transfer (route ${protocols.join("+") || "?"}, target ${message.targetAddress}, transfer ${payload.jettonTransfer.amount} to ${payload.jettonTransfer.destination}, forwardTon ${payload.jettonTransfer.forwardTon}, value ${sendAmount})`);
|
|
1734
1824
|
}
|
|
1735
1825
|
const input = BigInt(q.inputUnits);
|
|
1736
|
-
if (sendAmount < input) throw
|
|
1826
|
+
if (sendAmount < input) throw refusal("policy_refused", "omniston: swap message carries less than the quoted input");
|
|
1737
1827
|
const gas = sendAmount - input;
|
|
1738
|
-
if (gas > gasCap || gas > gasBudget) throw
|
|
1828
|
+
if (gas > gasCap || gas > gasBudget) throw refusal("policy_refused", `omniston: swap message attaches more TON than the quoted gas budget (${gas} > ${gasBudget} nanoTON, cap ${gasCap}, ${protocols.join("+")})`);
|
|
1739
1829
|
const targetValid = await opts.validateTonSwapTarget({ address: message.targetAddress, protocols });
|
|
1740
|
-
if (!targetValid) throw
|
|
1830
|
+
if (!targetValid) throw refusal("vault_mismatch", "omniston: swap target is not a recognized DEX contract for the quoted protocol");
|
|
1741
1831
|
}
|
|
1742
1832
|
const mode = ptonV1Native ? "enforce" : opts.tonSwapPayloadCheck ?? "enforce";
|
|
1743
1833
|
const trace = traceFor(ref.quoteId);
|
|
1744
1834
|
const payloadRefusal = (reason, detail) => {
|
|
1745
1835
|
trace("ton-swap-payload", { reason, detail, mode, protocols: protocols.join("+") });
|
|
1746
|
-
if (mode === "enforce") throw
|
|
1836
|
+
if (mode === "enforce") throw refusal(tonSwapPayloadReason(reason), `omniston: ton-swap-payload ${reason} \u2014 ${detail}`);
|
|
1747
1837
|
};
|
|
1748
1838
|
let checked = null;
|
|
1749
1839
|
try {
|
|
@@ -1817,33 +1907,33 @@ function omnistonProvider(opts = {}) {
|
|
|
1817
1907
|
const srcChain = ref.srcChain;
|
|
1818
1908
|
const chainId = evmRef(srcChain);
|
|
1819
1909
|
const srcCase = q.inputAsset.chain.$case;
|
|
1820
|
-
if (chainId === null || EVM_CASE[chainId] !== srcCase) throw
|
|
1910
|
+
if (chainId === null || EVM_CASE[chainId] !== srcCase) throw refusal("policy_refused", "omniston: source chain does not match the EVM quote");
|
|
1821
1911
|
const destCase = q.outputAsset.chain.$case;
|
|
1822
1912
|
const destChain = caipForChainCase(destCase);
|
|
1823
1913
|
const tonDest = destCase === "ton";
|
|
1824
|
-
if (!destChain || destChain === srcChain) throw
|
|
1914
|
+
if (!destChain || destChain === srcChain) throw refusal("unsupported_pair", "omniston: EVM-source order has no supported destination chain");
|
|
1825
1915
|
if (!trustedOrderProtocols.has(srcChain) || typeof opts.validateEvmInputPosition !== "function" || typeof (tonDest ? opts.validateTonOutputPosition : opts.validateEvmOutputPosition) !== "function") {
|
|
1826
|
-
throw
|
|
1916
|
+
throw refusal("unsupported_pair", `omniston: EVM\u2192${tonDest ? "TON" : "EVM"} needs a trusted order protocol and both position validators`);
|
|
1827
1917
|
}
|
|
1828
1918
|
let owner;
|
|
1829
1919
|
try {
|
|
1830
1920
|
owner = getAddress(ref.trader);
|
|
1831
1921
|
} catch {
|
|
1832
|
-
throw
|
|
1922
|
+
throw refusal("recipient_mismatch", "omniston: trader is not an EVM address");
|
|
1833
1923
|
}
|
|
1834
1924
|
let dest;
|
|
1835
1925
|
if (tonDest) {
|
|
1836
1926
|
try {
|
|
1837
1927
|
TonAddress.parse(ref.dest);
|
|
1838
1928
|
} catch {
|
|
1839
|
-
throw
|
|
1929
|
+
throw refusal("recipient_mismatch", "omniston: destination is not a TON address");
|
|
1840
1930
|
}
|
|
1841
1931
|
dest = ref.dest;
|
|
1842
1932
|
} else {
|
|
1843
1933
|
try {
|
|
1844
1934
|
dest = getAddress(ref.dest);
|
|
1845
1935
|
} catch {
|
|
1846
|
-
throw
|
|
1936
|
+
throw refusal("recipient_mismatch", "omniston: destination is not an EVM address");
|
|
1847
1937
|
}
|
|
1848
1938
|
}
|
|
1849
1939
|
const traderDst = { chain: { $case: destCase, value: dest } };
|
|
@@ -1854,9 +1944,9 @@ function omnistonProvider(opts = {}) {
|
|
|
1854
1944
|
const outputToken = assetContract(q.outputAsset);
|
|
1855
1945
|
const destChainId = tonDest ? null : evmRef(destChain);
|
|
1856
1946
|
if (factory?.chain.$case === destCase && !isTrusted(destChain, factory.chain.value)) {
|
|
1857
|
-
throw
|
|
1947
|
+
throw refusal("vault_mismatch", `omniston: untrusted ${destChain} settlement contract ${factory.chain.value}`);
|
|
1858
1948
|
}
|
|
1859
|
-
if (protocol.chain.$case !== srcCase || !isTrustedOrderProtocol(srcChain, protocol.chain.value) || resolver?.chain.$case !== srcCase || !validEvmAddress(resolver.chain.value) || factory?.chain.$case !== destCase || !isTrusted(destChain, factory.chain.value) || !inputToken || !outputToken || !Number.isSafeInteger(settlement.protocolFeePips) || !tonDest && (destChainId === null || !validEvmAddress(outputToken) || !isPositiveInteger(settlement.dstSecurityDepositUnits ?? ""))) throw
|
|
1949
|
+
if (protocol.chain.$case !== srcCase || !isTrustedOrderProtocol(srcChain, protocol.chain.value) || resolver?.chain.$case !== srcCase || !validEvmAddress(resolver.chain.value) || factory?.chain.$case !== destCase || !isTrusted(destChain, factory.chain.value) || !inputToken || !outputToken || !Number.isSafeInteger(settlement.protocolFeePips) || !tonDest && (destChainId === null || !validEvmAddress(outputToken) || !isPositiveInteger(settlement.dstSecurityDepositUnits ?? ""))) throw refusal("policy_refused", "omniston: incomplete EVM order settlement binding in quote");
|
|
1860
1950
|
const { secrets, created } = await secretsFor(ref, q, 1);
|
|
1861
1951
|
const hashes = secrets.map((secret) => hashlock(secret, settlement.htlcHashingFunction));
|
|
1862
1952
|
const forget = async () => {
|
|
@@ -1873,14 +1963,14 @@ function omnistonProvider(opts = {}) {
|
|
|
1873
1963
|
});
|
|
1874
1964
|
} catch (error) {
|
|
1875
1965
|
await forget();
|
|
1876
|
-
throw error;
|
|
1966
|
+
throw venueFailure(error);
|
|
1877
1967
|
}
|
|
1878
1968
|
let td;
|
|
1879
1969
|
try {
|
|
1880
1970
|
try {
|
|
1881
1971
|
td = JSON.parse(payload.typedData);
|
|
1882
1972
|
} catch {
|
|
1883
|
-
throw
|
|
1973
|
+
throw refusal("policy_refused", "omniston: malformed EVM order typed data");
|
|
1884
1974
|
}
|
|
1885
1975
|
validateSerializedOrderDetails(payload.serializedOrderDetails, {
|
|
1886
1976
|
quoteId: ref.quoteId,
|
|
@@ -1963,45 +2053,46 @@ function omnistonProvider(opts = {}) {
|
|
|
1963
2053
|
async getQuote(params, signal) {
|
|
1964
2054
|
if (params.flex) return null;
|
|
1965
2055
|
if (isTon(params.sourceChain) && isTon(params.destChain)) return await getSwapQuote(params, signal);
|
|
1966
|
-
const refuse2 = (reason) => {
|
|
1967
|
-
params.onRefusal?.(`omniston: ${reason}
|
|
2056
|
+
const refuse2 = (reason, why) => {
|
|
2057
|
+
if (why) params.onRefusal?.(`omniston: ${reason}`, { reason: why });
|
|
2058
|
+
else params.onRefusal?.(`omniston: ${reason}`);
|
|
1968
2059
|
return null;
|
|
1969
2060
|
};
|
|
1970
2061
|
const evmSource = !isTon(params.sourceChain);
|
|
1971
2062
|
const tonDest = isTon(params.destChain);
|
|
1972
|
-
if (!omniston.supports(params.sourceChain, params.destChain)) return refuse2("route is not an Omniston order corridor");
|
|
2063
|
+
if (!omniston.supports(params.sourceChain, params.destChain)) return refuse2("route is not an Omniston order corridor", "unsupported_pair");
|
|
1973
2064
|
if (!trustedContracts.has(params.sourceChain) || !trustedContracts.has(params.destChain)) {
|
|
1974
|
-
return refuse2(`no trusted settlement contract configured for ${trustedContracts.has(params.sourceChain) ? params.destChain : params.sourceChain}
|
|
2065
|
+
return refuse2(`no trusted settlement contract configured for ${trustedContracts.has(params.sourceChain) ? params.destChain : params.sourceChain}`, "unsupported_pair");
|
|
1975
2066
|
}
|
|
1976
2067
|
if (evmSource) {
|
|
1977
|
-
if (!trustedOrderProtocols.has(params.sourceChain)) return refuse2(`no trusted EVM order protocol configured for ${params.sourceChain}
|
|
2068
|
+
if (!trustedOrderProtocols.has(params.sourceChain)) return refuse2(`no trusted EVM order protocol configured for ${params.sourceChain}`, "unsupported_pair");
|
|
1978
2069
|
if (typeof opts.validateEvmInputPosition !== "function" || typeof (tonDest ? opts.validateTonOutputPosition : opts.validateEvmOutputPosition) !== "function") {
|
|
1979
|
-
return refuse2(tonDest ? "EVM\u2192TON needs the EVM input-position and TON output-position validators" : "EVM\u2192EVM needs the EVM input-position and EVM output-position validators");
|
|
2070
|
+
return refuse2(tonDest ? "EVM\u2192TON needs the EVM input-position and TON output-position validators" : "EVM\u2192EVM needs the EVM input-position and EVM output-position validators", "unsupported_pair");
|
|
1980
2071
|
}
|
|
1981
2072
|
if (params.dry === false) {
|
|
1982
2073
|
if (tonDest) {
|
|
1983
2074
|
try {
|
|
1984
2075
|
TonAddress.parse(params.destAddress);
|
|
1985
2076
|
} catch {
|
|
1986
|
-
return refuse2("destination is not a TON address");
|
|
2077
|
+
return refuse2("destination is not a TON address", "recipient_mismatch");
|
|
1987
2078
|
}
|
|
1988
|
-
} else if (!validEvmAddress(params.destAddress)) return refuse2("destination is not an EVM address");
|
|
2079
|
+
} else if (!validEvmAddress(params.destAddress)) return refuse2("destination is not an EVM address", "recipient_mismatch");
|
|
1989
2080
|
}
|
|
1990
|
-
} else if (typeof opts.validateTonJettonWallet !== "function" || typeof opts.validateTonEscrowPayload !== "function" || typeof opts.validateEvmOutputPosition !== "function") return refuse2("TON\u2192EVM needs the jetton-wallet, escrow-payload and EVM output-position validators");
|
|
2081
|
+
} else if (typeof opts.validateTonJettonWallet !== "function" || typeof opts.validateTonEscrowPayload !== "function" || typeof opts.validateEvmOutputPosition !== "function") return refuse2("TON\u2192EVM needs the jetton-wallet, escrow-payload and EVM output-position validators", "unsupported_pair");
|
|
1991
2082
|
const source = ccAsset(params.sourceChain, params.sourceToken);
|
|
1992
2083
|
const dest = ccAssetByToken(params.destChain, params.destToken);
|
|
1993
|
-
if (!source || !dest) return refuse2("asset is not in the catalog");
|
|
2084
|
+
if (!source || !dest) return refuse2("asset is not in the catalog", "unsupported_pair");
|
|
1994
2085
|
const inputAsset = toAssetId(params.sourceChain, source);
|
|
1995
2086
|
const outputAsset = toAssetId(params.destChain, dest);
|
|
1996
2087
|
if (!inputAsset || !outputAsset) {
|
|
1997
|
-
return refuse2(`cross-chain orders are token corridors only (no ${!inputAsset ? source.symbol : dest.symbol} native leg)
|
|
2088
|
+
return refuse2(`cross-chain orders are token corridors only (no ${!inputAsset ? source.symbol : dest.symbol} native leg)`, "unsupported_pair");
|
|
1998
2089
|
}
|
|
1999
2090
|
const maxUnits = decimalToUnits(maxUsdText, source.decimals);
|
|
2000
2091
|
if (!maxUnits || !isPositiveInteger(params.amountIn)) return refuse2("invalid amount");
|
|
2001
|
-
if (BigInt(params.amountIn) > maxUnits) return refuse2(`amount above the ${maxUsdText} USD per-swap cap
|
|
2092
|
+
if (BigInt(params.amountIn) > maxUnits) return refuse2(`amount above the ${maxUsdText} USD per-swap cap`, "amount_too_large");
|
|
2002
2093
|
const trader = params.refundAddress ?? "";
|
|
2003
2094
|
if (!trader && !params.dry) return refuse2(`execution quote needs the trader's ${evmSource ? "EVM" : "TON"} address (refundAddress)`);
|
|
2004
|
-
if (evmSource && trader && !validEvmAddress(trader)) return refuse2("refundAddress is not an EVM address");
|
|
2095
|
+
if (evmSource && trader && !validEvmAddress(trader)) return refuse2("refundAddress is not an EVM address", "recipient_mismatch");
|
|
2005
2096
|
const request = {
|
|
2006
2097
|
inputAsset,
|
|
2007
2098
|
outputAsset,
|
|
@@ -2029,27 +2120,28 @@ function omnistonProvider(opts = {}) {
|
|
|
2029
2120
|
if (!q) {
|
|
2030
2121
|
if (signal?.aborted) return null;
|
|
2031
2122
|
return refuse2(
|
|
2032
|
-
rfq.error ? `RFQ failed: ${rfq.error}` : rfq.rejected ? `resolver quote rejected: ${rfq.rejected}` : "no resolver quoted this route/amount"
|
|
2123
|
+
rfq.error ? `RFQ failed: ${rfq.error}` : rfq.rejected ? `resolver quote rejected: ${rfq.rejected}` : "no resolver quoted this route/amount",
|
|
2124
|
+
rfqReason(rfq)
|
|
2033
2125
|
);
|
|
2034
2126
|
}
|
|
2035
2127
|
const orderData = q.settlementData.$case === "order" ? q.settlementData.value : null;
|
|
2036
|
-
if (!orderData) return refuse2("resolver answered without order settlement");
|
|
2128
|
+
if (!orderData) return refuse2("resolver answered without order settlement", "upstream_unavailable");
|
|
2037
2129
|
const srcContract = orderData.srcProtocolContractAddress.chain.value;
|
|
2038
2130
|
if (evmSource ? !isTrustedOrderProtocol(params.sourceChain, srcContract) : !isTrusted(params.sourceChain, srcContract)) {
|
|
2039
|
-
return refuse2(evmSource ? `untrusted ${params.sourceChain} order protocol ${srcContract}` : `untrusted TON settlement contract ${srcContract}
|
|
2131
|
+
return refuse2(evmSource ? `untrusted ${params.sourceChain} order protocol ${srcContract}` : `untrusted TON settlement contract ${srcContract}`, "vault_mismatch");
|
|
2040
2132
|
}
|
|
2041
2133
|
if (!orderData.dstProtocolContractAddress || !isTrusted(params.destChain, orderData.dstProtocolContractAddress.chain.value)) {
|
|
2042
|
-
return refuse2(`untrusted ${params.destChain} settlement contract ${orderData.dstProtocolContractAddress?.chain.value ?? "(none)"}
|
|
2134
|
+
return refuse2(`untrusted ${params.destChain} settlement contract ${orderData.dstProtocolContractAddress?.chain.value ?? "(none)"}`, "vault_mismatch");
|
|
2043
2135
|
}
|
|
2044
2136
|
const deadlineMs = orderData.tradeStartDeadline * 1e3;
|
|
2045
|
-
if (!Number.isFinite(deadlineMs) || deadlineMs <= Date.now()) return refuse2("quote trade-start deadline already passed");
|
|
2046
|
-
if (params.dry === false && deadlineMs - Date.now() < MIN_START_HEADROOM_MS) return refuse2("quote trade-start deadline too close to execute");
|
|
2137
|
+
if (!Number.isFinite(deadlineMs) || deadlineMs <= Date.now()) return refuse2("quote trade-start deadline already passed", "quote_expired");
|
|
2138
|
+
if (params.dry === false && deadlineMs - Date.now() < MIN_START_HEADROOM_MS) return refuse2("quote trade-start deadline too close to execute", "quote_expired");
|
|
2047
2139
|
const inputScaled = BigInt(q.inputUnits) * 10n ** BigInt(dest.decimals);
|
|
2048
2140
|
const outputScaled = BigInt(q.outputUnits) * 10n ** BigInt(source.decimals);
|
|
2049
|
-
if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
|
|
2050
|
-
if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
|
|
2141
|
+
if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input", "price_check_failed");
|
|
2142
|
+
if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order", "policy_refused");
|
|
2051
2143
|
if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
|
|
2052
|
-
return refuse2("the platform reused a quote id that already holds another quote");
|
|
2144
|
+
return refuse2("the platform reused a quote id that already holds another quote", "policy_refused");
|
|
2053
2145
|
}
|
|
2054
2146
|
const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
|
|
2055
2147
|
const feeUsd = feeUnits / 10 ** dest.decimals;
|
|
@@ -2080,29 +2172,29 @@ function omnistonProvider(opts = {}) {
|
|
|
2080
2172
|
*/
|
|
2081
2173
|
async buildDeposit(quote) {
|
|
2082
2174
|
if (!Number.isFinite(quote.validUntilMs) || quote.validUntilMs <= Date.now()) {
|
|
2083
|
-
throw
|
|
2175
|
+
throw refusal("quote_expired", "omniston: quote expired \u2014 request a fresh quote");
|
|
2084
2176
|
}
|
|
2085
2177
|
const decoded = decodeIntent(quote.intentId);
|
|
2086
2178
|
if (!decoded) throw new Error("omniston: malformed intent id");
|
|
2087
2179
|
const cached = await loadBuilt(decoded.quoteId);
|
|
2088
|
-
if (!cached) throw
|
|
2180
|
+
if (!cached) throw refusal("quote_expired", "omniston: quote expired \u2014 request a fresh quote");
|
|
2089
2181
|
const ref = requireBinding(decoded, cached, "buildDeposit");
|
|
2090
2182
|
const q = cached.quote;
|
|
2091
2183
|
if (q.settlementData.$case === "swap") return await buildSwapDeposit(ref, q, q.settlementData.value);
|
|
2092
2184
|
if (q.settlementData.$case !== "order" || q.settlementData.value.tradeStartDeadline * 1e3 <= Date.now()) {
|
|
2093
|
-
throw
|
|
2185
|
+
throw refusal("quote_expired", "omniston: quote trade deadline expired \u2014 request a fresh quote");
|
|
2094
2186
|
}
|
|
2095
2187
|
if (!ref.trader) throw new Error("omniston: quote was requested without a refund/source address");
|
|
2096
2188
|
const srcCase = q.inputAsset.chain.$case;
|
|
2097
2189
|
if (srcCase !== "ton") return await buildEvmOrder(ref, q, q.settlementData.value, cached.bound);
|
|
2098
2190
|
const srcChain = ref.srcChain;
|
|
2099
|
-
if (!isTon(srcChain)) throw
|
|
2191
|
+
if (!isTon(srcChain)) throw refusal("policy_refused", "omniston: source chain does not match TON quote");
|
|
2100
2192
|
const destCase = q.outputAsset.chain.$case;
|
|
2101
2193
|
const traderDst = destCase === "ton" ? null : { chain: { $case: destCase, value: getAddress(ref.dest) } };
|
|
2102
|
-
if (!traderDst) throw
|
|
2194
|
+
if (!traderDst) throw refusal("unsupported_pair", "omniston: TON-destination orders are not supported");
|
|
2103
2195
|
const minter = q.settlementData.value.srcProtocolContractAddress;
|
|
2104
2196
|
if (minter.chain.$case !== "ton" || !isTrusted(srcChain, minter.chain.value)) {
|
|
2105
|
-
throw
|
|
2197
|
+
throw refusal("vault_mismatch", `omniston: untrusted TON settlement contract ${minter.chain.value}`);
|
|
2106
2198
|
}
|
|
2107
2199
|
const settlement = q.settlementData;
|
|
2108
2200
|
const hashingFunction = settlement.$case === "order" ? settlement.value.htlcHashingFunction : void 0;
|
|
@@ -2126,27 +2218,27 @@ function omnistonProvider(opts = {}) {
|
|
|
2126
2218
|
});
|
|
2127
2219
|
} catch (error) {
|
|
2128
2220
|
await forget();
|
|
2129
|
-
throw error;
|
|
2221
|
+
throw venueFailure(error);
|
|
2130
2222
|
}
|
|
2131
2223
|
const [message, ...rest] = tx.messages;
|
|
2132
2224
|
if (!message || rest.length > 0) {
|
|
2133
2225
|
await forget();
|
|
2134
|
-
throw
|
|
2226
|
+
throw refusal("policy_refused", `omniston: expected exactly one escrow message, got ${tx.messages.length}`);
|
|
2135
2227
|
}
|
|
2136
2228
|
if (!isPositiveInteger(message.sendAmount) || BigInt(message.sendAmount) > BigInt(maxTonExecutionNano)) {
|
|
2137
2229
|
await forget();
|
|
2138
|
-
throw
|
|
2230
|
+
throw refusal("policy_refused", "omniston: resolver TON execution value exceeds the configured safety cap");
|
|
2139
2231
|
}
|
|
2140
2232
|
const expectedProtocol = q.settlementData.value.srcProtocolContractAddress;
|
|
2141
2233
|
const initialResolver = q.settlementData.value.srcInitialResolverAddress;
|
|
2142
2234
|
const jettonMaster = assetContract(q.inputAsset);
|
|
2143
2235
|
if (expectedProtocol.chain.$case !== "ton" || initialResolver?.chain.$case !== "ton" || !jettonMaster) {
|
|
2144
2236
|
await forget();
|
|
2145
|
-
throw
|
|
2237
|
+
throw refusal("policy_refused", "omniston: incomplete TON settlement binding in quote");
|
|
2146
2238
|
}
|
|
2147
2239
|
if (message.jettonWalletStateInit) {
|
|
2148
2240
|
await forget();
|
|
2149
|
-
throw
|
|
2241
|
+
throw refusal("policy_refused", "omniston: deploying a resolver-provided jetton wallet is not supported");
|
|
2150
2242
|
}
|
|
2151
2243
|
let walletValid = false;
|
|
2152
2244
|
try {
|
|
@@ -2161,11 +2253,11 @@ function omnistonProvider(opts = {}) {
|
|
|
2161
2253
|
}
|
|
2162
2254
|
if (!walletValid) {
|
|
2163
2255
|
await forget();
|
|
2164
|
-
throw
|
|
2256
|
+
throw refusal("vault_mismatch", "omniston: TON jetton wallet was not independently validated");
|
|
2165
2257
|
}
|
|
2166
2258
|
try {
|
|
2167
2259
|
if (!opts.validateTonEscrowPayload) {
|
|
2168
|
-
throw
|
|
2260
|
+
throw refusal("unsupported_pair", "omniston: TON source requires an independent escrow-payload validator");
|
|
2169
2261
|
}
|
|
2170
2262
|
await validateTonEscrowPayload(message, {
|
|
2171
2263
|
quoteId: ref.quoteId,
|
|
@@ -2212,7 +2304,7 @@ function omnistonProvider(opts = {}) {
|
|
|
2212
2304
|
const result = bindIntent(decoded, stored);
|
|
2213
2305
|
if (!result.ok && result.reason === "mismatch") {
|
|
2214
2306
|
trace("intent-mismatch", { where: "trackIntent", field: result.field });
|
|
2215
|
-
throw
|
|
2307
|
+
throw refusal(mismatchReason(result.field), `omniston: intent id does not match the quote it was issued for (${result.field})`);
|
|
2216
2308
|
}
|
|
2217
2309
|
if (result.ok) {
|
|
2218
2310
|
if (result.legacy) trace("legacy-intent", { where: "trackIntent" });
|
|
@@ -2392,12 +2484,12 @@ function omnistonProvider(opts = {}) {
|
|
|
2392
2484
|
if (!ref) throw new Error("omniston: malformed intent id");
|
|
2393
2485
|
const cached = await loadBuilt(ref.quoteId);
|
|
2394
2486
|
const order = cached?.evmOrder;
|
|
2395
|
-
if (!order) throw
|
|
2487
|
+
if (!order) throw refusal("quote_expired", "omniston: no signed-order context for this quote \u2014 build the order again");
|
|
2396
2488
|
requireBinding(ref, cached, "registerSignedOrder");
|
|
2397
2489
|
if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) throw new Error("omniston: malformed order signature");
|
|
2398
2490
|
const td = JSON.parse(order.typedData);
|
|
2399
2491
|
const omni = await getClient();
|
|
2400
|
-
|
|
2492
|
+
const request = {
|
|
2401
2493
|
quoteId: ref.quoteId,
|
|
2402
2494
|
ownerSrcAddress: { chain: { $case: order.chainCase, value: order.owner } },
|
|
2403
2495
|
signedOrder: { order: { $case: "evmV1", value: {
|
|
@@ -2406,7 +2498,12 @@ function omnistonProvider(opts = {}) {
|
|
|
2406
2498
|
orderExtension: hexToBytes(order.orderExtension)
|
|
2407
2499
|
} } },
|
|
2408
2500
|
...order.serializedOrderDetails ? { serializedOrderDetails: hexToBytes(order.serializedOrderDetails) } : {}
|
|
2409
|
-
}
|
|
2501
|
+
};
|
|
2502
|
+
try {
|
|
2503
|
+
await omni.orderRegisterSignedOrder(request);
|
|
2504
|
+
} catch (error) {
|
|
2505
|
+
throw venueFailure(error);
|
|
2506
|
+
}
|
|
2410
2507
|
},
|
|
2411
2508
|
close() {
|
|
2412
2509
|
clientGeneration += 1;
|
|
@@ -2432,4 +2529,4 @@ export {
|
|
|
2432
2529
|
omnistonVerifyBuild,
|
|
2433
2530
|
omnistonProvider
|
|
2434
2531
|
};
|
|
2435
|
-
//# sourceMappingURL=chunk-
|
|
2532
|
+
//# sourceMappingURL=chunk-DMO24BFW.js.map
|