@sodax/sdk 2.0.0-rc.18 → 2.0.0-rc.19

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
@@ -19,6 +19,7 @@ var IconSdkRaw = require('icon-sdk-js');
19
19
  var sdkTs = require('@injectivelabs/sdk-ts');
20
20
  var tx_js = require('cosmjs-types/cosmos/tx/v1beta1/tx.js');
21
21
  var networks = require('@injectivelabs/networks');
22
+ var v4 = require('valibot');
22
23
  var BigNumber5 = require('bignumber.js');
23
24
  var swapSdkCore = require('@pancakeswap/swap-sdk-core');
24
25
  var v3Sdk = require('@pancakeswap/v3-sdk');
@@ -47,6 +48,7 @@ var rlp__namespace = /*#__PURE__*/_interopNamespace(rlp);
47
48
  var ecc__namespace = /*#__PURE__*/_interopNamespace(ecc);
48
49
  var BN__default = /*#__PURE__*/_interopDefault(BN);
49
50
  var IconSdkRaw__namespace = /*#__PURE__*/_interopNamespace(IconSdkRaw);
51
+ var v4__namespace = /*#__PURE__*/_interopNamespace(v4);
50
52
  var BigNumber5__default = /*#__PURE__*/_interopDefault(BigNumber5);
51
53
 
52
54
  // src/errors/SodaxError.ts
@@ -119,8 +121,8 @@ function serializeCause(cause, depth) {
119
121
  }
