@anywalletkit/protocol-swap2-omniston 0.2.7 → 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/dist/index.cjs CHANGED
@@ -43,8 +43,11 @@ __export(src_exports, {
43
43
  inMemoryQuoteStore: () => inMemoryQuoteStore,
44
44
  inMemorySecretStore: () => inMemorySecretStore,
45
45
  omniston: () => omniston,
46
+ omnistonPins: () => omnistonPins,
46
47
  omnistonProvider: () => omnistonProvider,
47
- validateEvmToTonOrderExtension: () => validateEvmToTonOrderExtension
48
+ omnistonVerifyBuild: () => omnistonVerifyBuild,
49
+ validateEvmToTonOrderExtension: () => validateEvmToTonOrderExtension,
50
+ validateEvmTypedOrder: () => validateEvmTypedOrder
48
51
  });
49
52
  module.exports = __toCommonJS(src_exports);
50
53
 
@@ -465,7 +468,12 @@ function inMemoryQuoteStore() {
465
468
  return {
466
469
  save: (id, v) => void map.set(id, v),
467
470
  load: (id) => map.get(id),
468
- clear: (id) => void map.delete(id)
471
+ clear: (id) => void map.delete(id),
472
+ claim: (id, v) => {
473
+ if (map.has(id)) return false;
474
+ map.set(id, v);
475
+ return true;
476
+ }
469
477
  };
470
478
  }
471
479
  function inMemorySecretStore() {
@@ -1039,6 +1047,13 @@ function validSwapSettlement(q) {
1039
1047
  const v = q.settlementData.value;
1040
1048
  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));
1041
1049
  }
1050
+ function swapSteps(v) {
1051
+ if (v.routes.length !== 1) return [{ protocol: `${v.routes.length}-routes`, inputJetton: void 0, outputJetton: void 0 }];
1052
+ return v.routes[0].steps.map((step) => {
1053
+ if (step.chunks.length !== 1) return { protocol: `split(${step.chunks.map((c) => c.protocol).join("|")})`, inputJetton: void 0, outputJetton: void 0 };
1054
+ return { protocol: step.chunks[0].protocol, inputJetton: stepJetton(step.inputAsset), outputJetton: stepJetton(step.outputAsset) };
1055
+ });
1056
+ }
1042
1057
  function swapProtocols(v) {
1043
1058
  const names = /* @__PURE__ */ new Set();
1044
1059
  for (const route of v.routes) for (const step of route.steps) for (const chunk of step.chunks) names.add(chunk.protocol);
@@ -1215,6 +1230,66 @@ function mapOrderStatus(order) {
1215
1230
  return { state: "pending", depositReceived };
1216
1231
  }
1217
1232
  }
