@anywalletkit/protocol-swap2-core 0.10.3 → 0.11.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
@@ -22,6 +22,7 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  BaseSwap2Protocol: () => BaseSwap2Protocol,
24
24
  NATIVE_DECIMALS: () => NATIVE_DECIMALS,
25
+ NATIVE_FEE_CEILING_MAX_MULTIPLE: () => NATIVE_FEE_CEILING_MAX_MULTIPLE,
25
26
  SLIPPAGE_ROUNDING_BPS: () => SLIPPAGE_ROUNDING_BPS,
26
27
  SWAP2_CHAIN_ALIASES: () => SWAP2_CHAIN_ALIASES,
27
28
  SWAP2_ID_PREFIX: () => SWAP2_ID_PREFIX,
@@ -498,12 +499,62 @@ var NATIVE_FEE_CAP = Object.freeze({
498
499
  // 0.1 HYPE
499
500
  });
500
501
  var DEFAULT_NATIVE_FEE_CAP = 5000000000000000n;
501
- function declaredNativeFee(q, chain) {
502
+ var NATIVE_FEE_CEILING_MAX_MULTIPLE = 20n;
503
+ function describeCeilingAnswer(value) {
504
+ if (value === null) return "null";
505
+ if (typeof value === "bigint") return String(value);
506
+ if (typeof value === "string") return `the string "${value.slice(0, 40)}"`;
507
+ if (typeof value === "number" || typeof value === "boolean") return `the ${typeof value} ${String(value)}`;
508
+ if (typeof value === "function") return "a function";
509
+ let thenable = false;
510
+ try {
511
+ thenable = typeof value.then === "function";
512
+ if (thenable) value.then(void 0, () => {
513
+ });
514
+ } catch {
515
+ }
516
+ return thenable ? "a Promise (the hook must answer synchronously)" : typeof value === "object" ? "an object" : `a ${typeof value}`;
517
+ }
518
+ function nativeFeeCap(chain, fee, route) {
519
+ const defaultCap = NATIVE_FEE_CAP[chain] ?? DEFAULT_NATIVE_FEE_CAP;
520
+ const hook = route?.ceiling;
521
+ if (hook === void 0) return defaultCap;
522
+ const maxCeiling = defaultCap * NATIVE_FEE_CEILING_MAX_MULTIPLE;
523
+ const where = `${route.provider} ${chain} \u2192 ${route.destChain}`;
524
+ if (typeof hook !== "function") {
525
+ throw new Swap2ConfigurationError(`swap2: nativeFeeCeiling is ${describeCeilingAnswer(hook)}, not a function \u2014 the native fee of ${where} is refused`);
526
+ }
527
+ const ctx = Object.freeze({
528
+ provider: route.provider,
529
+ intentId: route.intentId,
530
+ fromChain: chain,
531
+ destChain: route.destChain,
532
+ nativeFee: fee,
533
+ defaultCap,
534
+ maxCeiling
535
+ });
536
+ let answer;
537
+ try {
538
+ answer = hook(ctx);
539
+ } catch (error) {
540
+ const why = (error instanceof Error ? error.message : String(error)).slice(0, 160);
541
+ throw new Swap2ConfigurationError(`swap2: nativeFeeCeiling threw for ${where} (${why}) \u2014 the native fee is refused`, { cause: error });
542
+ }
543
+ if (answer === void 0) return defaultCap;
544
+ if (typeof answer !== "bigint" || answer < 0n) {
545
+ throw new Swap2ConfigurationError(`swap2: nativeFeeCeiling returned ${describeCeilingAnswer(answer)} for ${where}, not a non-negative bigint or undefined \u2014 the native fee is refused`);
546
+ }
547
+ if (answer > maxCeiling) {
548
+ throw new Swap2ConfigurationError(`swap2: nativeFeeCeiling returned ${answer} for ${where}, above the hard maximum ${maxCeiling} (${NATIVE_FEE_CEILING_MAX_MULTIPLE} \xD7 the default cap ${defaultCap}) \u2014 the native fee is refused`);
549
+ }
550
+ return answer;
551
+ }
552
+ function declaredNativeFee(q, chain, route) {
502
553
  const raw = q.txRequest?.nativeFee;
503
554
  if (raw === void 0) return 0n;
504
555
  if (!/^\d+$/.test(raw)) throw new Error("swap2: quote declares a malformed native fee");
505
556
  const fee = BigInt(raw);
506
- const cap = NATIVE_FEE_CAP[chain] ?? DEFAULT_NATIVE_FEE_CAP;
557
+ const cap = nativeFeeCap(chain, fee, route);
507
558
  if (fee > cap) throw new Error(`swap2: quoted native fee ${fee} exceeds the cap ${cap} for ${chain}`);
508
559
  return fee;
509
560
  }
@@ -1127,16 +1178,18 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1127
1178
  const source = this.sourceAsset(executionOptions);
1128
1179
  if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
1129
1180
  const executionValue = BigInt(unsigned.value);
1181
+ const ceiling = this._config.nativeFeeCeiling;
1182
+ const feeRoute = { provider: q.provider, intentId: q.intentId, destChain: params.destChain, ...ceiling !== void 0 ? { ceiling } : {} };
1130
1183
  if (serialized) {
1131
1184
  if (executionValue !== (source.contract ? 0n : BigInt(q.sourceAmount))) {
1132
1185
  throw new Error("swap2: provider execution amount does not match the validated quote");
1133
1186
  }
1134
1187
  } else if (source.contract && this.fromChain.startsWith("eip155:")) {
1135
- const declared = declaredNativeFee(q, this.fromChain);
1188
+ const declared = declaredNativeFee(q, this.fromChain, feeRoute);
1136
1189
  if (executionValue !== declared) {
1137
1190
  throw new Error(declared === 0n ? "swap2: token execution attempted to attach native value" : `swap2: token execution value ${executionValue} does not match the quoted native fee ${declared}`);
1138
1191
  }
1139
- } else if (!unsigned.typedData && !unsigned.bodyBoc && executionValue !== BigInt(q.sourceAmount) + declaredNativeFee(q, this.fromChain)) {
1192
+ } else if (!unsigned.typedData && !unsigned.bodyBoc && executionValue !== BigInt(q.sourceAmount) + declaredNativeFee(q, this.fromChain, feeRoute)) {
1140
1193
  throw new Error("swap2: provider execution amount does not match the validated quote");
1141
1194
  }
1142
1195
  let tonBody;
@@ -1145,13 +1198,13 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1145
1198
  throw new Swap2ValidationError(`swap2: TON cell-body build refused \u2014 ${why}`);
1146
1199
  };