120
122
  function sanitizeContext(value, depth) {
121
123
  const out = {};
122
- for (const [key, v] of Object.entries(value)) {
123
- out[key] = sanitizeValue(v, depth + 1);
124
+ for (const [key, v6] of Object.entries(value)) {
125
+ out[key] = sanitizeValue(v6, depth + 1);
124
126
  }
125
127
  return out;
126
128
  }
@@ -131,13 +133,13 @@ function sanitizeValue(value, depth) {
131
133
  if (t === "bigint") return value.toString();
132
134
  if (t === "string" || t === "number" || t === "boolean") return value;
133
135
  if (t === "function" || t === "symbol") return safeString(value);
134
- if (Array.isArray(value)) return value.map((v) => sanitizeValue(v, depth + 1));
136
+ if (Array.isArray(value)) return value.map((v6) => sanitizeValue(v6, depth + 1));
135
137
  if (value instanceof Date) return value.toISOString();
136
138
  if (value instanceof Map) {
137
- return Array.from(value.entries()).map(([k, v]) => [sanitizeValue(k, depth + 1), sanitizeValue(v, depth + 1)]);
139
+ return Array.from(value.entries()).map(([k, v6]) => [sanitizeValue(k, depth + 1), sanitizeValue(v6, depth + 1)]);
138
140
  }
139
141
  if (value instanceof Set) {
140
- return Array.from(value.values()).map((v) => sanitizeValue(v, depth + 1));
142
+ return Array.from(value.values()).map((v6) => sanitizeValue(v6, depth + 1));
141
143
  }
142
144
  if (value instanceof Error) return { name: value.name, message: value.message };
143
145
  if (t === "object") {
@@ -149,9 +151,9 @@ function sanitizeValue(value, depth) {
149
151
  }
150
152
  return safeString(value);
151
153
  }
152
- function safeString(v) {
154
+ function safeString(v6) {
153
155
  try {
154
- return String(v);
156
+ return String(v6);
155
157
  } catch {
156
158
  return "[unserializable]";
157
159
  }
@@ -199,6 +201,7 @@ var SODAX_FEATURES = [
199
201
  "dex",
200
202
  "partner",
201
203
  "recovery",
204
+ "backend",
202
205
  "leverageYield"
203
206
  ];
204
207
 
@@ -2640,7 +2643,7 @@ var RelayChainIdMap = {
2640
2643
  [ChainKeys.STACKS_MAINNET]: 60n
2641
2644
  };
2642
2645
  var INTENT_CHAIN_IDS = Object.values(RelayChainIdMap);
2643
- var IntentRelayChainIdToChainKey = Object.fromEntries(Object.entries(RelayChainIdMap).map(([chainKey, chainId]) => [chainId, chainKey]));
2646
+ var IntentRelayChainIdToChainKey = new Map(Object.entries(RelayChainIdMap).map(([chainKey, chainId]) => [chainId, chainKey]));
2644
2647
  var CHAIN_LOGO_BASE_URL = "https://raw.githubusercontent.com/icon-project/sodax-sdks/main/packages/assets/chain";
2645
2648
  var chainLogo = (key) => `${CHAIN_LOGO_BASE_URL}/${key}.png`;
2646
2649
  var baseChainInfo = {
@@ -4246,7 +4249,7 @@ function isValidWalletProviderForChainKey(chainKey, walletProvider) {
4246
4249
  }
4247
4250
 
4248
4251
  // ../types/dist/index.js
4249
- var CONFIG_VERSION = 216;
4252
+ var CONFIG_VERSION = 217;
4250
4253
  function isEvmSpokeChainConfig(value) {
4251
4254
  return typeof value === "object" && value !== null && value.chain.type === "EVM" && value.chain.key !== HUB_CHAIN_KEY;
4252
4255
  }
@@ -4336,26 +4339,6 @@ function isRawDestinationParams(value) {
4336
4339
  if (typeof obj.dstAddress !== "string") return false;
4337
4340
  return typeof obj.dstChainKey === "string" && spokeChainKeysSet.has(obj.dstChainKey);
4338
4341
  }
4339
- function isSubmitSwapTxResponse(value) {
4340
- return typeof value === "object" && value !== null && typeof value.success === "boolean" && typeof value.message === "string";
4341
- }
4342
- function isSubmitSwapTxStatusResponse(value) {
4343
- if (typeof value !== "object" || value === null) return false;
4344
- const obj = value;
4345
- if (typeof obj.success !== "boolean") return false;
4346
- if (typeof obj.data !== "object" || obj.data === null) return false;
4347
- const data = obj.data;
4348
- if (typeof data.txHash !== "string") return false;
4349
- if (typeof data.srcChainKey !== "string") return false;
4350
- if (typeof data.status !== "string") return false;
4351
- if (typeof data.failedAttempts !== "number") return false;
4352
- if (data.result !== void 0) {
4353
- if (typeof data.result !== "object" || data.result === null) return false;
4354
- const result = data.result;
4355
- if (typeof result.dstIntentTxHash !== "string") return false;
4356
- }
4357
- return true;
4358
- }
4359
4342
  function isBitcoinWalletProviderType(wp) {
4360
4343
  return wp.chainType === "BITCOIN";
4361
4344
  }
@@ -4393,11 +4376,11 @@ async function retry(action, retryCount = DEFAULT_MAX_RETRY, delayMs = DEFAULT_R
4393
4376
  throw new Error(`Retry exceeded MAX_RETRY_DEFAULT=${DEFAULT_MAX_RETRY}`);
4394
4377
  }
4395
4378
  function getRandomBytes(length) {
4396
- const array = new Uint8Array(length);
4379
+ const array3 = new Uint8Array(length);
4397
4380
  for (let i = 0; i < length; i++) {
4398
- array[i] = Math.floor(Math.random() * 256);
4381
+ array3[i] = Math.floor(Math.random() * 256);
4399
4382
  }
4400
- return array;
4383
+ return array3;
4401
4384
  }
4402
4385
  function randomUint256() {
4403
4386
  const bytes = getRandomBytes(32);
@@ -13644,29 +13627,6 @@ var universalRouterAbi = [
13644
13627
  }
13645
13628
  ];
13646
13629
 
13647
- // src/shared/utils/deepMerge.ts
13648
- function deepMerge(target, source) {
13649
- const result = { ...target };
13650
- for (const key of Object.keys(source)) {
13651
- const sourceVal = source[key];
13652
- const targetVal = target[key];
13653
- if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
13654
- result[key] = deepMerge(
13655
- targetVal,
13656
- sourceVal
13657
- );
13658
- } else if (sourceVal !== void 0) {
13659
- result[key] = sourceVal;
13660
- }
13661
- }
13662
- return result;
13663
- }
13664
-
13665
- // src/shared/config/mergeSodaxConfig.ts
13666
- function mergeSodaxConfig(base2, override) {
13667
- return deepMerge(base2, override);
13668
- }
13669
-
13670
13630
  // src/shared/logger.ts
13671
13631
  var consoleLogger = {
13672
13632
  debug: (message, data) => data ? console.debug(message, data) : console.debug(message),
@@ -13759,8 +13719,9 @@ function resolveAnalytics(option) {
13759
13719
  // src/shared/config/ConfigService.ts
13760
13720
  var ConfigService = class {
13761
13721
  sodax;
13762
- api;
13763
- userConfig;
13722
+ // TODO(config-v2): restore `api` / `userConfig` when initialize() dynamic fetch is re-enabled.
13723
+ // private readonly api: BackendApiService;
13724
+ // private readonly userConfig?: SodaxOptions;
13764
13725
  /**
13765
13726
  * SDK log sink. Resolved once at construction and kept independent of {@link sodax} so that
13766
13727
  * {@link initialize}'s dynamic-config swap never clobbers it. Read by services via `config.logger`.
@@ -13789,10 +13750,10 @@ var ConfigService = class {
13789
13750
  stakedATokenAddressesSet;
13790
13751
  chainToSupportedTokenAddressMap;
13791
13752
  hubAssetToXTokenMap;
13753
+ // `api` / `userConfig` are accepted but unused while initialize()'s dynamic fetch is disabled
13754
+ // (see TODO(config-v2) below); restore their assignments when re-enabling.
13792
13755
  constructor({ api, config, userConfig, logger, analytics, fee }) {
13793
- this.api = api;
13794
13756
  this.sodax = config;
13795
- this.userConfig = userConfig;
13796
13757
  this.logger = logger ?? resolveLogger(void 0);
13797
13758
  this.analytics = analytics ?? noopAnalytics;
13798
13759
  this.fee = fee;
@@ -13800,19 +13761,6 @@ var ConfigService = class {
13800
13761
  }
13801
13762
  async initialize() {
13802
13763
  try {
13803
- const result = await this.api.getAllConfig();
13804
- if (!result.ok) return result;
13805
- const response = result.value;
13806
- if (!response.version || response.version < CONFIG_VERSION) {
13807
- this.logger.warn(
13808
- `Dynamic config version is less than the current version, resorting to the default one. Current version: ${CONFIG_VERSION}, response version: ${response.version}`
13809
- );
13810
- } else {
13811
- const next = this.userConfig ? mergeSodaxConfig(response.config, this.userConfig) : response.config;
13812
- this.loadSodaxConfigDataStructures(next);
13813
- this.sodax = next;
13814
- this.initialized = true;
13815
- }
13816
13764
  return { ok: true, value: void 0 };
13817
13765
  } catch (error) {
13818
13766
  return { ok: false, error };
@@ -14082,9 +14030,32 @@ function parseTokenArrayFromJson(input) {
14082
14030
  }
14083
14031
  return tokens;
14084
14032
  }
14033
+
14034
+ // src/shared/utils/deepMerge.ts
14035
+ function deepMerge(target, source) {
14036
+ const result = { ...target };
14037
+ for (const key of Object.keys(source)) {
14038
+ const sourceVal = source[key];
14039
+ const targetVal = target[key];
14040
+ if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
14041
+ result[key] = deepMerge(
14042
+ targetVal,
14043
+ sourceVal
14044
+ );
14045
+ } else if (sourceVal !== void 0) {
14046
+ result[key] = sourceVal;
14047
+ }
14048
+ }
14049
+ return result;
14050
+ }
14051
+
14052
+ // src/shared/config/mergeSodaxConfig.ts
14053
+ function mergeSodaxConfig(base2, override) {
14054
+ return deepMerge(base2, override);
14055
+ }
14085
14056
  function encodeContractCalls(calls) {
14086
14057
  return viem.encodeAbiParameters(viem.parseAbiParameters("(address,uint256,bytes)[]"), [
14087
- calls.map((v) => [v.address, v.value, v.data])
14058
+ calls.map((v6) => [v6.address, v6.value, v6.data])
14088
14059
  ]);
14089
14060
  }
14090
14061
  async function waitForTransactionReceipt(hash, provider) {
@@ -15765,6 +15736,32 @@ var BitcoinSpokeService = class {
15765
15736
  throw error;
15766
15737
  }
15767
15738
  }
15739
+ /**
15740
+ * Sign and submit a TRADING-wallet raw transaction — the Bound-built *unsigned* PSBT returned by
15741
+ * `deposit({ raw: true })` / the Swaps API (`createIntent().tx.data`). Signs it with the wallet
15742
+ * provider's key, then sends it to Bound Exchange to co-sign with the second 2-of-2 key and
15743
+ * broadcast. Returns the broadcast tx id.
15744
+ *
15745
+ * This is the client-side completion of the TRADING deposit flow: the backend builds the PSBT
15746
+ * (it can't broadcast — the user's signature is missing), the client signs here, and Bound
15747
+ * co-signs + broadcasts. `relayData` ({ address, payload }) is the relay identity returned by
15748
+ * `createIntent()`; forwarding it lets Bound auto-resubmit a stuck relay. It is **not** recoverable
15749
+ * from `rawTx` (whose `to` is the asset manager, not the hub wallet), so callers must supply it.
15750
+ *
15751
+ * @throws if the chain is not in `TRADING` wallet mode (raw txs only exist in TRADING mode).
15752
+ */
15753
+ async signAndSubmitRawTransaction(params) {
15754
+ if (this.walletMode !== "TRADING") {
15755
+ throw new Error("signAndSubmitRawTransaction requires TRADING wallet mode.");
15756
+ }
15757
+ const { rawTx, walletProvider, relayData, accessToken = this.radfi.accessToken } = params;
15758
+ if (!rawTx?.data || !rawTx?.from) {
15759
+ throw new Error("signAndSubmitRawTransaction: rawTx.data (PSBT) and rawTx.from are required.");
15760
+ }
15761
+ const signedTx = await walletProvider.signTransaction(rawTx.data, false);
15762
+ const signedBase64Tx = normalizePsbtToBase64(signedTx);
15763
+ return this.radfi.requestRadfiSignature({ userAddress: rawTx.from, signedBase64Tx, relayData }, accessToken);
15764
+ }
15768
15765
  /**
15769
15766
  * Build deposit PSBT with embedded cross-chain data
15770
15767
  */
@@ -16034,7 +16031,7 @@ function hexToBytes2(hex) {
16034
16031
  const al = hl / 2;
16035
16032
  if (hl % 2)
16036
16033
  throw new Error("hex string expected, got unpadded hex of length " + hl);
16037
- const array = new Uint8Array(al);
16034
+ const array3 = new Uint8Array(al);
16038
16035
  for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
16039
16036
  const n1 = asciiToBase16(hex.charCodeAt(hi));
16040
16037
  const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
@@ -16042,9 +16039,9 @@ function hexToBytes2(hex) {
16042
16039
  const char = hex[hi] + hex[hi + 1];
16043
16040
  throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
16044
16041
  }
16045
- array[ai] = n1 * 16 + n2;
16042
+ array3[ai] = n1 * 16 + n2;
16046
16043
  }
16047
- return array;
16044
+ return array3;
16048
16045
  }
16049
16046
  function concatBytes(...arrays) {
16050
16047
  let sum = 0;
@@ -16674,22 +16671,22 @@ function createHmacDrbg(hashLen, qByteLen, hmacFn) {
16674
16671
  const byte0 = Uint8Array.of(0);
16675
16672
  const byte1 = Uint8Array.of(1);
16676
16673
  const _maxDrbgIters = 1e3;
16677
- let v = u8n(hashLen);
16674
+ let v6 = u8n(hashLen);
16678
16675
  let k = u8n(hashLen);
16679
16676
  let i = 0;
16680
16677
  const reset = () => {
16681
- v.fill(1);
16678
+ v6.fill(1);
16682
16679
  k.fill(0);
16683
16680
  i = 0;
16684
16681
  };
16685
- const h = (...msgs) => hmacFn(k, concatBytes(v, ...msgs));
16682
+ const h = (...msgs) => hmacFn(k, concatBytes(v6, ...msgs));
16686
16683
  const reseed = (seed = NULL) => {
16687
16684
  k = h(byte0, seed);
16688
- v = h();
16685
+ v6 = h();
16689
16686
  if (seed.length === 0)
16690
16687
  return;
16691
16688
  k = h(byte1, seed);
16692
- v = h();
16689
+ v6 = h();
16693
16690
  };
16694
16691
  const gen = () => {
16695
16692
  if (i++ >= _maxDrbgIters)
@@ -16697,10 +16694,10 @@ function createHmacDrbg(hashLen, qByteLen, hmacFn) {
16697
16694
  let len = 0;
16698
16695
  const out = [];
16699
16696
  while (len < qByteLen) {
16700
- v = h();
16701
- const sl = v.slice();
16697
+ v6 = h();
16698
+ const sl = v6.slice();
16702
16699
  out.push(sl);
16703
- len += v.length;
16700
+ len += v6.length;
16704
16701
  }
16705
16702
  return concatBytes(...out);
16706
16703
  };
@@ -16715,18 +16712,18 @@ function createHmacDrbg(hashLen, qByteLen, hmacFn) {
16715
16712
  };
16716
16713
  return genUntil;
16717
16714
  }
16718
- function validateObject(object, fields = {}, optFields = {}) {
16719
- if (!object || typeof object !== "object")
16715
+ function validateObject(object4, fields = {}, optFields = {}) {
16716
+ if (!object4 || typeof object4 !== "object")
16720
16717
  throw new Error("expected valid options object");
16721
16718
  function checkField(fieldName, expectedType, isOpt) {
16722
- const val = object[fieldName];
16719
+ const val = object4[fieldName];
16723
16720
  if (isOpt && val === void 0)
16724
16721
  return;
16725
16722
  const current = typeof val;
16726
16723
  if (current !== expectedType || val === null)
16727
16724
  throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`);
16728
16725
  }
16729
- const iter = (f, isOpt) => Object.entries(f).forEach(([k, v]) => checkField(k, v, isOpt));
16726
+ const iter = (f, isOpt) => Object.entries(f).forEach(([k, v6]) => checkField(k, v6, isOpt));
16730
16727
  iter(fields, false);
16731
16728
  iter(optFields, true);
16732
16729
  }
@@ -16765,12 +16762,12 @@ function pow2(x, power, modulo) {
16765
16762
  }
16766
16763
  return res;
16767
16764
  }
16768
- function invert(number, modulo) {
16769
- if (number === _0n2)
16765
+ function invert(number4, modulo) {
16766
+ if (number4 === _0n2)
16770
16767
  throw new Error("invert: expected non-zero number");
16771
16768
  if (modulo <= _0n2)
16772
16769
  throw new Error("invert: expected positive modulus, got " + modulo);
16773
- let a = mod(number, modulo);
16770
+ let a = mod(number4, modulo);
16774
16771
  let b = modulo;
16775
16772
  let x = _0n2, u = _1n2;
16776
16773
  while (a !== _0n2) {
@@ -16797,9 +16794,9 @@ function sqrt3mod4(Fp, n) {
16797
16794
  function sqrt5mod8(Fp, n) {
16798
16795
  const p5div8 = (Fp.ORDER - _5n) / _8n;
16799
16796
  const n2 = Fp.mul(n, _2n);
16800
- const v = Fp.pow(n2, p5div8);
16801
- const nv = Fp.mul(n, v);
16802
- const i = Fp.mul(Fp.mul(nv, _2n), v);
16797
+ const v6 = Fp.pow(n2, p5div8);
16798
+ const nv = Fp.mul(n, v6);
16799
+ const i = Fp.mul(Fp.mul(nv, _2n), v6);
16803
16800
  const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
16804
16801
  assertIsSquare(Fp, root, n);
16805
16802
  return root;
@@ -17274,27 +17271,27 @@ var wNAF = class {
17274
17271
  assert0(n);
17275
17272
  return acc;
17276
17273
  }
17277
- getPrecomputes(W, point, transform) {
17274
+ getPrecomputes(W, point, transform2) {
17278
17275
  let comp = pointPrecomputes.get(point);
17279
17276
  if (!comp) {
17280
17277
  comp = this.precomputeWindow(point, W);
17281
17278
  if (W !== 1) {
17282
- if (typeof transform === "function")
17283
- comp = transform(comp);
17279
+ if (typeof transform2 === "function")
17280
+ comp = transform2(comp);
17284
17281
  pointPrecomputes.set(point, comp);
17285
17282
  }
17286
17283
  }
17287
17284
  return comp;
17288
17285
  }
17289
- cached(point, scalar, transform) {
17286
+ cached(point, scalar, transform2) {
17290
17287
  const W = getW(point);
17291
- return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);
17288
+ return this.wNAF(W, this.getPrecomputes(W, point, transform2), scalar);
17292
17289
  }
17293
- unsafe(point, scalar, transform, prev) {
17290
+ unsafe(point, scalar, transform2, prev) {
17294
17291
  const W = getW(point);
17295
17292
  if (W === 1)
17296
17293
  return this._unsafeLadder(point, scalar, prev);
17297
- return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);
17294
+ return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform2), scalar, prev);
17298
17295
  }
17299
17296
  // We calculate precomputes for elliptic curve point multiplication
17300
17297
  // using windowed method. This specifies window size and
@@ -17381,9 +17378,9 @@ function edwards(params, extraOpts = {}) {
17381
17378
  validateObject(extraOpts, {}, { uvRatio: "function" });
17382
17379
  const MASK = _2n2 << BigInt(Fn.BYTES * 8) - _1n4;
17383
17380
  const modP = (n) => Fp.create(n);
17384
- const uvRatio2 = extraOpts.uvRatio || ((u, v) => {
17381
+ const uvRatio2 = extraOpts.uvRatio || ((u, v6) => {
17385
17382
  try {
17386
- return { isValid: true, value: Fp.sqrt(Fp.div(u, v)) };
17383
+ return { isValid: true, value: Fp.sqrt(Fp.div(u, v6)) };
17387
17384
  } catch (e) {
17388
17385
  return { isValid: false, value: _0n4 };
17389
17386
  }
@@ -17479,8 +17476,8 @@ function edwards(params, extraOpts = {}) {
17479
17476
  aInRange("point.y", y, _0n4, max);
17480
17477
  const y2 = modP(y * y);
17481
17478
  const u = modP(y2 - _1n4);
17482
- const v = modP(d * y2 - a);
17483
- let { isValid, value: x } = uvRatio2(u, v);
17479
+ const v6 = modP(d * y2 - a);
17480
+ let { isValid, value: x } = uvRatio2(u, v6);
17484
17481
  if (!isValid)
17485
17482
  throw new Error("bad point: invalid y coordinate");
17486
17483
  const isXOdd = (x & _1n4) === _1n4;
@@ -17820,13 +17817,13 @@ function adjustScalarBytes(bytes) {
17820
17817
  return bytes;
17821
17818
  }
17822
17819
  var ED25519_SQRT_M1 = /* @__PURE__ */ BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752");
17823
- function uvRatio(u, v) {
17820
+ function uvRatio(u, v6) {
17824
17821
  const P = ed25519_CURVE_p;
17825
- const v3 = mod(v * v * v, P);
17826
- const v7 = mod(v3 * v3 * v, P);
17822
+ const v32 = mod(v6 * v6 * v6, P);
17823
+ const v7 = mod(v32 * v32 * v6, P);
17827
17824
  const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
17828
- let x = mod(u * v3 * pow, P);
17829
- const vx2 = mod(v * x * x, P);
17825
+ let x = mod(u * v32 * pow, P);
17826
+ const vx2 = mod(v6 * x * x, P);
17830
17827
  const root1 = x;
17831
17828
  const root2 = mod(x * ED25519_SQRT_M1, P);
17832
17829
  const useRoot1 = vx2 === u;
@@ -18157,7 +18154,7 @@ function findSeatPrice(validators, maxNumberOfSeats, minimumStakeRatio, protocol
18157
18154
  return findSeatPriceForProtocolAfter49(validators, maxNumberOfSeats, minimumStakeRatio);
18158
18155
  }
18159
18156
  function findSeatPriceForProtocolBefore49(validators, numSeats) {
18160
- const stakes = validators.map((v) => BigInt(v.stake)).sort(sortBigIntAsc);
18157
+ const stakes = validators.map((v6) => BigInt(v6.stake)).sort(sortBigIntAsc);
18161
18158
  const num = BigInt(numSeats);
18162
18159
  const stakesSum = stakes.reduce((a, b) => a + b);
18163
18160
  if (stakesSum < num) {
@@ -18186,7 +18183,7 @@ function findSeatPriceForProtocolAfter49(validators, maxNumberOfSeats, minimumSt
18186
18183
  if (minimumStakeRatio.length != 2) {
18187
18184
  throw Error("minimumStakeRatio should have 2 elements");
18188
18185
  }
18189
- const stakes = validators.map((v) => BigInt(v.stake)).sort(sortBigIntAsc);
18186
+ const stakes = validators.map((v6) => BigInt(v6.stake)).sort(sortBigIntAsc);
18190
18187
  const stakesSum = stakes.reduce((a, b) => a + b);
18191
18188
  if (validators.length < maxNumberOfSeats) {
18192
18189
  return stakesSum * BigInt(minimumStakeRatio[0]) / BigInt(minimumStakeRatio[1]);
@@ -18358,10 +18355,10 @@ var DER = {
18358
18355
  if (length < 128)
18359
18356
  throw new E("tlv.decode(long): not minimal encoding");
18360
18357
  }
18361
- const v = data.subarray(pos, pos + length);
18362
- if (v.length !== length)
18358
+ const v6 = data.subarray(pos, pos + length);
18359
+ if (v6.length !== length)
18363
18360
  throw new E("tlv.decode: wrong value length");
18364
- return { v, l: data.subarray(pos + length) };
18361
+ return { v: v6, l: data.subarray(pos + length) };
18365
18362
  }
18366
18363
  },
18367
18364
  // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
@@ -18929,9 +18926,9 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
18929
18926
  extraEntropy: false
18930
18927
  };
18931
18928
  const hasLargeCofactor = CURVE_ORDER * _2n4 < Fp.ORDER;
18932
- function isBiggerThanHalfOrder(number) {
18929
+ function isBiggerThanHalfOrder(number4) {
18933
18930
  const HALF = CURVE_ORDER >> _1n6;
18934
- return number > HALF;
18931
+ return number4 > HALF;
18935
18932
  }
18936
18933
  function validateRS(title, num) {
18937
18934
  if (!Fn.isValidNot0(num))
@@ -19108,14 +19105,14 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
19108
19105
  return false;
19109
19106
  const { r, s } = sig;
19110
19107
  const h = bits2int_modN(message);
19111
- const is = Fn.inv(s);
19112
- const u1 = Fn.create(h * is);
19113
- const u2 = Fn.create(r * is);
19108
+ const is2 = Fn.inv(s);
19109
+ const u1 = Fn.create(h * is2);
19110
+ const u2 = Fn.create(r * is2);
19114
19111
  const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
19115
19112
  if (R.is0())
19116
19113
  return false;
19117
- const v = Fn.create(R.x);
19118
- return v === r;
19114
+ const v6 = Fn.create(R.x);
19115
+ return v6 === r;
19119
19116
  } catch (e) {
19120
19117
  return false;
19121
19118
  }
@@ -26461,13 +26458,18 @@ var SwapService = class {
26461
26458
  solver;
26462
26459
  partnerFee;
26463
26460
  relayerApiEndpoint;
26464
- constructor({ config, hubProvider, spoke }) {
26461
+ // backend swaps-API client + opt-in 2-step submit-tx flag
26462
+ backendApi;
26463
+ useBackendSubmitTx;
26464
+ constructor({ config, hubProvider, spoke, backendApi, useBackendSubmitTx }) {
26465
26465
  this.solver = config.solver;
26466
26466
  this.partnerFee = config.swapPartnerFee;
26467
26467
  this.relayerApiEndpoint = config.relay.relayerApiEndpoint;
26468
26468
  this.config = config;
26469
26469
  this.hubProvider = hubProvider;
26470
26470
  this.spoke = spoke;
26471
+ this.backendApi = backendApi;
26472
+ this.useBackendSubmitTx = useBackendSubmitTx ?? false;
26471
26473
  }
26472
26474
  /**
26473
26475
  * Estimates the gas cost for a raw (unsigned) transaction on a spoke chain.
@@ -26611,14 +26613,22 @@ var SwapService = class {
26611
26613
  /**
26612
26614
  * Executes a full end-to-end cross-chain swap.
26613
26615
  *
26614
- * Orchestrates the complete swap lifecycle:
26615
- * 1. Calls `createIntent` to submit the intent transaction on the source spoke chain.
26616
- * 2. Verifies the spoke transaction landed on-chain.
26617
- * 3. For non-hub source chains: submits the spoke tx to the relayer and waits for the
26618
- * relay packet to land on the hub (Sonic). Skipped when `srcChainKey` is the hub.
26619
- * 4. Calls `postExecution` to notify the solver, triggering it to fill the intent.
26620
- *
26621
- * @param _params - Swap action params including intent parameters, wallet provider, and optional timeout.
26616
+ * Orchestrates the complete swap lifecycle. `createIntent` first submits the intent transaction on
26617
+ * the source spoke chain; completion then runs via one of two paths, both bounded by a single
26618
+ * shared `timeout` budget:
26619
+ *
26620
+ * - **Client-side (default), {@link fallbackSwapSteps}:** verifies the spoke tx landed on-chain,
26621
+ * relays it to the hub (Sonic) and waits for the packet skipped when `srcChainKey` is the hub,
26622
+ * where the spoke tx already is the hub tx — then calls `postExecution` to notify the solver,
26623
+ * triggering it to fill the intent.
26624
+ * - **Backend 2-step (opt-in via `swapsOptions.useBackendSubmitTx`), {@link submitTx}:** hands the
26625
+ * broadcast tx to the swaps API, which verifies, relays and post-executes server-side, then polls
26626
+ * for completion. On ANY non-success it transparently falls back to the client-side path above —
26627
+ * safe because re-relaying / re-posting an already-processed swap is idempotent (no double-fill).
26628
+ *
26629
+ * @param _params - Swap action params including intent parameters, wallet provider, and an optional
26630
+ * `timeout` — the single shared budget for the whole completion flow (relay/poll plus any
26631
+ * fallback), so total wall-clock never exceeds it.
26622
26632
  * @returns A `Result<SwapResponse, SwapError>`. On success:
26623
26633
  * - `solverExecutionResponse` — solver acknowledgement (`{ answer: 'OK', intent_hash }`).
26624
26634
  * - `intent` — the on-chain intent object that was created.
@@ -26647,59 +26657,23 @@ var SwapService = class {
26647
26657
  "swap",
26648
26658
  async () => {
26649
26659
  try {
26650
- const timeout = _params.timeout;
26651
26660
  const createIntentResult = await this.createIntent(_params);
26652
26661
  if (!createIntentResult.ok) {
26653
26662
  return { ok: false, error: createIntentResult.error };
26654
26663
  }
26655
- const { tx: spokeTxHash, intent, relayData } = createIntentResult.value;
26656
- const verifyTxHashResult = await this.spoke.verifyTxHash({
26657
- txHash: spokeTxHash,
26658
- chainKey: srcChainKey
26659
- });
26660
- if (!verifyTxHashResult.ok) {
26661
- return { ok: false, error: verifyFailed("swap", verifyTxHashResult.error, { ...baseCtx, action: "swap" }) };
26662
- }
26663
- let dstIntentTxHash;
26664
- if (isHubChainKeyType(srcChainKey)) {
26665
- dstIntentTxHash = spokeTxHash;
26666
- } else {
26667
- const packet = await relayTxAndWaitPacket({
26668
- srcTxHash: spokeTxHash,
26669
- data: relayData,
26670
- chainKey: srcChainKey,
26671
- relayerApiEndpoint: this.relayerApiEndpoint,
26672
- timeout
26673
- });
26674
- if (!packet.ok) {
26675
- return {
26676
- ok: false,
26677
- error: mapRelayFailure(packet.error, { feature: "swap", action: "swap", ...baseCtx })
26678
- };
26679
- }
26680
- dstIntentTxHash = packet.value.dst_tx_hash;
26681
- }
26682
- const postExecResult = await this.postExecution({
26683
- intent_tx_hash: dstIntentTxHash
26684
- });
26685
- if (!postExecResult.ok) {
26686
- return { ok: false, error: postExecResult.error };
26687
- }
26688
- return {
26689
- ok: true,
26690
- value: {
26691
- solverExecutionResponse: postExecResult.value,
26692
- intent,
26693
- intentDeliveryInfo: {
26694
- srcChainKey,
26695
- srcTxHash: spokeTxHash,
26696
- srcAddress: params.srcAddress,
26697
- dstChainKey: params.dstChainKey,
26698
- dstTxHash: dstIntentTxHash,
26699
- dstAddress: params.dstAddress
26664
+ const created = createIntentResult.value;
26665
+ const deadline = Date.now() + (_params.timeout ?? DEFAULT_RELAY_TX_TIMEOUT);
26666
+ if (this.useBackendSubmitTx) {
26667
+ const submitted = await this.submitTx(_params, created, deadline);
26668
+ if (submitted.ok) return submitted;
26669
+ this.config.logger.warn(
26670
+ "[swap] backend submit-tx did not complete; falling back to the client-side relay",
26671
+ {
26672
+ error: submitted.error
26700
26673
  }
26701
- }
26702
- };
26674
+ );
26675
+ }
26676
+ return this.fallbackSwapSteps(_params, created, deadline);
26703
26677
  } catch (error) {
26704
26678
  if (isSwapError(error)) return { ok: false, error };
26705
26679
  return {
@@ -26725,6 +26699,134 @@ var SwapService = class {
26725
26699
  }
26726
26700
  );
26727
26701
  }
26702
+ /**
26703
+ * Client-side swap completion (the default path): relay the broadcast intent tx to the hub — or
26704
+ * use it directly when the source IS the hub — then notify the solver via post-execution and
26705
+ * build the {@link SwapResponse}. Extracted verbatim from `swap()` so the opt-in backend 2-step
26706
+ * path ({@link submitTx}) can fall back to it on any non-success.
26707
+ */
26708
+ async fallbackSwapSteps(_params, created, deadline) {
26709
+ const { params } = _params;
26710
+ const srcChainKey = params.srcChainKey;
26711
+ const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey };
26712
+ const { tx: spokeTxHash, intent, relayData } = created;
26713
+ const verifyTxHashResult = await this.spoke.verifyTxHash({
26714
+ txHash: created.tx,
26715
+ chainKey: srcChainKey
26716
+ });
26717
+ if (!verifyTxHashResult.ok) {
26718
+ return { ok: false, error: verifyFailed("swap", verifyTxHashResult.error, { ...baseCtx, action: "swap" }) };
26719
+ }
26720
+ let dstIntentTxHash;
26721
+ if (isHubChainKeyType(srcChainKey)) {
26722
+ dstIntentTxHash = spokeTxHash;
26723
+ } else {
26724
+ const packet = await relayTxAndWaitPacket({
26725
+ srcTxHash: spokeTxHash,
26726
+ data: relayData,
26727
+ chainKey: srcChainKey,
26728
+ relayerApiEndpoint: this.relayerApiEndpoint,
26729
+ // Remaining shared budget: ≈ full `timeout` on the flag-off path (called immediately), or
26730
+ // the reserve `submitTx` left on the backend path. Floor keeps a stalled-backend fallback viable.
26731
+ timeout: Math.max(deadline - Date.now(), 5e3)
26732
+ });
26733
+ if (!packet.ok) {
26734
+ return { ok: false, error: mapRelayFailure(packet.error, { feature: "swap", action: "swap", ...baseCtx }) };
26735
+ }
26736
+ dstIntentTxHash = packet.value.dst_tx_hash;
26737
+ }
26738
+ const postExecResult = await this.postExecution({
26739
+ intent_tx_hash: dstIntentTxHash
26740
+ });
26741
+ if (!postExecResult.ok) {
26742
+ return { ok: false, error: postExecResult.error };
26743
+ }
26744
+ return {
26745
+ ok: true,
26746
+ value: {
26747
+ solverExecutionResponse: postExecResult.value,
26748
+ intent,
26749
+ intentDeliveryInfo: {
26750
+ srcChainKey,
26751
+ srcTxHash: spokeTxHash,
26752
+ srcAddress: params.srcAddress,
26753
+ dstChainKey: params.dstChainKey,
26754
+ dstTxHash: dstIntentTxHash,
26755
+ dstAddress: params.dstAddress
26756
+ }
26757
+ }
26758
+ };
26759
+ }
26760
+ /**
26761
+ * Backend 2-step swap path (opt-in via `swapsOptions.useBackendSubmitTx`): hand the broadcast
26762
+ * intent tx to the swaps API (`POST /swaps/submit-tx`); the backend relays + post-executes
26763
+ * server-side. Polls `getSubmitTxStatus` until `executed`, then reconstructs the same
26764
+ * {@link SwapResponse} the client-side path returns (`result.dstIntentTxHash` → delivery info,
26765
+ * `result.intent_hash` → solver response).
26766
+ *
26767
+ * Never throws — returns `{ ok: false }` on any non-success (submit `!ok`, terminal `failed` /
26768
+ * abandoned, or poll timeout) so `swap()` falls back to {@link fallbackSwapSteps}.
26769
+ *
26770
+ * Falling back is safe: re-relaying / re-posting an already-processed swap is idempotent — the
26771
+ * relay dedups and returns the existing `executed` packet, and the solver re-affirms the intent
26772
+ * (no double-fill). Verified live by `e2e-tests/e2e-relay.test.ts`. Polling stops at
26773
+ * `deadline - reserve` so the fallback keeps a guaranteed slice of the shared `swap` budget.
26774
+ */
26775
+ async submitTx(_params, created, deadline) {
26776
+ const { params } = _params;
26777
+ const srcChainKey = params.srcChainKey;
26778
+ const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey };
26779
+ const { tx: spokeTxHash, intent, relayData } = created;
26780
+ const submitTxFailed = (cause) => ({
26781
+ ok: false,
26782
+ error: executionFailed("swap", cause, { ...baseCtx, action: "swap" })
26783
+ });
26784
+ try {
26785
+ const submitted = await this.backendApi.swaps.submitTx({
26786
+ txHash: spokeTxHash,
26787
+ srcChainKey,
26788
+ walletAddress: params.srcAddress,
26789
+ intent,
26790
+ relayData: relayData.payload
26791
+ });
26792
+ if (!submitted.ok) return submitTxFailed(submitted.error);
26793
+ const reserveMs = Math.min(Math.ceil((deadline - Date.now()) / 3), 2e4);
26794
+ const pollDeadline = deadline - reserveMs;
26795
+ const pollIntervalMs = 1e3;
26796
+ while (Date.now() < pollDeadline) {
26797
+ const statusResult = await this.backendApi.swaps.getSubmitTxStatus({ txHash: spokeTxHash, srcChainKey });
26798
+ if (statusResult.ok) {
26799
+ const { status, result, failureReason, abandonedAt } = statusResult.value.data;
26800
+ if (status === "executed" && result?.dstIntentTxHash && result.intent_hash) {
26801
+ return {
26802
+ ok: true,
26803
+ value: {
26804
+ // Backend serializes the hex intent_hash as a plain string; brand it at the boundary.
26805
+ solverExecutionResponse: { answer: "OK", intent_hash: result.intent_hash },
26806
+ intent,
26807
+ intentDeliveryInfo: {
26808
+ srcChainKey,
26809
+ srcTxHash: spokeTxHash,
26810
+ srcAddress: params.srcAddress,
26811
+ dstChainKey: params.dstChainKey,
26812
+ dstTxHash: result.dstIntentTxHash,
26813
+ dstAddress: params.dstAddress
26814
+ }
26815
+ }
26816
+ };
26817
+ }
26818
+ if (status === "failed" || abandonedAt) {
26819
+ const reason = failureReason ? `: ${failureReason}` : "";
26820
+ return submitTxFailed(new Error(`backend submit-tx ${status}${reason}`));
26821
+ }
26822
+ }
26823
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
26824
+ }
26825
+ return submitTxFailed(new Error("backend submit-tx polling timed out before reaching executed"));
26826
+ } catch (error) {
26827
+ return { ok: false, error: unknownFailed("swap", error, { ...baseCtx, action: "swap" }) };
26828
+ }
26829
+ }
26728
26830
  /**
26729
26831
  * Checks whether the relevant spender contract is already approved to spend the input token amount.
26730
26832
  *
@@ -29447,71 +29549,883 @@ var MigrationService = class {
29447
29549
  }
29448
29550
  };
29449
29551
 
29450
- // src/backendApi/BackendApiService.ts
29451
- var BackendApiService = class {
29552
+ // src/backendApi/api-utils.ts
29553
+ var toJsonBody = (value) => JSON.stringify(value, (_key, val) => typeof val === "bigint" ? val.toString() : val);
29554
+ async function makeRequest(params) {
29555
+ const { endpoint, config, overrideConfig = {}, logger, serviceLabel } = params;
29556
+ const baseURL = overrideConfig.baseURL || config.baseURL || "";
29557
+ const url = `${baseURL}${endpoint}`;
29558
+ const headers = { ...config.headers, ...overrideConfig.headers };
29559
+ const controller = new AbortController();
29560
+ const timeout = overrideConfig.timeout ?? config.timeout ?? DEFAULT_BACKEND_API_TIMEOUT;
29561
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
29562
+ try {
29563
+ const response = await fetch(url, {
29564
+ method: config.method,
29565
+ headers,
29566
+ body: config.body,
29567
+ signal: controller.signal
29568
+ });
29569
+ clearTimeout(timeoutId);
29570
+ if (!response.ok) {
29571
+ const errorText = await response.text();
29572
+ throw new Error("HTTP_REQUEST_FAILED", { cause: new Error(`HTTP ${response.status}: ${errorText}`) });
29573
+ }
29574
+ const data = await response.json();
29575
+ return data;
29576
+ } catch (error) {
29577
+ clearTimeout(timeoutId);
29578
+ if (error instanceof Error) {
29579
+ if (error.name === "AbortError") {
29580
+ throw new Error("REQUEST_TIMEOUT", { cause: new Error(`Request timeout after ${timeout}ms`) });
29581
+ }
29582
+ logger.error(`[${serviceLabel}] Request error`, error);
29583
+ throw error;
29584
+ }
29585
+ logger.error(`[${serviceLabel}] Unknown error`, error);
29586
+ throw new Error("UNKNOWN_REQUEST_ERROR", { cause: error });
29587
+ }
29588
+ }
29589
+ var SwapTokenSchema = v4__namespace.object({
29590
+ symbol: v4__namespace.string(),
29591
+ name: v4__namespace.string(),
29592
+ decimals: v4__namespace.number(),
29593
+ address: v4__namespace.string(),
29594
+ chainKey: v4__namespace.string(),
29595
+ hubAsset: v4__namespace.string(),
29596
+ vault: v4__namespace.string()
29597
+ });
29598
+ var IntentResponseSchema = v4__namespace.object({
29599
+ intentId: v4__namespace.string(),
29600
+ creator: v4__namespace.string(),
29601
+ inputToken: v4__namespace.string(),
29602
+ outputToken: v4__namespace.string(),
29603
+ inputAmount: v4__namespace.string(),
29604
+ minOutputAmount: v4__namespace.string(),
29605
+ deadline: v4__namespace.string(),
29606
+ allowPartialFill: v4__namespace.boolean(),
29607
+ srcChain: v4__namespace.string(),
29608
+ dstChain: v4__namespace.string(),
29609
+ srcAddress: v4__namespace.string(),
29610
+ dstAddress: v4__namespace.string(),
29611
+ solver: v4__namespace.string(),
29612
+ data: v4__namespace.string()
29613
+ });
29614
+ var RelayExtraDataResponseSchema = v4__namespace.object({
29615
+ address: v4__namespace.string(),
29616
+ payload: v4__namespace.string()
29617
+ });
29618
+ var makeCreateIntentResponseSchema = (txSchema) => v4__namespace.object({
29619
+ tx: txSchema,
29620
+ intent: IntentResponseSchema,
29621
+ relayData: RelayExtraDataResponseSchema
29622
+ });
29623
+ var GetSwapTokensResponseSchema = v4__namespace.record(v4__namespace.string(), v4__namespace.array(SwapTokenSchema));
29624
+ var GetSwapTokensByChainResponseSchema = v4__namespace.array(SwapTokenSchema);
29625
+ var makeQuoteResponseSchema = (txSchema) => v4__namespace.object({
29626
+ quotedAmount: v4__namespace.string(),
29627
+ txData: v4__namespace.optional(makeCreateIntentResponseSchema(txSchema))
29628
+ });
29629
+ var DeadlineResponseSchema = v4__namespace.object({
29630
+ deadline: v4__namespace.string()
29631
+ });
29632
+ var AllowanceCheckResponseSchema = v4__namespace.object({
29633
+ valid: v4__namespace.boolean()
29634
+ });
29635
+ var makeApproveResponseSchema = (txSchema) => v4__namespace.object({ tx: txSchema });
29636
+ var SubmitIntentResponseSchema = v4__namespace.object({
29637
+ result: v4__namespace.unknown()
29638
+ });
29639
+ var StatusResponseSchema = v4__namespace.object({
29640
+ status: v4__namespace.picklist([-1, 1, 2, 3, 4]),
29641
+ fillTxHash: v4__namespace.optional(v4__namespace.string())
29642
+ });
29643
+ var makeCancelIntentResponseSchema = (txSchema) => v4__namespace.object({ tx: txSchema });
29644
+ var IntentHashResponseSchema = v4__namespace.object({
29645
+ hash: v4__namespace.string()
29646
+ });
29647
+ var IntentPacketResponseSchema = v4__namespace.object({
29648
+ srcChainId: v4__namespace.number(),
29649
+ srcTxHash: v4__namespace.string(),
29650
+ srcAddress: v4__namespace.string(),
29651
+ status: v4__namespace.string(),
29652
+ dstChainId: v4__namespace.number(),
29653
+ connSn: v4__namespace.number(),
29654
+ dstAddress: v4__namespace.string(),
29655
+ dstTxHash: v4__namespace.string(),
29656
+ signatures: v4__namespace.array(v4__namespace.string()),
29657
+ payload: v4__namespace.string()
29658
+ });
29659
+ var IntentStateResponseSchema = v4__namespace.object({
29660
+ exists: v4__namespace.boolean(),
29661
+ remainingInput: v4__namespace.string(),
29662
+ receivedOutput: v4__namespace.string(),
29663
+ pendingPayment: v4__namespace.boolean()
29664
+ });
29665
+ var GasEstimateResponseSchema = v4__namespace.object({
29666
+ gas: v4__namespace.unknown()
29667
+ });
29668
+ var FeeResponseSchema = v4__namespace.object({
29669
+ fee: v4__namespace.string()
29670
+ });
29671
+ var SubmitTxResponseSchema = v4__namespace.object({
29672
+ success: v4__namespace.boolean(),
29673
+ data: v4__namespace.object({
29674
+ status: v4__namespace.picklist(["inserted", "duplicate"]),
29675
+ message: v4__namespace.string()
29676
+ })
29677
+ });
29678
+ var SubmitSwapTxStatusSchema = v4__namespace.picklist([
29679
+ "pending",
29680
+ "relaying",
29681
+ "relayed",
29682
+ "posting_execution",
29683
+ "executed",
29684
+ "failed"
29685
+ ]);
29686
+ var PacketDataSchema = v4__namespace.object({
29687
+ src_chain_id: v4__namespace.number(),
29688
+ src_tx_hash: v4__namespace.string(),
29689
+ src_address: v4__namespace.string(),
29690
+ status: v4__namespace.picklist(["pending", "validating", "executing", "executed"]),
29691
+ dst_chain_id: v4__namespace.number(),
29692
+ conn_sn: v4__namespace.number(),
29693
+ dst_address: v4__namespace.string(),
29694
+ dst_tx_hash: v4__namespace.string(),
29695
+ signatures: v4__namespace.array(v4__namespace.string()),
29696
+ payload: v4__namespace.string()
29697
+ });
29698
+ var SubmitTxStatusResultSchema = v4__namespace.object({
29699
+ dstIntentTxHash: v4__namespace.string(),
29700
+ packetData: v4__namespace.optional(PacketDataSchema),
29701
+ intent_hash: v4__namespace.optional(v4__namespace.string())
29702
+ });
29703
+ var SubmitTxStatusDataSchema = v4__namespace.object({
29704
+ txHash: v4__namespace.string(),
29705
+ srcChainKey: v4__namespace.string(),
29706
+ status: SubmitSwapTxStatusSchema,
29707
+ failedAtStep: v4__namespace.optional(SubmitSwapTxStatusSchema),
29708
+ failureReason: v4__namespace.optional(v4__namespace.string()),
29709
+ processingAttempts: v4__namespace.number(),
29710
+ abandonedAt: v4__namespace.optional(v4__namespace.string()),
29711
+ result: v4__namespace.optional(SubmitTxStatusResultSchema),
29712
+ userMessage: v4__namespace.optional(v4__namespace.string()),
29713
+ intentCancelled: v4__namespace.optional(v4__namespace.boolean())
29714
+ });
29715
+ var SubmitTxStatusResponseSchema = v4__namespace.object({
29716
+ success: v4__namespace.boolean(),
29717
+ data: SubmitTxStatusDataSchema
29718
+ });
29719
+ var AddressSchema = v4__namespace.custom((input) => typeof input === "string");
29720
+ var HexSchema = v4__namespace.custom((input) => typeof input === "string");
29721
+ var BigintFromString = v4__namespace.pipe(v4__namespace.string(), v4__namespace.toBigint());
29722
+ var BytesFromIndexRecord = v4__namespace.pipe(
29723
+ v4__namespace.record(v4__namespace.string(), v4__namespace.number()),
29724
+ v4__namespace.transform((indexed) => Uint8Array.from(Object.values(indexed)))
29725
+ );
29726
+ var EvmRawTxSchema = v4__namespace.object({
29727
+ from: AddressSchema,
29728
+ to: AddressSchema,
29729
+ value: BigintFromString,
29730
+ data: HexSchema
29731
+ });
29732
+ var SolanaRawTxSchema = v4__namespace.object({
29733
+ from: v4__namespace.string(),
29734
+ to: v4__namespace.string(),
29735
+ value: BigintFromString,
29736
+ data: v4__namespace.string()
29737
+ });
29738
+ var SuiRawTxSchema = v4__namespace.object({
29739
+ from: HexSchema,
29740
+ to: v4__namespace.string(),
29741
+ value: BigintFromString,
29742
+ data: v4__namespace.string()
29743
+ });
29744
+ var StellarRawTxSchema = v4__namespace.object({
29745
+ from: v4__namespace.string(),
29746
+ to: v4__namespace.string(),
29747
+ value: BigintFromString,
29748
+ data: v4__namespace.string()
29749
+ });
29750
+ var InjectiveRawTxSchema = v4__namespace.object({
29751
+ from: HexSchema,
29752
+ to: HexSchema,
29753
+ signedDoc: v4__namespace.object({
29754
+ bodyBytes: BytesFromIndexRecord,
29755
+ authInfoBytes: BytesFromIndexRecord,
29756
+ chainId: v4__namespace.string(),
29757
+ accountNumber: BigintFromString
29758
+ })
29759
+ });
29760
+ var NearRawTxSchema = v4__namespace.object({
29761
+ signerId: v4__namespace.string(),
29762
+ params: v4__namespace.object({
29763
+ contractId: v4__namespace.string(),
29764
+ method: v4__namespace.string(),
29765
+ args: v4__namespace.custom((input) => typeof input === "object" && input !== null),
29766
+ gas: v4__namespace.optional(BigintFromString),
29767
+ deposit: v4__namespace.optional(BigintFromString)
29768
+ })
29769
+ });
29770
+ var IconRawTxSchema = v4__namespace.record(
29771
+ v4__namespace.string(),
29772
+ v4__namespace.union([v4__namespace.string(), v4__namespace.custom((input) => typeof input === "object" && input !== null)])
29773
+ );
29774
+ var StacksRawTxSchema = v4__namespace.object({
29775
+ payload: v4__namespace.string(),
29776
+ estimatedLength: v4__namespace.optional(v4__namespace.number())
29777
+ });
29778
+ var AnyRawTxSchema = v4__namespace.custom((input) => typeof input === "object" && input !== null);
29779
+ function rawTxSchemaForChainKey(chainKey) {
29780
+ let chainType;
29781
+ try {
29782
+ chainType = getChainType(chainKey);
29783
+ } catch {
29784
+ chainType = void 0;
29785
+ }
29786
+ switch (chainType) {
29787
+ case "EVM":
29788
+ return EvmRawTxSchema;
29789
+ case "SOLANA":
29790
+ return SolanaRawTxSchema;
29791
+ case "SUI":
29792
+ return SuiRawTxSchema;
29793
+ case "STELLAR":
29794
+ return StellarRawTxSchema;
29795
+ case "INJECTIVE":
29796
+ return InjectiveRawTxSchema;
29797
+ case "ICON":
29798
+ return IconRawTxSchema;
29799
+ case "STACKS":
29800
+ return StacksRawTxSchema;
29801
+ case "NEAR":
29802
+ return NearRawTxSchema;
29803
+ default:
29804
+ return AnyRawTxSchema;
29805
+ }
29806
+ }
29807
+
29808
+ // src/backendApi/SwapsApiService.ts
29809
+ var SwapsApiService = class {
29810
+ // Fully-resolved swaps-API config supplied by the caller (BackendApiService resolves the
29811
+ // `ApiConfig` union via `resolveSwapsApiConfig`); this service does not resolve the union.
29812
+ config;
29813
+ headers;
29814
+ logger;
29452
29815
  constructor(config, logger = consoleLogger) {
29453
29816
  this.config = config;
29454
29817
  this.headers = { ...config.headers };
29455
29818
  this.logger = logger;
29456
29819
  }
29457
- headers;
29458
- logger;
29459
29820
  /**
29460
- * Execute a single HTTP request and return the parsed JSON body.
29461
- *
29462
- * Applies an `AbortController`-backed timeout (falls back to `this.config.timeout`
29463
- * when `config.timeout` is absent). Throws on non-2xx status codes or when the
29464
- * request exceeds the timeout, so callers should use {@link request} instead of
29465
- * calling this directly.
29466
- *
29467
- * @throws `Error('HTTP_REQUEST_FAILED')` on non-2xx responses.
29468
- * @throws `Error('REQUEST_TIMEOUT')` when the request exceeds the timeout.
29469
- * @throws `Error('UNKNOWN_REQUEST_ERROR')` for any other unexpected failure.
29821
+ * Issues a single HTTP request, validates the JSON body against `schema`, and
29822
+ * wraps the result in `Result<T>`. The service-level `baseURL`/`timeout`/
29823
+ * `headers` are merged with the optional per-call `overrideConfig` (which takes
29824
+ * precedence) inside {@link makeRequest}. Every public method delegates here.
29470
29825
  */
29471
- async makeRequest(endpoint, config) {
29472
- const url = config.baseURL ? `${config.baseURL}${endpoint}` : `${this.config.baseURL}${endpoint}`;
29473
- const headers = { ...this.headers, ...config.headers };
29474
- const controller = new AbortController();
29475
- const timeout = config.timeout ?? this.config.timeout;
29476
- const timeoutId = setTimeout(() => controller.abort(), timeout);
29826
+ async request(endpoint, config, schema, overrideConfig) {
29477
29827
  try {
29478
- const response = await fetch(url, {
29479
- method: config.method,
29480
- headers,
29481
- body: config.body,
29482
- signal: controller.signal
29828
+ const raw = await makeRequest({
29829
+ endpoint,
29830
+ config: { baseURL: this.config.baseURL, timeout: this.config.timeout, headers: this.headers, ...config },
29831
+ overrideConfig,
29832
+ logger: this.logger,
29833
+ serviceLabel: "SwapsApiService"
29483
29834
  });
29484
- clearTimeout(timeoutId);
29485
- if (!response.ok) {
29486
- const errorText = await response.text();
29487
- throw new Error("HTTP_REQUEST_FAILED", { cause: new Error(`HTTP ${response.status}: ${errorText}`) });
29835
+ const parsed = v4__namespace.safeParse(schema, raw);
29836
+ if (!parsed.success) {
29837
+ return {
29838
+ ok: false,
29839
+ error: new SodaxError("EXTERNAL_API_ERROR", `Invalid response shape from swaps API for ${endpoint}`, {
29840
+ feature: "backend",
29841
+ context: { api: "swaps", endpoint, reason: "invalid_response_shape", issues: v4__namespace.flatten(parsed.issues) }
29842
+ })
29843
+ };
29488
29844
  }
29489
- const data = await response.json();
29490
- return data;
29845
+ return { ok: true, value: parsed.output };
29491
29846
  } catch (error) {
29492
- clearTimeout(timeoutId);
29493
- if (error instanceof Error) {
29494
- if (error.name === "AbortError") {
29495
- throw new Error("REQUEST_TIMEOUT", { cause: new Error(`Request timeout after ${timeout}ms`) });
29496
- }
29497
- this.logger.error("[BackendApiService] Request error", error);
29498
- throw error;
29499
- }
29500
- this.logger.error("[BackendApiService] Unknown error", error);
29501
- throw new Error("UNKNOWN_REQUEST_ERROR", { cause: error });
29847
+ return {
29848
+ ok: false,
29849
+ error: new SodaxError(
29850
+ "EXTERNAL_API_ERROR",
29851
+ error instanceof Error ? error.message : `Request to ${endpoint} failed`,
29852
+ {
29853
+ feature: "backend",
29854
+ cause: error,
29855
+ context: { api: "swaps", endpoint }
29856
+ }
29857
+ )
29858
+ };
29502
29859
  }
29503
29860
  }
29861
+ // ──────────────────────────────────────────────────────────────────────
29862
+ // Tokens
29863
+ // ──────────────────────────────────────────────────────────────────────
29864
+ /**
29865
+ * Fetch all supported swap tokens grouped by SpokeChainKey.
29866
+ *
29867
+ * @returns `Result<GetSwapTokensResponseV2>` — map of chain key → token list.
29868
+ */
29869
+ async getTokens(config) {
29870
+ return this.request("/swaps/tokens", { method: "GET" }, GetSwapTokensResponseSchema, config);
29871
+ }
29872
+ /**
29873
+ * Fetch supported swap tokens for a single SpokeChainKey.
29874
+ *
29875
+ * @param chainKey - SODAX SpokeChainKey (e.g. `0xa4b1.arbitrum`, `solana`).
29876
+ * @returns `Result<GetSwapTokensByChainResponseV2>` — token list for the chain.
29877
+ */
29878
+ async getTokensByChain(chainKey, config) {
29879
+ return this.request(
29880
+ `/swaps/tokens/${chainKey}`,
29881
+ { method: "GET" },
29882
+ GetSwapTokensByChainResponseSchema,
29883
+ config
29884
+ );
29885
+ }
29886
+ // ──────────────────────────────────────────────────────────────────────
29887
+ // Quote · deadline
29888
+ // ──────────────────────────────────────────────────────────────────────
29889
+ /**
29890
+ * Get a solver quote for a cross-chain swap.
29891
+ *
29892
+ * Pass `query.includeTxData = true` to also build an unsigned create-intent
29893
+ * transaction (`txData`) using the quoted amount as `minOutputAmount`; in that
29894
+ * case `srcAddress`/`dstAddress` are required in the body.
29895
+ *
29896
+ * @returns `Result<QuoteResponseV2>` — `quotedAmount` (decimal string) and optional `txData`.
29897
+ */
29898
+ async getQuote(body, query, config) {
29899
+ const endpoint = query?.includeTxData ? "/swaps/quote?includeTxData=true" : "/swaps/quote";
29900
+ const txSchema = rawTxSchemaForChainKey(body.tokenSrcChainKey);
29901
+ return this.request(
29902
+ endpoint,
29903
+ { method: "POST", body: toJsonBody(body) },
29904
+ makeQuoteResponseSchema(txSchema),
29905
+ config
29906
+ );
29907
+ }
29908
+ /**
29909
+ * Compute a swap deadline (hub timestamp + `offsetSeconds`, default 300s).
29910
+ *
29911
+ * @returns `Result<DeadlineResponseV2>` — unix-seconds deadline (decimal string).
29912
+ */
29913
+ async getDeadline(query, config) {
29914
+ const queryParams = new URLSearchParams();
29915
+ if (query?.offsetSeconds !== void 0) queryParams.append("offsetSeconds", String(query.offsetSeconds));
29916
+ const queryString = queryParams.toString();
29917
+ const endpoint = queryString.length > 0 ? `/swaps/deadline?${queryString}` : "/swaps/deadline";
29918
+ return this.request(endpoint, { method: "GET" }, DeadlineResponseSchema, config);
29919
+ }
29920
+ // ──────────────────────────────────────────────────────────────────────
29921
+ // Allowance · approve · create intent
29922
+ // ──────────────────────────────────────────────────────────────────────
29923
+ /**
29924
+ * Check whether the source token allowance is already sufficient for the intent.
29925
+ *
29926
+ * @returns `Result<AllowanceCheckResponseV2>` — `{ valid }`.
29927
+ */
29928
+ async checkAllowance(body, config) {
29929
+ return this.request(
29930
+ "/swaps/allowance/check",
29931
+ { method: "POST", body: toJsonBody(body) },
29932
+ AllowanceCheckResponseSchema,
29933
+ config
29934
+ );
29935
+ }
29936
+ /**
29937
+ * Build an unsigned token-approval transaction for the source token.
29938
+ *
29939
+ * @returns `Result<ApproveResponseV2>` — `{ tx }` (chain-specific unsigned tx).
29940
+ */
29941
+ async approve(body, config) {
29942
+ const txSchema = rawTxSchemaForChainKey(body.srcChainKey);
29943
+ return this.request(
29944
+ "/swaps/approve",
29945
+ { method: "POST", body: toJsonBody(body) },
29946
+ makeApproveResponseSchema(txSchema),
29947
+ config
29948
+ );
29949
+ }
29950
+ /**
29951
+ * Build an unsigned create-intent transaction.
29952
+ *
29953
+ * @returns `Result<CreateIntentResponseV2>` — `{ tx, intent, relayData }`.
29954
+ */
29955
+ async createIntent(body, config) {
29956
+ const txSchema = rawTxSchemaForChainKey(body.srcChainKey);
29957
+ return this.request(
29958
+ "/swaps/intents",
29959
+ { method: "POST", body: toJsonBody(body) },
29960
+ makeCreateIntentResponseSchema(txSchema),
29961
+ config
29962
+ );
29963
+ }
29964
+ // ──────────────────────────────────────────────────────────────────────
29965
+ // Intent lifecycle: submit · status · cancel · hash · packet · extra-data
29966
+ // ──────────────────────────────────────────────────────────────────────
29967
+ /**
29968
+ * Submit the broadcast intent tx to the relay.
29969
+ *
29970
+ * @returns `Result<SubmitIntentResponseV2>` — `{ result }` (opaque relay response).
29971
+ */
29972
+ async submitIntent(body, config) {
29973
+ return this.request(
29974
+ "/swaps/intents/submit",
29975
+ { method: "POST", body: toJsonBody(body) },
29976
+ SubmitIntentResponseSchema,
29977
+ config
29978
+ );
29979
+ }
29980
+ /**
29981
+ * Poll the solver for intent execution status.
29982
+ *
29983
+ * @returns `Result<StatusResponseV2>` — `{ status, fillTxHash? }` (`fillTxHash` set when `status === 3`).
29984
+ */
29985
+ async getStatus(body, config) {
29986
+ return this.request(
29987
+ "/swaps/intents/status",
29988
+ { method: "POST", body: toJsonBody(body) },
29989
+ StatusResponseSchema,
29990
+ config
29991
+ );
29992
+ }
29993
+ /**
29994
+ * Build an unsigned cancel-intent transaction. The `intent` field carries
29995
+ * `bigint` numerics — {@link toJsonBody} serializes them to decimal strings.
29996
+ *
29997
+ * @returns `Result<CancelIntentResponseV2>` — `{ tx }`.
29998
+ */
29999
+ async cancelIntent(body, config) {
30000
+ const txSchema = rawTxSchemaForChainKey(body.srcChainKey);
30001
+ return this.request(
30002
+ "/swaps/intents/cancel",
30003
+ { method: "POST", body: toJsonBody(body) },
30004
+ makeCancelIntentResponseSchema(txSchema),
30005
+ config
30006
+ );
30007
+ }
30008
+ /**
30009
+ * Compute the keccak256 hash of an Intent struct. The `intent` field carries
30010
+ * `bigint` numerics — {@link toJsonBody} serializes them to decimal strings.
30011
+ *
30012
+ * @returns `Result<IntentHashResponseV2>` — `{ hash }`.
30013
+ */
30014
+ async getIntentHash(body, config) {
30015
+ return this.request(
30016
+ "/swaps/intents/hash",
30017
+ { method: "POST", body: toJsonBody(body) },
30018
+ IntentHashResponseSchema,
30019
+ config
30020
+ );
30021
+ }
30022
+ /**
30023
+ * Long-poll the relayer until the fill packet lands on the destination chain.
30024
+ *
30025
+ * @returns `Result<IntentPacketResponseV2>` — delivered packet data.
30026
+ */
30027
+ async getSolvedIntentPacket(body, config) {
30028
+ return this.request(
30029
+ "/swaps/intents/packet",
30030
+ { method: "POST", body: toJsonBody(body) },
30031
+ IntentPacketResponseSchema,
30032
+ config
30033
+ );
30034
+ }
30035
+ /**
30036
+ * Recover the relay extra data needed by `/swaps/intents/submit`. Provide
30037
+ * EITHER `txHash` OR `intent` (whose `bigint` numerics {@link toJsonBody} serializes).
30038
+ *
30039
+ * @returns `Result<IntentExtraDataResponseV2>` — `{ address, payload }`.
30040
+ */
30041
+ async getIntentSubmitTxExtraData(body, config) {
30042
+ return this.request(
30043
+ "/swaps/intents/extra-data",
30044
+ { method: "POST", body: toJsonBody(body) },
30045
+ RelayExtraDataResponseSchema,
30046
+ config
30047
+ );
30048
+ }
30049
+ /**
30050
+ * Get the on-chain fill state for an intent by its hub-chain tx hash.
30051
+ *
30052
+ * @returns `Result<IntentStateV2>` — `{ exists, remainingInput, receivedOutput, pendingPayment }`.
30053
+ */
30054
+ async getFilledIntent(txHash, config) {
30055
+ return this.request(`/swaps/intents/${txHash}/fill`, { method: "GET" }, IntentStateResponseSchema, config);
30056
+ }
30057
+ /**
30058
+ * Look up an Intent struct by its hub-chain creation tx hash.
30059
+ *
30060
+ * @returns `Result<GetIntentResponseV2>` — the decoded intent (bigint fields as decimal strings).
30061
+ */
30062
+ async getIntent(txHash, config) {
30063
+ return this.request(`/swaps/intents/${txHash}`, { method: "GET" }, IntentResponseSchema, config);
30064
+ }
30065
+ // ──────────────────────────────────────────────────────────────────────
30066
+ // Limit orders · gas · fees
30067
+ // ──────────────────────────────────────────────────────────────────────
30068
+ /**
30069
+ * Build an unsigned create-limit-order-intent transaction (same as create-intent
30070
+ * but `deadline` is optional).
30071
+ *
30072
+ * @returns `Result<CreateLimitOrderResponseV2>` — `{ tx, intent, relayData }`.
30073
+ */
30074
+ async createLimitOrderIntent(body, config) {
30075
+ const txSchema = rawTxSchemaForChainKey(body.srcChainKey);
30076
+ return this.request(
30077
+ "/swaps/limit-orders",
30078
+ { method: "POST", body: toJsonBody(body) },
30079
+ makeCreateIntentResponseSchema(txSchema),
30080
+ config
30081
+ );
30082
+ }
30083
+ /**
30084
+ * Estimate gas for a raw transaction on a spoke chain.
30085
+ *
30086
+ * @returns `Result<GasEstimateResponseV2>` — `{ gas }` (chain-specific shape).
30087
+ */
30088
+ async estimateGas(body, config) {
30089
+ return this.request(
30090
+ "/swaps/gas/estimate",
30091
+ { method: "POST", body: toJsonBody(body) },
30092
+ GasEstimateResponseSchema,
30093
+ config
30094
+ );
30095
+ }
30096
+ /**
30097
+ * Compute the partner fee for a given input amount.
30098
+ *
30099
+ * @returns `Result<FeeResponseV2>` — `{ fee }` (decimal string).
30100
+ */
30101
+ async getPartnerFee(query, config) {
30102
+ const queryParams = new URLSearchParams({ amount: query.amount });
30103
+ return this.request(
30104
+ `/swaps/fees/partner?${queryParams.toString()}`,
30105
+ { method: "GET" },
30106
+ FeeResponseSchema,
30107
+ config
30108
+ );
30109
+ }
30110
+ /**
30111
+ * Compute the protocol (solver) fee for a given input amount.
30112
+ *
30113
+ * @returns `Result<FeeResponseV2>` — `{ fee }` (decimal string).
30114
+ */
30115
+ async getSolverFee(query, config) {
30116
+ const queryParams = new URLSearchParams({ amount: query.amount });
30117
+ return this.request(
30118
+ `/swaps/fees/solver?${queryParams.toString()}`,
30119
+ { method: "GET" },
30120
+ FeeResponseSchema,
30121
+ config
30122
+ );
30123
+ }
30124
+ // ──────────────────────────────────────────────────────────────────────
30125
+ // Submit-tx state machine
30126
+ // ──────────────────────────────────────────────────────────────────────
30127
+ /**
30128
+ * Submit a swap transaction to be processed (relay, post-execution, etc.). The
30129
+ * `intent` field carries `bigint` numerics — {@link toJsonBody} serializes them.
30130
+ * Idempotent on `(txHash, srcChainKey)`.
30131
+ *
30132
+ * @returns `Result<SubmitTxResponseV2>` — `{ success, data: { status, message } }`.
30133
+ */
30134
+ async submitTx(body, config) {
30135
+ return this.request(
30136
+ "/swaps/submit-tx",
30137
+ { method: "POST", body: toJsonBody(body) },
30138
+ SubmitTxResponseSchema,
30139
+ config
30140
+ );
30141
+ }
30142
+ /**
30143
+ * Get the processing status of a submitted swap transaction by `(txHash, srcChainKey)`.
30144
+ *
30145
+ * @returns `Result<SubmitTxStatusResponseV2>` — `{ success, data }` (processing state).
30146
+ */
30147
+ async getSubmitTxStatus(query, config) {
30148
+ const queryParams = new URLSearchParams({ txHash: query.txHash, srcChainKey: query.srcChainKey });
30149
+ return this.request(
30150
+ `/swaps/submit-tx/status?${queryParams.toString()}`,
30151
+ { method: "GET" },
30152
+ SubmitTxStatusResponseSchema,
30153
+ config
30154
+ );
30155
+ }
30156
+ // ──────────────────────────────────────────────────────────────────────
30157
+ // Utilities (parity with BackendApiService)
30158
+ // ──────────────────────────────────────────────────────────────────────
30159
+ /**
30160
+ * Merge additional headers into the service's default header set. Existing
30161
+ * keys are overwritten; keys absent from `headers` are preserved.
30162
+ */
30163
+ setHeaders(headers) {
30164
+ Object.entries(headers).forEach(([key, value]) => {
30165
+ this.headers[key] = value;
30166
+ });
30167
+ }
30168
+ /** Return the base URL the service is currently pointing at. */
30169
+ getBaseURL() {
30170
+ return this.config.baseURL;
30171
+ }
30172
+ };
30173
+
30174
+ // src/backendApi/apiConfig.ts
30175
+ function isCustomApiConfig(config) {
30176
+ return "baseApiConfig" in config || "swapsApiConfig" in config;
30177
+ }
30178
+ function layerConfigs(...slices) {
30179
+ return slices.reduce(
30180
+ (acc, slice) => slice ? {
30181
+ baseURL: slice.baseURL ?? acc.baseURL,
30182
+ timeout: slice.timeout ?? acc.timeout,
30183
+ headers: { ...acc.headers, ...slice.headers }
30184
+ } : acc,
30185
+ {
30186
+ baseURL: DEFAULT_BACKEND_API_ENDPOINT,
30187
+ timeout: DEFAULT_BACKEND_API_TIMEOUT,
30188
+ headers: { ...DEFAULT_BACKEND_API_HEADERS }
30189
+ }
30190
+ );
30191
+ }
30192
+ function resolveBaseApiConfig(config) {
30193
+ if (isCustomApiConfig(config)) {
30194
+ return layerConfigs(config.baseApiConfig);
30195
+ }
30196
+ return layerConfigs(config);
30197
+ }
30198
+ function resolveSwapsApiConfig(config) {
30199
+ if (isCustomApiConfig(config)) {
30200
+ return layerConfigs(config.baseApiConfig, config.swapsApiConfig);
30201
+ }
30202
+ return layerConfigs(config);
30203
+ }
30204
+ var ChainKeySchema = v4__namespace.custom((input) => typeof input === "string");
30205
+ var SpokeChainKeySchema = v4__namespace.custom((input) => typeof input === "string");
30206
+ var AddressSchema2 = v4__namespace.custom((input) => typeof input === "string");
30207
+ var XTokenSchema = v4__namespace.object({
30208
+ symbol: v4__namespace.string(),
30209
+ name: v4__namespace.string(),
30210
+ decimals: v4__namespace.number(),
30211
+ address: v4__namespace.string(),
30212
+ chainKey: ChainKeySchema,
30213
+ hubAsset: AddressSchema2,
30214
+ vault: AddressSchema2,
30215
+ access: v4__namespace.optional(v4__namespace.picklist(["withdrawOnly", "depositOnly"]))
30216
+ });
30217
+ var IntentStructSchema = v4__namespace.object({
30218
+ intentId: v4__namespace.string(),
30219
+ creator: v4__namespace.string(),
30220
+ inputToken: v4__namespace.string(),
30221
+ outputToken: v4__namespace.string(),
30222
+ inputAmount: v4__namespace.string(),
30223
+ minOutputAmount: v4__namespace.string(),
30224
+ deadline: v4__namespace.string(),
30225
+ allowPartialFill: v4__namespace.boolean(),
30226
+ srcChain: v4__namespace.number(),
30227
+ dstChain: v4__namespace.number(),
30228
+ srcAddress: v4__namespace.string(),
30229
+ dstAddress: v4__namespace.string(),
30230
+ solver: v4__namespace.string(),
30231
+ data: v4__namespace.string()
30232
+ });
30233
+ var IntentResponseSchema2 = v4__namespace.object({
30234
+ intentHash: v4__namespace.string(),
30235
+ txHash: v4__namespace.string(),
30236
+ logIndex: v4__namespace.number(),
30237
+ chainId: v4__namespace.number(),
30238
+ blockNumber: v4__namespace.number(),
30239
+ open: v4__namespace.boolean(),
30240
+ intent: IntentStructSchema,
30241
+ events: v4__namespace.array(v4__namespace.unknown())
30242
+ });
30243
+ var UserIntentsResponseSchema = v4__namespace.object({
30244
+ total: v4__namespace.number(),
30245
+ offset: v4__namespace.number(),
30246
+ limit: v4__namespace.number(),
30247
+ items: v4__namespace.array(IntentResponseSchema2)
30248
+ });
30249
+ var OrderbookResponseSchema = v4__namespace.object({
30250
+ total: v4__namespace.number(),
30251
+ data: v4__namespace.array(
30252
+ v4__namespace.object({
30253
+ intentState: v4__namespace.object({
30254
+ exists: v4__namespace.boolean(),
30255
+ remainingInput: v4__namespace.string(),
30256
+ receivedOutput: v4__namespace.string(),
30257
+ pendingPayment: v4__namespace.boolean()
30258
+ }),
30259
+ intentData: v4__namespace.object({
30260
+ intentId: v4__namespace.string(),
30261
+ creator: v4__namespace.string(),
30262
+ inputToken: v4__namespace.string(),
30263
+ outputToken: v4__namespace.string(),
30264
+ inputAmount: v4__namespace.string(),
30265
+ minOutputAmount: v4__namespace.string(),
30266
+ deadline: v4__namespace.string(),
30267
+ allowPartialFill: v4__namespace.boolean(),
30268
+ srcChain: v4__namespace.number(),
30269
+ dstChain: v4__namespace.number(),
30270
+ srcAddress: v4__namespace.string(),
30271
+ dstAddress: v4__namespace.string(),
30272
+ solver: v4__namespace.string(),
30273
+ data: v4__namespace.string(),
30274
+ intentHash: v4__namespace.string(),
30275
+ txHash: v4__namespace.string(),
30276
+ blockNumber: v4__namespace.number()
30277
+ })
30278
+ })
30279
+ )
30280
+ });
30281
+ var MoneyMarketPositionSchema = v4__namespace.object({
30282
+ userAddress: v4__namespace.string(),
30283
+ positions: v4__namespace.array(
30284
+ v4__namespace.object({
30285
+ reserveAddress: v4__namespace.string(),
30286
+ aTokenAddress: v4__namespace.string(),
30287
+ variableDebtTokenAddress: v4__namespace.string(),
30288
+ aTokenBalance: v4__namespace.string(),
30289
+ variableDebtTokenBalance: v4__namespace.string(),
30290
+ blockNumber: v4__namespace.number()
30291
+ })
30292
+ )
30293
+ });
30294
+ var MoneyMarketAssetSchema = v4__namespace.object({
30295
+ reserveAddress: v4__namespace.string(),
30296
+ aTokenAddress: v4__namespace.string(),
30297
+ totalATokenBalance: v4__namespace.string(),
30298
+ variableDebtTokenAddress: v4__namespace.string(),
30299
+ totalVariableDebtTokenBalance: v4__namespace.string(),
30300
+ liquidityRate: v4__namespace.string(),
30301
+ symbol: v4__namespace.string(),
30302
+ totalSuppliers: v4__namespace.number(),
30303
+ totalBorrowers: v4__namespace.number(),
30304
+ variableBorrowRate: v4__namespace.string(),
30305
+ stableBorrowRate: v4__namespace.string(),
30306
+ liquidityIndex: v4__namespace.string(),
30307
+ variableBorrowIndex: v4__namespace.string(),
30308
+ blockNumber: v4__namespace.number()
30309
+ });
30310
+ var MoneyMarketAssetsSchema = v4__namespace.array(MoneyMarketAssetSchema);
30311
+ var MoneyMarketAssetBorrowersSchema = v4__namespace.object({
30312
+ borrowers: v4__namespace.array(v4__namespace.string()),
30313
+ total: v4__namespace.number(),
30314
+ offset: v4__namespace.number(),
30315
+ limit: v4__namespace.number()
30316
+ });
30317
+ var MoneyMarketAssetSuppliersSchema = v4__namespace.object({
30318
+ suppliers: v4__namespace.array(v4__namespace.string()),
30319
+ total: v4__namespace.number(),
30320
+ offset: v4__namespace.number(),
30321
+ limit: v4__namespace.number()
30322
+ });
30323
+ var MoneyMarketBorrowersSchema = v4__namespace.object({
30324
+ borrowers: v4__namespace.array(v4__namespace.string()),
30325
+ total: v4__namespace.number(),
30326
+ offset: v4__namespace.number(),
30327
+ limit: v4__namespace.number()
30328
+ });
30329
+ var GetChainsResponseSchema = v4__namespace.array(SpokeChainKeySchema);
30330
+ var TokensByChainMapSchema = v4__namespace.custom(
30331
+ (input) => typeof input === "object" && input !== null && Object.values(input).every((tokens) => Array.isArray(tokens) && tokens.every((t) => v4__namespace.is(XTokenSchema, t)))
30332
+ );
30333
+ var TokensListSchema = v4__namespace.array(XTokenSchema);
30334
+ var ReserveAssetsSchema = v4__namespace.array(AddressSchema2);
30335
+
30336
+ // src/backendApi/BackendApiService.ts
30337
+ var BackendApiService = class {
30338
+ // sub-services exposing domain-specific APIs
30339
+ swaps;
30340
+ // resolved base-API slice of the ApiConfig union (flat config, or its `baseApiConfig`)
30341
+ config;
30342
+ headers;
30343
+ logger;
30344
+ constructor(config, logger = consoleLogger) {
30345
+ this.config = resolveBaseApiConfig(config);
30346
+ this.headers = { ...this.config.headers };
30347
+ this.logger = logger;
30348
+ this.swaps = new SwapsApiService(resolveSwapsApiConfig(config), this.logger);
30349
+ }
29504
30350
  /**
29505
30351
  * Wraps {@link makeRequest} in a `Result<T>` so all errors are captured rather
29506
30352
  * than propagated as thrown exceptions. Every public endpoint method delegates
29507
30353
  * here instead of calling `makeRequest` directly.
29508
30354
  */
29509
- async request(endpoint, config) {
30355
+ /**
30356
+ * Fold any per-call override (carried on `config` alongside `method`/`body`) over the service
30357
+ * defaults: baseURL truthy-fallback (an empty-string override defers to the default), timeout
30358
+ * nullish-fallback, headers merged (override wins per key). Mirrors the original makeRequest merge.
30359
+ */
30360
+ resolveRequestConfig(config) {
30361
+ const { baseURL, timeout, headers, ...rest } = config;
30362
+ return {
30363
+ ...rest,
30364
+ baseURL: baseURL || this.config.baseURL,
30365
+ timeout: timeout ?? this.config.timeout,
30366
+ headers: { ...this.headers, ...headers }
30367
+ };
30368
+ }
30369
+ /**
30370
+ * Wrap a thrown transport failure (HTTP_REQUEST_FAILED / REQUEST_TIMEOUT / network error) as the
30371
+ * canonical backend `SodaxError` — identical shape to SwapsApiService; the underlying failure is
30372
+ * preserved on `error.cause`.
30373
+ */
30374
+ toExternalApiError(endpoint, error) {
30375
+ return new SodaxError(
30376
+ "EXTERNAL_API_ERROR",
30377
+ error instanceof Error ? error.message : `Backend API request to ${endpoint} failed`,
30378
+ { feature: "backend", cause: error, context: { api: "backend", endpoint } }
30379
+ );
30380
+ }
30381
+ /**
30382
+ * Issue a request and validate the JSON body against `schema`, wrapping the result in `Result<T>`.
30383
+ * Mirrors {@link SwapsApiService}: a 2xx body that fails the schema is surfaced as
30384
+ * `EXTERNAL_API_ERROR` (`context.reason: 'invalid_response_shape'`) rather than returned untyped.
30385
+ * Every data/token/money-market method delegates here; the config/relay reads use
30386
+ * {@link requestUnvalidated} instead.
30387
+ */
30388
+ async request(endpoint, config, schema) {
30389
+ try {
30390
+ const raw = await makeRequest({
30391
+ endpoint,
30392
+ config: this.resolveRequestConfig(config),
30393
+ logger: this.logger,
30394
+ serviceLabel: "BackendApiService"
30395
+ });
30396
+ const parsed = v4__namespace.safeParse(schema, raw);
30397
+ if (!parsed.success) {
30398
+ return {
30399
+ ok: false,
30400
+ error: new SodaxError("EXTERNAL_API_ERROR", `Invalid response shape from backend API for ${endpoint}`, {
30401
+ feature: "backend",
30402
+ context: { api: "backend", endpoint, reason: "invalid_response_shape", issues: v4__namespace.flatten(parsed.issues) }
30403
+ })
30404
+ };
30405
+ }
30406
+ return { ok: true, value: parsed.output };
30407
+ } catch (error) {
30408
+ return { ok: false, error: this.toExternalApiError(endpoint, error) };
30409
+ }
30410
+ }
30411
+ /**
30412
+ * Issue a request WITHOUT response-shape validation (passthrough typing). Reserved for the
30413
+ * config/relay reads whose shapes are too large/brittle to schema-validate (`SodaxConfig` via
30414
+ * `getAllConfig`, `SpokeChainConfigMap` via `getSpokeChainConfig`) or carry `bigint` values that
30415
+ * cannot survive JSON validation (`getRelayChainIdMap`). `ConfigService` version-gates and falls
30416
+ * back to packaged defaults, so it relies on no response-shape guarantee from these endpoints.
30417
+ */
30418
+ async requestUnvalidated(endpoint, config) {
29510
30419
  try {
29511
- const value = await this.makeRequest(endpoint, config);
30420
+ const value = await makeRequest({
30421
+ endpoint,
30422
+ config: this.resolveRequestConfig(config),
30423
+ logger: this.logger,
30424
+ serviceLabel: "BackendApiService"
30425
+ });
29512
30426
  return { ok: true, value };
29513
30427
  } catch (error) {
29514
- return { ok: false, error };
30428
+ return { ok: false, error: this.toExternalApiError(endpoint, error) };
29515
30429
  }
29516
30430
  }
29517
30431
  // Intent endpoints
@@ -29526,7 +30440,7 @@ var BackendApiService = class {
29526
30440
  * open/closed state, token amounts, and any fill events.
29527
30441
  */
29528
30442
  async getIntentByTxHash(txHash, config) {
29529
- return this.request(`/intent/tx/${txHash}`, { ...config, method: "GET" });
30443
+ return this.request(`/intent/tx/${txHash}`, { ...config, method: "GET" }, IntentResponseSchema2);
29530
30444
  }
29531
30445
  /**
29532
30446
  * Fetch a swap intent by its canonical intent hash.
@@ -29535,58 +30449,7 @@ var BackendApiService = class {
29535
30449
  * @returns `Result<IntentResponse>` — on success, the full intent details.
29536
30450
  */
29537
30451
  async getIntentByHash(intentHash, config) {
29538
- return this.request(`/intent/${intentHash}`, { ...config, method: "GET" });
29539
- }
29540
- // Swap submit-tx endpoints
29541
- /**
29542
- * Submit a signed spoke-chain swap transaction to the backend for processing.
29543
- *
29544
- * The backend relays the transaction to the hub chain, posts execution data
29545
- * to the solver, and advances the intent through its lifecycle. The response
29546
- * shape is validated at runtime via a type guard; an invalid shape is
29547
- * returned as `{ ok: false }`.
29548
- *
29549
- * @param params - The signed transaction hash, source chain key, sender wallet
29550
- * address, intent data, and relay data required to process the swap.
29551
- * @returns `Result<SubmitSwapTxResponse>` — on success, a confirmation object
29552
- * with `success: true` and a human-readable `message`.
29553
- */
29554
- async submitSwapTx(params, config) {
29555
- const result = await this.request("/swaps/submit-tx", {
29556
- ...config,
29557
- method: "POST",
29558
- body: JSON.stringify(params)
29559
- });
29560
- if (!result.ok) return result;
29561
- if (!isSubmitSwapTxResponse(result.value)) {
29562
- return { ok: false, error: new Error("Invalid submitSwapTx response: unexpected response shape") };
29563
- }
29564
- return { ok: true, value: result.value };
29565
- }
29566
- /**
29567
- * Poll the backend relay pipeline for the current status of a previously
29568
- * submitted swap transaction.
29569
- *
29570
- * Status progresses through: `pending` → `verifying` → `verified` →
29571
- * `relaying` → `relayed` → `posting_execution` → `executed` (or `failed`).
29572
- *
29573
- * @param params - Object containing the source-chain transaction hash and,
29574
- * optionally, the source chain key to disambiguate cross-chain hashes.
29575
- * @returns `Result<SubmitSwapTxStatusResponse>` — on success, includes the
29576
- * current `status`, any `failureReason`, and (once executed) the
29577
- * `dstIntentTxHash` on the hub chain.
29578
- */
29579
- async getSubmitSwapTxStatus(params, config) {
29580
- const queryParams = new URLSearchParams();
29581
- queryParams.append("txHash", params.txHash);
29582
- if (params.srcChainKey) queryParams.append("srcChainKey", params.srcChainKey);
29583
- const endpoint = `/swaps/submit-tx/status?${queryParams.toString()}`;
29584
- const result = await this.request(endpoint, { ...config, method: "GET" });
29585
- if (!result.ok) return result;
29586
- if (!isSubmitSwapTxStatusResponse(result.value)) {
29587
- return { ok: false, error: new Error("Invalid submitSwapTxStatus response: unexpected response shape") };
29588
- }
29589
- return { ok: true, value: result.value };
30452
+ return this.request(`/intent/${intentHash}`, { ...config, method: "GET" }, IntentResponseSchema2);
29590
30453
  }
29591
30454
  // Solver endpoints
29592
30455
  /**
@@ -29603,7 +30466,7 @@ var BackendApiService = class {
29603
30466
  queryParams.append("limit", params.limit);
29604
30467
  const queryString = queryParams.toString();
29605
30468
  const endpoint = `/solver/orderbook?${queryString}`;
29606
- return this.request(endpoint, { ...config, method: "GET" });
30469
+ return this.request(endpoint, { ...config, method: "GET" }, OrderbookResponseSchema);
29607
30470
  }
29608
30471
  /**
29609
30472
  * Fetch all swap intents created by a specific wallet address, with optional
@@ -29629,7 +30492,7 @@ var BackendApiService = class {
29629
30492
  if (offset) queryParams.append("offset", offset);
29630
30493
  const queryString = queryParams.toString();
29631
30494
  const endpoint = queryString.length > 0 ? `/intent/user/${userAddress}?${queryString}` : `/intent/user/${userAddress}`;
29632
- return this.request(endpoint, { ...config, method: "GET" });
30495
+ return this.request(endpoint, { ...config, method: "GET" }, UserIntentsResponseSchema);
29633
30496
  }
29634
30497
  // Money Market endpoints
29635
30498
  /**
@@ -29644,7 +30507,11 @@ var BackendApiService = class {
29644
30507
  * position across all active reserves.
29645
30508
  */
29646
30509
  async getMoneyMarketPosition(userAddress, config) {
29647
- return this.request(`/moneymarket/position/${userAddress}`, { ...config, method: "GET" });
30510
+ return this.request(
30511
+ `/moneymarket/position/${userAddress}`,
30512
+ { ...config, method: "GET" },
30513
+ MoneyMarketPositionSchema
30514
+ );
29648
30515
  }
29649
30516
  /**
29650
30517
  * Fetch the on-chain state for every active money market reserve asset.
@@ -29653,7 +30520,7 @@ var BackendApiService = class {
29653
30520
  * snapshots including interest rates, liquidity indices, and participant counts.
29654
30521
  */
29655
30522
  async getAllMoneyMarketAssets(config) {
29656
- return this.request("/moneymarket/asset/all", { ...config, method: "GET" });
30523
+ return this.request("/moneymarket/asset/all", { ...config, method: "GET" }, MoneyMarketAssetsSchema);
29657
30524
  }
29658
30525
  /**
29659
30526
  * Fetch the on-chain state for a single money market reserve asset.
@@ -29663,7 +30530,11 @@ var BackendApiService = class {
29663
30530
  * including interest rates, total balances, and liquidity indices.
29664
30531
  */
29665
30532
  async getMoneyMarketAsset(reserveAddress, config) {
29666
- return this.request(`/moneymarket/asset/${reserveAddress}`, { ...config, method: "GET" });
30533
+ return this.request(
30534
+ `/moneymarket/asset/${reserveAddress}`,
30535
+ { ...config, method: "GET" },
30536
+ MoneyMarketAssetSchema
30537
+ );
29667
30538
  }
29668
30539
  /**
29669
30540
  * Fetch a paginated list of wallets that currently have an outstanding borrow
@@ -29680,7 +30551,7 @@ var BackendApiService = class {
29680
30551
  queryParams.append("limit", params.limit);
29681
30552
  const queryString = queryParams.toString();
29682
30553
  const endpoint = `/moneymarket/asset/${reserveAddress}/borrowers?${queryString}`;
29683
- return this.request(endpoint, { ...config, method: "GET" });
30554
+ return this.request(endpoint, { ...config, method: "GET" }, MoneyMarketAssetBorrowersSchema);
29684
30555
  }
29685
30556
  /**
29686
30557
  * Fetch a paginated list of wallets that currently have an active supply
@@ -29697,7 +30568,7 @@ var BackendApiService = class {
29697
30568
  queryParams.append("limit", params.limit);
29698
30569
  const queryString = queryParams.toString();
29699
30570
  const endpoint = `/moneymarket/asset/${reserveAddress}/suppliers?${queryString}`;
29700
- return this.request(endpoint, { ...config, method: "GET" });
30571
+ return this.request(endpoint, { ...config, method: "GET" }, MoneyMarketAssetSuppliersSchema);
29701
30572
  }
29702
30573
  /**
29703
30574
  * Fetch a paginated list of all wallet addresses that hold an active borrow
@@ -29713,7 +30584,7 @@ var BackendApiService = class {
29713
30584
  queryParams.append("limit", params.limit);
29714
30585
  const queryString = queryParams.toString();
29715
30586
  const endpoint = `/moneymarket/borrowers?${queryString}`;
29716
- return this.request(endpoint, { ...config, method: "GET" });
30587
+ return this.request(endpoint, { ...config, method: "GET" }, MoneyMarketBorrowersSchema);
29717
30588
  }
29718
30589
  /**
29719
30590
  * Fetch the complete SODAX runtime configuration in a single request.
@@ -29726,7 +30597,7 @@ var BackendApiService = class {
29726
30597
  * `config` is the current `SodaxConfig` used by all SDK services.
29727
30598
  */
29728
30599
  async getAllConfig(config) {
29729
- return this.request("/config/all", { ...config, method: "GET" });
30600
+ return this.requestUnvalidated("/config/all", { ...config, method: "GET" });
29730
30601
  }
29731
30602
  /**
29732
30603
  * Fetch the list of spoke chain keys that are currently supported by the
@@ -29739,7 +30610,7 @@ var BackendApiService = class {
29739
30610
  * `SpokeChainKey` strings (e.g. `["ethereum", "arbitrum", "solana", …]`).
29740
30611
  */
29741
30612
  async getChains(config) {
29742
- return this.request("/config/spoke/chains", { ...config, method: "GET" });
30613
+ return this.request("/config/spoke/chains", { ...config, method: "GET" }, GetChainsResponseSchema);
29743
30614
  }
29744
30615
  /**
29745
30616
  * Fetch the full map of tokens available for swapping, keyed by spoke chain.
@@ -29751,7 +30622,7 @@ var BackendApiService = class {
29751
30622
  * supported spoke chain key to its list of swappable `XToken` definitions.
29752
30623
  */
29753
30624
  async getSwapTokens(config) {
29754
- return this.request("/config/swap/tokens", { ...config, method: "GET" });
30625
+ return this.request("/config/swap/tokens", { ...config, method: "GET" }, TokensByChainMapSchema);
29755
30626
  }
29756
30627
  /**
29757
30628
  * Fetch the list of tokens available for swapping on a specific spoke chain.
@@ -29763,10 +30634,7 @@ var BackendApiService = class {
29763
30634
  * array of `XToken` definitions supported for swapping on that chain.
29764
30635
  */
29765
30636
  async getSwapTokensByChainId(chainId, config) {
29766
- return this.request(`/config/swap/${chainId}/tokens`, {
29767
- ...config,
29768
- method: "GET"
29769
- });
30637
+ return this.request(`/config/swap/${chainId}/tokens`, { ...config, method: "GET" }, TokensListSchema);
29770
30638
  }
29771
30639
  /**
29772
30640
  * Fetch the full map of tokens available in the money market (lending/borrowing),
@@ -29778,10 +30646,7 @@ var BackendApiService = class {
29778
30646
  * each supported spoke chain key to its list of money-market `XToken` definitions.
29779
30647
  */
29780
30648
  async getMoneyMarketTokens(config) {
29781
- return this.request("/config/money-market/tokens", {
29782
- ...config,
29783
- method: "GET"
29784
- });
30649
+ return this.request("/config/money-market/tokens", { ...config, method: "GET" }, TokensByChainMapSchema);
29785
30650
  }
29786
30651
  /**
29787
30652
  * Fetch the list of hub-chain reserve asset addresses registered in the
@@ -29795,10 +30660,11 @@ var BackendApiService = class {
29795
30660
  * readonly array of reserve `Address` strings.
29796
30661
  */
29797
30662
  async getMoneyMarketReserveAssets(config) {
29798
- return this.request("/config/money-market/reserve-assets", {
29799
- ...config,
29800
- method: "GET"
29801
- });
30663
+ return this.request(
30664
+ "/config/money-market/reserve-assets",
30665
+ { ...config, method: "GET" },
30666
+ ReserveAssetsSchema
30667
+ );
29802
30668
  }
29803
30669
  /**
29804
30670
  * Fetch the list of tokens available for lending/borrowing on a specific
@@ -29811,10 +30677,11 @@ var BackendApiService = class {
29811
30677
  * readonly array of `XToken` definitions supported in the money market on that chain.
29812
30678
  */
29813
30679
  async getMoneyMarketTokensByChainId(chainId, config) {
29814
- return this.request(`/config/money-market/${chainId}/tokens`, {
29815
- ...config,
29816
- method: "GET"
29817
- });
30680
+ return this.request(
30681
+ `/config/money-market/${chainId}/tokens`,
30682
+ { ...config, method: "GET" },
30683
+ TokensListSchema
30684
+ );
29818
30685
  }
29819
30686
  /**
29820
30687
  * Fetch the mapping from spoke chain keys to the numeric chain IDs used by
@@ -29828,7 +30695,7 @@ var BackendApiService = class {
29828
30695
  * `IntentRelayChainIdMap` record mapping each spoke chain key to its relay chain ID.
29829
30696
  */
29830
30697
  async getRelayChainIdMap(config) {
29831
- return this.request("/config/relay/chain-id-map", {
30698
+ return this.requestUnvalidated("/config/relay/chain-id-map", {
29832
30699
  ...config,
29833
30700
  method: "GET"
29834
30701
  });
@@ -29845,7 +30712,7 @@ var BackendApiService = class {
29845
30712
  * `SpokeChainConfigMap` for all currently enabled spoke chains.
29846
30713
  */
29847
30714
  async getSpokeChainConfig(config) {
29848
- return this.request("/config/spoke/all-chains-configs", {
30715
+ return this.requestUnvalidated("/config/spoke/all-chains-configs", {
29849
30716
  ...config,
29850
30717
  method: "GET"
29851
30718
  });
@@ -29857,12 +30724,17 @@ var BackendApiService = class {
29857
30724
  * without constructing a new service instance. Existing header keys are
29858
30725
  * overwritten; keys absent from `headers` are preserved.
29859
30726
  *
30727
+ * The headers are also fanned out to the sub-services (`swaps`), which hold
30728
+ * their own header copies — so a token set here applies to every request made
30729
+ * through this client, including `swaps.*`.
30730
+ *
29860
30731
  * @param headers - Key-value pairs to add or overwrite in the default headers.
29861
30732
  */
29862
30733
  setHeaders(headers) {
29863
30734
  Object.entries(headers).forEach(([key, value]) => {
29864
30735
  this.headers[key] = value;
29865
30736
  });
30737
+ this.swaps.setHeaders(headers);
29866
30738
  }
29867
30739
  /**
29868
30740
  * Return the base URL the service is currently pointing at.
@@ -38582,7 +39454,7 @@ var LeverageYieldService = class {
38582
39454
  }
38583
39455
  /** Looks up a vault by its `name` field. Returns `undefined` when not registered. */
38584
39456
  getVault(name) {
38585
- return this.listVaults().find((v) => v.name === name);
39457
+ return this.listVaults().find((v6) => v6.name === name);
38586
39458
  }
38587
39459
  /**
38588
39460
  * Looks up a registered vault by its on-chain proxy address (case-insensitive).
@@ -38590,7 +39462,7 @@ var LeverageYieldService = class {
38590
39462
  */
38591
39463
  getVaultByAddress(address) {
38592
39464
  const normalized = address.toLowerCase();
38593
- return this.listVaults().find((v) => v.vault.toLowerCase() === normalized);
39465
+ return this.listVaults().find((v6) => v6.vault.toLowerCase() === normalized);
38594
39466
  }
38595
39467
  /**
38596
39468
  * Resolves the intent `deadline`: returns the caller-supplied value verbatim, otherwise
@@ -39383,6 +40255,8 @@ var Sodax = class {
39383
40255
  // ICX migration service enabling ICX migration to SODA
39384
40256
  backendApi;
39385
40257
  // backend API service enabling backend API endpoints
40258
+ api;
40259
+ // syntactic sugar for backend API service
39386
40260
  bridge;
39387
40261
  // Bridge service enabling cross-chain transfers
39388
40262
  staking;
@@ -39405,8 +40279,10 @@ var Sodax = class {
39405
40279
  const logger = resolveLogger(options?.logger);
39406
40280
  const analytics = resolveAnalytics(options?.analytics);
39407
40281
  const fee = options?.fee;
40282
+ const useBackendSubmitTx = options?.swapsOptions?.useBackendSubmitTx ?? false;
39408
40283
  this.instanceConfig = options ? mergeSodaxConfig(sodaxConfig, options) : sodaxConfig;
39409
40284
  this.backendApi = new BackendApiService(this.instanceConfig.api, logger);
40285
+ this.api = this.backendApi;
39410
40286
  this.config = new ConfigService({
39411
40287
  api: this.backendApi,
39412
40288
  config: this.instanceConfig,
@@ -39420,7 +40296,9 @@ var Sodax = class {
39420
40296
  this.swaps = new SwapService({
39421
40297
  config: this.config,
39422
40298
  hubProvider: this.hubProvider,
39423
- spoke: this.spoke
40299
+ spoke: this.spoke,
40300
+ backendApi: this.backendApi,
40301
+ useBackendSubmitTx
39424
40302
  });
39425
40303
  this.moneyMarket = new MoneyMarketService({
39426
40304
  config: this.config,
@@ -39620,6 +40498,7 @@ exports.StellarSpokeService = StellarSpokeService;
39620
40498
  exports.SuiSpokeService = SuiSpokeService;
39621
40499
  exports.SupportedMigrationTokens = SupportedMigrationTokens;
39622
40500
  exports.SwapService = SwapService;
40501
+ exports.SwapsApiService = SwapsApiService;
39623
40502
  exports.USD_DECIMALS = USD_DECIMALS;
39624
40503
  exports.UiPoolDataProviderService = UiPoolDataProviderService;
39625
40504
  exports.VAULT_TOKEN_DECIMALS = VAULT_TOKEN_DECIMALS;
@@ -39820,8 +40699,6 @@ exports.isStakingOrchestrationError = isStakingOrchestrationError;
39820
40699
  exports.isStellarChainKey = isStellarChainKey;
39821
40700
  exports.isStellarChainKeyType = isStellarChainKeyType;
39822
40701
  exports.isStellarWalletProviderType = isStellarWalletProviderType;
39823
- exports.isSubmitSwapTxResponse = isSubmitSwapTxResponse;
39824
- exports.isSubmitSwapTxStatusResponse = isSubmitSwapTxStatusResponse;
39825
40702
  exports.isSuiChainKey = isSuiChainKey;
39826
40703
  exports.isSuiChainKeyType = isSuiChainKeyType;
39827
40704
  exports.isSupportedBitcoinAddressType = isSupportedBitcoinAddressType;