1233
+ function omnistonPins(trustedEvmOrderProtocols) {
1234
+ const byChain = new Map(Object.entries(trustedEvmOrderProtocols).map(([chain, list]) => [chain, list.map((a) => (0, import_viem.getAddress)(a))]));
1235
+ return Object.freeze({ callTargets: () => [], spenders: (chain) => byChain.get(chain) ?? [] });
1236
+ }
1237
+ function omnistonVerifyBuild(hooks) {
1238
+ return async (quote, tx, params) => {
1239
+ if (tx.bodyBoc === void 0) return null;
1240
+ if (!params) return "the request this quote answered is unknown";
1241
+ if (quote.providerData?.settlement !== "swap") return "only TON swap bodies can be checked on the client";
1242
+ let steps;
1243
+ try {
1244
+ steps = JSON.parse(quote.providerData.routeSteps ?? "");
1245
+ } catch {
1246
+ return "the quote carries no route steps";
1247
+ }
1248
+ if (!Array.isArray(steps) || steps.length === 0) return "the quote carries no route steps";
1249
+ const source = (0, import_swap2_assets.ccAsset)(params.sourceChain, params.sourceToken);
1250
+ const dest = (0, import_swap2_assets.ccAssetByToken)(params.destChain, params.destToken);
1251
+ if (!source || !dest) return "unknown source or destination token";
1252
+ const protocols = (quote.providerData.protocols ?? "").split(",").filter(Boolean);
1253
+ let checked;
1254
+ try {
1255
+ checked = checkTonSwapPayload({
1256
+ payloadHex: import_buffer2.Buffer.from(tx.bodyBoc, "base64").toString("hex"),
1257
+ targetAddress: tx.to,
1258
+ sendAmount: BigInt(tx.value),
1259
+ trader: params.refundAddress ?? "",
1260
+ dest: params.destAddress,
1261
+ inputUnits: quote.sourceAmount,
1262
+ minOutputAmount: quote.minDestAmount,
1263
+ inputJetton: source.contract ?? null,
1264
+ outputJetton: dest.contract ?? null,
1265
+ steps: steps.map((st) => ({ protocol: String(st.protocol), inputJetton: st.inputJetton, outputJetton: st.outputJetton })),
1266
+ integratorAddress: hooks.integratorAddress ?? null
1267
+ });
1268
+ } catch (e) {
1269
+ return e instanceof Error ? e.message.replace(/^omniston: /, "") : String(e);
1270
+ }
1271
+ const payload = parseSwapPayload(import_buffer2.Buffer.from(tx.bodyBoc, "base64").toString("hex"));
1272
+ const target = source.contract ? payload.jettonTransfer?.destination : tx.to;
1273
+ if (!target) return "the body names no swap destination";
1274
+ try {
1275
+ if (!await hooks.validateTonSwapTarget({ address: target, protocols })) return `${target} is not a recognized DEX contract for ${protocols.join("+")}`;
1276
+ } catch {
1277
+ return "the swap target could not be checked";
1278
+ }
1279
+ if (hooks.validateTonSwapLeg) {
1280
+ for (const leg of checked.legs) {
1281
+ let ok = false;
1282
+ try {
1283
+ ok = await hooks.validateTonSwapLeg(leg);
1284
+ } catch {
1285
+ ok = false;
1286
+ }
1287
+ if (!ok) return `${leg.kind} v${leg.version} leg not verified`;
1288
+ }
1289
+ }
1290
+ return null;
1291
+ };
1292
+ }
1218
1293
  function omnistonProvider(opts = {}) {
1219
1294
  opts = Object.freeze({ ...opts });
1220
1295
  const apiUrl = opts.apiUrl ?? "wss://omni-ws.ston.fi";
@@ -1332,6 +1407,26 @@ function omnistonProvider(opts = {}) {
1332
1407
  async function saveBuilt(quoteId, entry) {
1333
1408
  await quotes.save(quoteId, JSON.stringify(entry));
1334
1409
  }
1410
+ async function claimBuilt(quoteId, entry) {
1411
+ const json = JSON.stringify(entry);
1412
+ if (typeof quotes.claim === "function") {
1413
+ if (await quotes.claim(quoteId, json)) return true;
1414
+ } else if (!await quotes.load(quoteId)) {
1415
+ await quotes.save(quoteId, json);
1416
+ return true;
1417
+ }
1418
+ const existing = await loadBuilt(quoteId);
1419
+ if (!existing) {
1420
+ if (typeof quotes.claim === "function") return await quotes.claim(quoteId, json);
1421
+ await quotes.save(quoteId, json);
1422
+ return true;
1423
+ }
1424
+ const a = existing.bound, b = entry.bound;
1425
+ const same = !!a && !!b && a.kind === b.kind && a.srcChain === b.srcChain && sameIntentAddress(a.trader, b.trader) && sameIntentAddress(a.dest, b.dest);
1426
+ if (!same) return false;
1427
+ await quotes.save(quoteId, json);
1428
+ return true;
1429
+ }
1335
1430
  async function loadBuilt(quoteId) {
1336
1431
  const raw2 = await quotes.load(quoteId);
1337
1432
  if (!raw2) return void 0;
@@ -1498,7 +1593,10 @@ function omnistonProvider(opts = {}) {
1498
1593
  if (BigInt(swap.minOutputAmount) < requiredMin(q.outputUnits)) return null;
1499
1594
  if (q.gasBudget !== void 0 && BigInt(q.gasBudget) > BigInt(maxTonExecutionNano)) return null;
1500
1595
  const minOut = swap.minOutputAmount;
1501
- await saveBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "swap" } });
1596
+ if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "swap" } })) {
1597
+ traceFor(q.quoteId)("quote-id-reused", { kind: "swap" });
1598
+ return null;
1599
+ }
1502
1600
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
1503
1601
  const stableOut = /^(USDT|USDC|USD₮|jUSDT)$/.test(dest.symbol);
1504
1602
  const feeUsd = stableOut ? feeUnits / 10 ** dest.decimals : 0;