1147
1200
  const raw = this._config.tonGasCeiling;
1148
- const ceiling = raw === void 0 ? DEFAULT_TON_GAS_CEILING : BigInt(raw);
1149
- if (ceiling < 0n) throw new Swap2ConfigurationError("swap2: tonGasCeiling must be non-negative");
1201
+ const ceiling2 = raw === void 0 ? DEFAULT_TON_GAS_CEILING : BigInt(raw);
1202
+ if (ceiling2 < 0n) throw new Swap2ConfigurationError("swap2: tonGasCeiling must be non-negative");
1150
1203
  if (provider.remote === true && provider.verifiesBuilds !== true) refuse("a remote provider's cell body needs the app's own validator (remoteProvider verifyBuild)");
1151
1204
  tonBody = readTonBody(cellBody);
1152
1205
  const input = BigInt(q.sourceAmount);
1153
1206
  if (source.contract) {
1154
- if (executionValue > ceiling) refuse(`it attaches ${executionValue} nanoTON to a jetton transfer, more than the ${ceiling} gas ceiling`);
1207
+ if (executionValue > ceiling2) refuse(`it attaches ${executionValue} nanoTON to a jetton transfer, more than the ${ceiling2} gas ceiling`);
1155
1208
  const t = tonBody.jettonTransfer;
1156
1209
  if (!t) refuse("a jetton-source body must be a jetton transfer");
1157
1210
  if (t.amount !== input) refuse(`the jetton transfer moves ${t.amount}, not the swap's ${input}`);
@@ -1161,7 +1214,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1161
1214
  if (!own) refuse("the account cannot name its own jetton wallet (getJettonWalletAddress), so the target cannot be checked");
1162
1215
  if (tonRawAddress(unsigned.to) !== own) refuse(`it is sent to ${unsigned.to}, not this account's ${source.symbol ?? "source"} jetton wallet`);
1163
1216
  } else {
1164
- if (executionValue < input || executionValue - input > ceiling) refuse(`it attaches ${executionValue} nanoTON for a ${input} input, more than the ${ceiling} gas ceiling on top`);
1217
+ if (executionValue < input || executionValue - input > ceiling2) refuse(`it attaches ${executionValue} nanoTON for a ${input} input, more than the ${ceiling2} gas ceiling on top`);
1165
1218
  const pinned = provider.remote === true ? [...provider.callTargets?.(this.fromChain) ?? [], ...q.depositAddress ? [q.depositAddress] : []].map(tonRawAddress).filter((a) => !!a) : [];
1166
1219
  if (pinned.length && !pinned.includes(tonRawAddress(unsigned.to) ?? "")) refuse(`remote provider '${q.provider}' sends it to ${unsigned.to}, which the app did not pin (remoteProvider callTargets)`);
1167
1220
  }