@anywalletkit/protocol-swap2-omniston 0.2.8 → 0.3.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/README.md +18 -1
- package/dist/{chunk-KVDPDEJB.js → chunk-CRYYP6X3.js} +2 -2
- package/dist/{chunk-KNT2XSQU.js → chunk-JVFDD7PV.js} +116 -9
- package/dist/chunk-JVFDD7PV.js.map +1 -0
- package/dist/index.cjs +116 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -4
- package/dist/protocol.cjs +52 -8
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +116 -9
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.cts +64 -2
- package/dist/provider.d.ts +64 -2
- package/dist/provider.js +9 -3
- package/dist/validators.cjs.map +1 -1
- package/dist/validators.d.cts +1 -1
- package/dist/validators.d.ts +1 -1
- package/dist/validators.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-KNT2XSQU.js.map +0 -1
- /package/dist/{chunk-KVDPDEJB.js.map → chunk-CRYYP6X3.js.map} +0 -0
package/README.md
CHANGED
|
@@ -34,6 +34,12 @@ Every route is fail-closed until its allowlists and validators are configured:
|
|
|
34
34
|
wallets, pTON wallet ownership and DeDust pool pairs on chain.
|
|
35
35
|
`tonSwapPayloadCheck: "report"` traces `ton-swap-payload <reason>` instead
|
|
36
36
|
of refusing (default `"enforce"`).
|
|
37
|
+
Behind `remoteProvider`, the app checks the built swap itself:
|
|
38
|
+
`remoteProvider({name: "omniston", baseUrl, verifyBuild: omnistonVerifyBuild({validateTonSwapTarget,
|
|
39
|
+
validateTonSwapLeg})})` opens the body with `checkTonSwapPayload` against the app's own request
|
|
40
|
+
(receiver, refund, on-chain minimum, input; the quote carries `routeSteps`) and asks the app's
|
|
41
|
+
validators about the DEX it pays. Without `verifyBuild` the engine refuses remote cell bodies; a
|
|
42
|
+
TON → EVM escrow body cannot be checked on the client and is refused by this helper.
|
|
37
43
|
- **TON → EVM** (HTLC order, the wallet pays a jetton escrow): factory
|
|
38
44
|
allowlisted on both chains, `validateTonJettonWallet`,
|
|
39
45
|
`validateTonEscrowPayload`, `validateEvmOutputPosition`.
|
|
@@ -48,6 +54,13 @@ Every route is fail-closed until its allowlists and validators are configured:
|
|
|
48
54
|
`validateEvmInputPosition` (finalized `SrcEscrowCreated` + escrow state)
|
|
49
55
|
AND `validateTonOutputPosition` (factory-deployed TON position with bound
|
|
50
56
|
state, inside its completion window) both pass.
|
|
57
|
+
Behind `remoteProvider` the order is built on your server, so the app pins
|
|
58
|
+
the order protocols itself: `remoteProvider({name: "omniston", baseUrl,
|
|
59
|
+
...omnistonPins(trustedEvmOrderProtocols)})` — the same audited table. The
|
|
60
|
+
engine then signs only an `Order` whose verifying contract is a pinned
|
|
61
|
+
spender, whose maker is this account and that sells exactly the swap's input
|
|
62
|
+
of the source token; a `Permit` or any other typed message is refused.
|
|
63
|
+
`validateEvmTypedOrder` is exported for apps that hold the settlement data.
|
|
51
64
|
- **EVM → EVM** (gasless order, same order protocol as EVM → TON): the signed
|
|
52
65
|
extension's ExtraDataArgs name the destination chain, token and deposit and
|
|
53
66
|
are checked against the quote. The EVM recipient is NOT in anything the
|
|
@@ -74,7 +87,11 @@ escrow, not that transaction), `AUDITED_OMNISTON_SETTLEMENT_CONTRACTS`,
|
|
|
74
87
|
own endpoints as `rpcUrls` (CAIP-2 → URLs, tried before the public fallbacks)
|
|
75
88
|
and `tonApiUrl` / `tonApiKey`. On serverless also inject durable
|
|
76
89
|
`secretStore` / `quoteStore` — the HTLC secret must survive to the poll that
|
|
77
|
-
discloses it.
|
|
90
|
+
discloses it. Give the quote store a `claim` (an atomic create-if-absent: Redis
|
|
91
|
+
`SET NX`, a conditional KV put): the platform picks quote ids, and a context is
|
|
92
|
+
never rebound to another user's quote under the same id. A secret is disclosed
|
|
93
|
+
only against the exact order it was locked to (its stored binding), so even a
|
|
94
|
+
rebound context gets nothing.
|
|
78
95
|
|
|
79
96
|
The destination-escrow proof requires the escrow immutables to be found in the
|
|
80
97
|
resolver's calldata AND to derive, through the trusted factory's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
omnistonProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JVFDD7PV.js";
|
|
4
4
|
import {
|
|
5
5
|
omniston
|
|
6
6
|
} from "./chunk-QDXFC6BC.js";
|
|
@@ -24,4 +24,4 @@ var OmnistonSwap2Protocol = class extends SingleProviderSwap2 {
|
|
|
24
24
|
export {
|
|
25
25
|
OmnistonSwap2Protocol
|
|
26
26
|
};
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
27
|
+
//# sourceMappingURL=chunk-CRYYP6X3.js.map
|
|
@@ -342,7 +342,12 @@ function inMemoryQuoteStore() {
|
|
|
342
342
|
return {
|
|
343
343
|
save: (id, v) => void map.set(id, v),
|
|
344
344
|
load: (id) => map.get(id),
|
|
345
|
-
clear: (id) => void map.delete(id)
|
|
345
|
+
clear: (id) => void map.delete(id),
|
|
346
|
+
claim: (id, v) => {
|
|
347
|
+
if (map.has(id)) return false;
|
|
348
|
+
map.set(id, v);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
346
351
|
};
|
|
347
352
|
}
|
|
348
353
|
function inMemorySecretStore() {
|
|
@@ -916,6 +921,13 @@ function validSwapSettlement(q) {
|
|
|
916
921
|
const v = q.settlementData.value;
|
|
917
922
|
return Array.isArray(v.routes) && v.routes.length > 0 && v.routes.every((route) => Array.isArray(route?.steps) && route.steps.length > 0 && route.steps.every((step) => Array.isArray(step?.chunks) && step.chunks.length > 0 && step.chunks.every((chunk) => typeof chunk?.protocol === "string" && chunk.protocol.length > 0 && isPositiveInteger(chunk.inputUnits) && isPositiveInteger(chunk.outputUnits)))) && isPositiveInteger(v.minOutputAmount) && BigInt(v.minOutputAmount) <= BigInt(q.outputUnits) && (v.recommendedMinOutputAmount === void 0 || isPositiveInteger(v.recommendedMinOutputAmount) && BigInt(v.recommendedMinOutputAmount) <= BigInt(q.outputUnits)) && (q.gasBudget === void 0 || isPositiveInteger(q.gasBudget));
|
|
918
923
|
}
|
|
924
|
+
function swapSteps(v) {
|
|
925
|
+
if (v.routes.length !== 1) return [{ protocol: `${v.routes.length}-routes`, inputJetton: void 0, outputJetton: void 0 }];
|
|
926
|
+
return v.routes[0].steps.map((step) => {
|
|
927
|
+
if (step.chunks.length !== 1) return { protocol: `split(${step.chunks.map((c) => c.protocol).join("|")})`, inputJetton: void 0, outputJetton: void 0 };
|
|
928
|
+
return { protocol: step.chunks[0].protocol, inputJetton: stepJetton(step.inputAsset), outputJetton: stepJetton(step.outputAsset) };
|
|
929
|
+
});
|
|
930
|
+
}
|
|
919
931
|
function swapProtocols(v) {
|
|
920
932
|
const names = /* @__PURE__ */ new Set();
|
|
921
933
|
for (const route of v.routes) for (const step of route.steps) for (const chunk of step.chunks) names.add(chunk.protocol);
|
|
@@ -1092,6 +1104,66 @@ function mapOrderStatus(order) {
|
|
|
1092
1104
|
return { state: "pending", depositReceived };
|
|
1093
1105
|
}
|
|
1094
1106
|
}
|
|
1107
|
+
function omnistonPins(trustedEvmOrderProtocols) {
|
|
1108
|
+
const byChain = new Map(Object.entries(trustedEvmOrderProtocols).map(([chain, list]) => [chain, list.map((a) => getAddress(a))]));
|
|
1109
|
+
return Object.freeze({ callTargets: () => [], spenders: (chain) => byChain.get(chain) ?? [] });
|
|
1110
|
+
}
|
|
1111
|
+
function omnistonVerifyBuild(hooks) {
|
|
1112
|
+
return async (quote, tx, params) => {
|
|
1113
|
+
if (tx.bodyBoc === void 0) return null;
|
|
1114
|
+
if (!params) return "the request this quote answered is unknown";
|
|
1115
|
+
if (quote.providerData?.settlement !== "swap") return "only TON swap bodies can be checked on the client";
|
|
1116
|
+
let steps;
|
|
1117
|
+
try {
|
|
1118
|
+
steps = JSON.parse(quote.providerData.routeSteps ?? "");
|
|
1119
|
+
} catch {
|
|
1120
|
+
return "the quote carries no route steps";
|
|
1121
|
+
}
|
|
1122
|
+
if (!Array.isArray(steps) || steps.length === 0) return "the quote carries no route steps";
|
|
1123
|
+
const source = ccAsset(params.sourceChain, params.sourceToken);
|
|
1124
|
+
const dest = ccAssetByToken(params.destChain, params.destToken);
|
|
1125
|
+
if (!source || !dest) return "unknown source or destination token";
|
|
1126
|
+
const protocols = (quote.providerData.protocols ?? "").split(",").filter(Boolean);
|
|
1127
|
+
let checked;
|
|
1128
|
+
try {
|
|
1129
|
+
checked = checkTonSwapPayload({
|
|
1130
|
+
payloadHex: Buffer2.from(tx.bodyBoc, "base64").toString("hex"),
|
|
1131
|
+
targetAddress: tx.to,
|
|
1132
|
+
sendAmount: BigInt(tx.value),
|
|
1133
|
+
trader: params.refundAddress ?? "",
|
|
1134
|
+
dest: params.destAddress,
|
|
1135
|
+
inputUnits: quote.sourceAmount,
|
|
1136
|
+
minOutputAmount: quote.minDestAmount,
|
|
1137
|
+
inputJetton: source.contract ?? null,
|
|
1138
|
+
outputJetton: dest.contract ?? null,
|
|
1139
|
+
steps: steps.map((st) => ({ protocol: String(st.protocol), inputJetton: st.inputJetton, outputJetton: st.outputJetton })),
|
|
1140
|
+
integratorAddress: hooks.integratorAddress ?? null
|
|
1141
|
+
});
|
|
1142
|
+
} catch (e) {
|
|
1143
|
+
return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
|
|
1144
|
+
}
|
|
1145
|
+
const payload = parseSwapPayload(Buffer2.from(tx.bodyBoc, "base64").toString("hex"));
|
|
1146
|
+
const target = source.contract ? payload.jettonTransfer?.destination : tx.to;
|
|
1147
|
+
if (!target) return "the body names no swap destination";
|
|
1148
|
+
try {
|
|
1149
|
+
if (!await hooks.validateTonSwapTarget({ address: target, protocols })) return `${target} is not a recognized DEX contract for ${protocols.join("+")}`;
|
|
1150
|
+
} catch {
|
|
1151
|
+
return "the swap target could not be checked";
|
|
1152
|
+
}
|
|
1153
|
+
if (hooks.validateTonSwapLeg) {
|
|
1154
|
+
for (const leg of checked.legs) {
|
|
1155
|
+
let ok = false;
|
|
1156
|
+
try {
|
|
1157
|
+
ok = await hooks.validateTonSwapLeg(leg);
|
|
1158
|
+
} catch {
|
|
1159
|
+
ok = false;
|
|
1160
|
+
}
|
|
1161
|
+
if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1095
1167
|
function omnistonProvider(opts = {}) {
|
|
1096
1168
|
opts = Object.freeze({ ...opts });
|
|
1097
1169
|
const apiUrl = opts.apiUrl ?? "wss://omni-ws.ston.fi";
|
|
@@ -1209,6 +1281,26 @@ function omnistonProvider(opts = {}) {
|
|
|
1209
1281
|
async function saveBuilt(quoteId, entry) {
|
|
1210
1282
|
await quotes.save(quoteId, JSON.stringify(entry));
|
|
1211
1283
|
}
|
|
1284
|
+
async function claimBuilt(quoteId, entry) {
|
|
1285
|
+
const json = JSON.stringify(entry);
|
|
1286
|
+
if (typeof quotes.claim === "function") {
|
|
1287
|
+
if (await quotes.claim(quoteId, json)) return true;
|
|
1288
|
+
} else if (!await quotes.load(quoteId)) {
|
|
1289
|
+
await quotes.save(quoteId, json);
|
|
1290
|
+
return true;
|
|
1291
|
+
}
|
|
1292
|
+
const existing = await loadBuilt(quoteId);
|
|
1293
|
+
if (!existing) {
|
|
1294
|
+
if (typeof quotes.claim === "function") return await quotes.claim(quoteId, json);
|
|
1295
|
+
await quotes.save(quoteId, json);
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
const a = existing.bound, b = entry.bound;
|
|
1299
|
+
const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest);
|
|
1300
|
+
if (!same) return false;
|
|
1301
|
+
await quotes.save(quoteId, json);
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1212
1304
|
async function loadBuilt(quoteId) {
|
|
1213
1305
|
const raw = await quotes.load(quoteId);
|
|
1214
1306
|
if (!raw) return void 0;
|
|
@@ -1375,7 +1467,10 @@ function omnistonProvider(opts = {}) {
|
|
|
1375
1467
|
if (BigInt(swap.minOutputAmount) < requiredMin(q.outputUnits)) return null;
|
|
1376
1468
|
if (q.gasBudget !== void 0 && BigInt(q.gasBudget) > BigInt(maxTonExecutionNano)) return null;
|
|
1377
1469
|
const minOut = swap.minOutputAmount;
|
|
1378
|
-
await
|
|
1470
|
+
if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "swap" } })) {
|
|
1471
|
+
traceFor(q.quoteId)("quote-id-reused", { kind: "swap" });
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1379
1474
|
const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
|
|
1380
1475
|
const stableOut = /^(USDT|USDC|USD₮|jUSDT)$/.test(dest.symbol);
|
|
1381
1476
|
const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
|
|
@@ -1402,6 +1497,8 @@ function omnistonProvider(opts = {}) {
|
|
|
1402
1497
|
providerData: {
|
|
1403
1498
|
settlement: "swap",
|
|
1404
1499
|
protocols: swapProtocols(swap).join(","),
|
|
1500
|
+
// For the app's own check of a remote build (omnistonVerifyBuild): the route the body must follow.
|
|
1501
|
+
routeSteps: JSON.stringify(swapSteps(swap)),
|
|
1405
1502
|
...q.gasBudget ? { gasBudget: q.gasBudget } : {},
|
|
1406
1503
|
...q.estimatedGasConsumption ? { gasEstimate: q.estimatedGasConsumption } : {}
|
|
1407
1504
|
}
|
|
@@ -1502,10 +1599,7 @@ function omnistonProvider(opts = {}) {
|
|
|
1502
1599
|
minOutputAmount: swap.minOutputAmount,
|
|
1503
1600
|
inputJetton,
|
|
1504
1601
|
outputJetton: assetContract(q.outputAsset),
|
|
1505
|
-
steps: swap
|
|
1506
|
-
if (step.chunks.length !== 1) return { protocol: `split(${step.chunks.map((c) => c.protocol).join("|")})`, inputJetton: void 0, outputJetton: void 0 };
|
|
1507
|
-
return { protocol: step.chunks[0].protocol, inputJetton: stepJetton(step.inputAsset), outputJetton: stepJetton(step.outputAsset) };
|
|
1508
|
-
}) : [{ protocol: `${swap.routes.length}-routes`, inputJetton: void 0, outputJetton: void 0 }],
|
|
1602
|
+
steps: swapSteps(swap),
|
|
1509
1603
|
integratorAddress: q.integratorAddress?.chain.$case === "ton" ? q.integratorAddress.chain.value : null
|
|
1510
1604
|
});
|
|
1511
1605
|
} catch (error) {
|
|
@@ -1793,7 +1887,9 @@ function omnistonProvider(opts = {}) {
|
|
|
1793
1887
|
const outputScaled = BigInt(q.outputUnits) * 10n ** BigInt(source.decimals);
|
|
1794
1888
|
if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
|
|
1795
1889
|
if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
|
|
1796
|
-
await
|
|
1890
|
+
if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
|
|
1891
|
+
return refuse2("the platform reused a quote id that already holds another user's quote");
|
|
1892
|
+
}
|
|
1797
1893
|
const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
|
|
1798
1894
|
const feeUsd = feeUnits / 10 ** dest.decimals;
|
|
1799
1895
|
return {
|
|
@@ -1925,6 +2021,7 @@ function omnistonProvider(opts = {}) {
|
|
|
1925
2021
|
await forget();
|
|
1926
2022
|
throw error;
|
|
1927
2023
|
}
|
|
2024
|
+
await saveBuilt(ref.quoteId, { quote: q, at: Date.now(), ...cached.bound ? { bound: cached.bound } : {} });
|
|
1928
2025
|
return {
|
|
1929
2026
|
chain: srcChain,
|
|
1930
2027
|
to: message.targetAddress,
|
|
@@ -1968,8 +2065,15 @@ function omnistonProvider(opts = {}) {
|
|
|
1968
2065
|
})
|
|
1969
2066
|
);
|
|
1970
2067
|
if (!order) return { state: "pending" };
|
|
1971
|
-
|
|
2068
|
+
let secretsHex = await store.load(decoded.quoteId);
|
|
1972
2069
|
if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
|
|
2070
|
+
if (secretsHex && cached?.quote.settlementData.$case === "order") {
|
|
2071
|
+
const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
|
|
2072
|
+
if (lockedTo !== orderBinding(bound, cached.quote)) {
|
|
2073
|
+
trace("disclosure-skipped", { gate: "binding", bound: lockedTo !== void 0, feedStatus: order.status });
|
|
2074
|
+
secretsHex = void 0;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
1973
2077
|
if (secretsHex && cached?.quote.settlementData.$case === "order") {
|
|
1974
2078
|
const q = cached.quote;
|
|
1975
2079
|
const settlement = q.settlementData.value;
|
|
@@ -2144,6 +2248,9 @@ export {
|
|
|
2144
2248
|
inMemorySecretStore,
|
|
2145
2249
|
encodeTypedMessage,
|
|
2146
2250
|
validateEvmToTonOrderExtension,
|
|
2251
|
+
validateEvmTypedOrder,
|
|
2252
|
+
omnistonPins,
|
|
2253
|
+
omnistonVerifyBuild,
|
|
2147
2254
|
omnistonProvider
|
|
2148
2255
|
};
|
|
2149
|
-
//# sourceMappingURL=chunk-
|
|
2256
|
+
//# sourceMappingURL=chunk-JVFDD7PV.js.map
|