@@ -1525,6 +1623,8 @@ function omnistonProvider(opts = {}) {
1525
1623
  providerData: {
1526
1624
  settlement: "swap",
1527
1625
  protocols: swapProtocols(swap).join(","),
1626
+ // For the app's own check of a remote build (omnistonVerifyBuild): the route the body must follow.
1627
+ routeSteps: JSON.stringify(swapSteps(swap)),
1528
1628
  ...q.gasBudget ? { gasBudget: q.gasBudget } : {},
1529
1629
  ...q.estimatedGasConsumption ? { gasEstimate: q.estimatedGasConsumption } : {}
1530
1630
  }
@@ -1625,10 +1725,7 @@ function omnistonProvider(opts = {}) {
1625
1725
  minOutputAmount: swap.minOutputAmount,
1626
1726
  inputJetton,
1627
1727
  outputJetton: assetContract(q.outputAsset),
1628
- steps: swap.routes.length === 1 ? routeSteps.map((step) => {
1629
- if (step.chunks.length !== 1) return { protocol: `split(${step.chunks.map((c) => c.protocol).join("|")})`, inputJetton: void 0, outputJetton: void 0 };
1630
- return { protocol: step.chunks[0].protocol, inputJetton: stepJetton(step.inputAsset), outputJetton: stepJetton(step.outputAsset) };
1631
- }) : [{ protocol: `${swap.routes.length}-routes`, inputJetton: void 0, outputJetton: void 0 }],
1728
+ steps: swapSteps(swap),
1632
1729
  integratorAddress: q.integratorAddress?.chain.$case === "ton" ? q.integratorAddress.chain.value : null
1633
1730
  });
1634
1731
  } catch (error) {
@@ -1916,7 +2013,9 @@ function omnistonProvider(opts = {}) {
1916
2013
  const outputScaled = BigInt(q.outputUnits) * 10n ** BigInt(source.decimals);
1917
2014
  if (outputScaled * 2n < inputScaled || outputScaled > inputScaled * 2n) return refuse2("quoted output is outside 0.5x\u20132x of the input");
1918
2015
  if (await quoteIdInUse(q.quoteId)) return refuse2("the platform reused a quote id that already belongs to an order");
1919
- await saveBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } });
2016
+ if (!await claimBuilt(q.quoteId, { quote: q, at: Date.now(), bound: { trader, dest: params.destAddress, srcChain: params.sourceChain, kind: "order" } })) {
2017
+ return refuse2("the platform reused a quote id that already holds another user's quote");
2018
+ }
1920
2019
  const feeUnits = Number(q.protocolFeeUnits || "0") + Number(q.integratorFeeUnits || "0");
1921
2020
  const feeUsd = feeUnits / 10 ** dest.decimals;
1922
2021
  return {
@@ -2048,6 +2147,7 @@ function omnistonProvider(opts = {}) {
2048
2147
  await forget();
2049
2148
  throw error;
2050
2149
  }
2150
+ await saveBuilt(ref.quoteId, { quote: q, at: Date.now(), ...cached.bound ? { bound: cached.bound } : {} });
2051
2151
  return {
2052
2152
  chain: srcChain,
2053
2153
  to: message.targetAddress,
@@ -2091,8 +2191,15 @@ function omnistonProvider(opts = {}) {
2091
2191
  })
2092
2192
  );
2093
2193
  if (!order) return { state: "pending" };
2094
- const secretsHex = await store.load(decoded.quoteId);
2194
+ let secretsHex = await store.load(decoded.quoteId);
2095
2195
  if (!secretsHex || cached?.quote.settlementData.$case !== "order") trace("disclosure-skipped", { gate: "context", secrets: !!secretsHex, cached: !!cached, feedStatus: order.status });
2196
+ if (secretsHex && cached?.quote.settlementData.$case === "order") {
2197
+ const [lockedTo] = await store.load(bindKey(decoded.quoteId)) ?? [];
2198
+ if (lockedTo !== orderBinding(bound, cached.quote)) {
2199
+ trace("disclosure-skipped", { gate: "binding", bound: lockedTo !== void 0, feedStatus: order.status });
2200
+ secretsHex = void 0;
2201
+ }
2202
+ }
2096
2203
  if (secretsHex && cached?.quote.settlementData.$case === "order") {
2097
2204
  const q = cached.quote;
2098
2205
  const settlement = q.settlementData.value;