@anywalletkit/protocol-swap2-chainflip 0.4.6 → 0.4.8

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.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  ChainflipSwap2Protocol
3
- } from "./chunk-RRCQYT67.js";
3
+ } from "./chunk-G5FZLGMT.js";
4
4
  import {
5
5
  chainflipProvider
6
- } from "./chunk-RU2NXJTL.js";
6
+ } from "./chunk-XUMGQNH4.js";
7
7
  import {
8
8
  CHAINFLIP_MODULE,
9
9
  describeChainflipActivity
10
- } from "./chunk-ZH5XKSCP.js";
10
+ } from "./chunk-C2OITXU4.js";
11
11
  import {
12
12
  CF_API_DEFAULT,
13
13
  CF_ASSETS,
@@ -18,7 +18,7 @@ import {
18
18
  chainflip,
19
19
  chainflipPins,
20
20
  chainflipVerifyQuote
21
- } from "./chunk-DGLXKJQ7.js";
21
+ } from "./chunk-IQSAPDND.js";
22
22
  import {
23
23
  decodeCfParameters,
24
24
  decodeEvmVaultCall,
package/dist/protocol.cjs CHANGED
@@ -25,6 +25,9 @@ __export(protocol_exports, {
25
25
  module.exports = __toCommonJS(protocol_exports);
26
26
  var import_single = require("@anywalletkit/protocol-swap2-core/swap2/single");
27
27
 
28
+ // src/config.ts
29
+ var import_errors = require("@anywalletkit/protocol-swap2-core/errors");
30
+
28
31
  // src/codec.ts
29
32
  var import_base = require("@scure/base");
30
33
  var import_sha2 = require("@noble/hashes/sha2.js");
@@ -205,50 +208,69 @@ var chainflip = {
205
208
  supportsChain: (chain) => !!CF_CHAINS[chain],
206
209
  supports: (from, to) => CF_SOURCE_CHAINS.has(from) && !!CF_CHAINS[to] && from !== to
207
210
  };
211
+ var notes = (0, import_errors.refusalNotes)();
212
+ var unparsed = /* @__PURE__ */ new Set();
213
+ function decodeFailure(text, requestParsed) {
214
+ unparsed.delete(text);
215
+ if (requestParsed) return notes.note(text, "upstream_unavailable");
216
+ unparsed.add(text);
217
+ if (unparsed.size > 64) unparsed.delete(unparsed.values().next().value);
218
+ return text;
219
+ }
220
+ var reasonOf = (text) => unparsed.has(text) ? void 0 : notes.reasonOf(text);
208
221
  function chainflipVerifyQuote(params, quote) {
222
+ let readingRequest = false;
209
223
  try {
210
224
  const src = cfAsset(params.sourceChain, params.sourceToken);
211
225
  const dst = cfAsset(params.destChain, params.destToken);
212
226
  const dstChain = CF_CHAINS[params.destChain];
213
227
  const vault = Object.prototype.hasOwnProperty.call(CF_VAULTS, params.sourceChain) ? CF_VAULTS[params.sourceChain] : void 0;
214
- if (!src || !dst || !dstChain || !vault || !CF_SOURCE_CHAINS.has(params.sourceChain)) return "not a vault swap this release signs";
228
+ if (!src || !dst || !dstChain || !vault || !CF_SOURCE_CHAINS.has(params.sourceChain)) return notes.note("not a vault swap this release signs", "unsupported_pair");
215
229
  if (!quote.txRequest && !quote.depositAddress) return null;
216
- if (!params.refundAddress) return "a live quote needs the refund address";
230
+ if (!params.refundAddress) return notes.note("a live quote needs the refund address", "policy_refused");
231
+ readingRequest = true;
217
232
  const amountIn = BigInt(params.amountIn);
218
233
  const dest = destAddressBytes(params.destChain, params.destAddress);
219
234
  const refund = destAddressBytes(params.sourceChain, params.refundAddress);
235
+ readingRequest = false;
220
236
  const checkParams = (p) => {
221
- if (!sameBytes(fromHex(p.refundAddress), refund)) return "the refund address is not the sender";
222
- if (minOutOf(amountIn, p.minPriceX128) < BigInt(quote.minDestAmount)) return "the minimum price does not guarantee the quoted minimum";
223
- if (p.dca !== null || p.boostFeeBps !== 0 || p.broker.feeBps !== 0 || p.affiliates.length) return "DCA, boost, a broker fee or affiliates";
237
+ if (!sameBytes(fromHex(p.refundAddress), refund)) return notes.note("the refund address is not the sender", "recipient_mismatch");
238
+ if (minOutOf(amountIn, p.minPriceX128) < BigInt(quote.minDestAmount)) return notes.note("the minimum price does not guarantee the quoted minimum", "price_check_failed");
239
+ if (p.dca !== null || p.boostFeeBps !== 0 || p.broker.feeBps !== 0 || p.affiliates.length) return notes.note("DCA, boost, a broker fee or affiliates", "policy_refused");
224
240
  return null;
225
241
  };
226
242
  if (params.sourceChain.startsWith("eip155:")) {
227
243
  const tx = quote.txRequest;
228
- if (quote.depositAddress || !tx?.data || tx.serializedTx !== void 0) return "an EVM vault swap is a vault call, not a deposit";
229
- if (!tx.to || tx.to.toLowerCase() !== vault.toLowerCase()) return `the call targets ${tx.to}, not the Vault`;
244
+ if (quote.depositAddress || !tx?.data || tx.serializedTx !== void 0) return notes.note("an EVM vault swap is a vault call, not a deposit", "policy_refused");
245
+ if (!tx.to || tx.to.toLowerCase() !== vault.toLowerCase()) return notes.note(`the call targets ${tx.to}, not the Vault`, "vault_mismatch");
230
246
  const v = decodeEvmVaultCall(tx.data);
231
- if (v.dstChain !== dstChain.id || v.dstToken !== dst.id || !sameBytes(v.dstAddress, dest)) return "the vault call names another destination";
247
+ if (v.dstChain !== dstChain.id || v.dstToken !== dst.id || !sameBytes(v.dstAddress, dest)) return notes.note("the vault call names another destination", "recipient_mismatch");
232
248
  if (src.token === null) {
233
- if (!v.native || BigInt(tx.value ?? "0") !== amountIn || tx.approvalAddress) return "the native vault call moves another amount";
249
+ if (!v.native || BigInt(tx.value ?? "0") !== amountIn || tx.approvalAddress) return notes.note("the native vault call moves another amount", "policy_refused");
234
250
  } else if (v.native || v.srcToken?.toLowerCase() !== src.token.toLowerCase() || v.amount !== amountIn || BigInt(tx.value ?? "0") !== 0n || tx.approvalAddress?.toLowerCase() !== vault.toLowerCase()) {
235
- return "the token vault call moves another token or amount";
251
+ return notes.note("the token vault call moves another token or amount", "policy_refused");
236
252
  }
237
253
  return checkParams(v.params);
238
254
  }
239
- if (quote.txRequest || quote.depositAddress !== vault || !quote.depositMemo) return "a Tron vault swap is a send to the Tron Vault with its note";
255
+ if (quote.txRequest || quote.depositAddress !== vault || !quote.depositMemo) {
256
+ return notes.note(
257
+ "a Tron vault swap is a send to the Tron Vault with its note",
258
+ quote.txRequest ? "policy_refused" : quote.depositAddress !== vault ? "vault_mismatch" : "recipient_unverifiable"
259
+ );
260
+ }
240
261
  const n = decodeTronNote(quote.depositMemo);
241
- if (n.outputAsset !== dst.id || n.destTag !== dstChain.addrTag || !sameBytes(n.destAddress, dest)) return "the note names another destination";
262
+ if (n.outputAsset !== dst.id || n.destTag !== dstChain.addrTag || !sameBytes(n.destAddress, dest)) return notes.note("the note names another destination", "recipient_mismatch");
242
263
  return checkParams(n.params);
243
264
  } catch (e) {
244
- return `the vault swap does not decode (${(e instanceof Error ? e.message : String(e)).slice(0, 120)})`;
265
+ return decodeFailure(`the vault swap does not decode (${(e instanceof Error ? e.message : String(e)).slice(0, 120)})`, !readingRequest);
245
266
  }
246
267
  }
247
268
  var chainflipPins = Object.freeze({
248
269
  callTargets: (chain) => chainflipVaultFor(chain),
249
270
  spenders: (chain) => chainflipVaultFor(chain),
250
271
  verifyQuote: chainflipVerifyQuote,
251
- verifyBuild: (quote, tx) => quote.depositMemo && tx.memoEncoding !== "hex" ? "the Tron note is not sent as hex bytes" : null,
272
+ verifyBuild: (quote, tx) => quote.depositMemo && tx.memoEncoding !== "hex" ? notes.note("the Tron note is not sent as hex bytes", "policy_refused") : null,
273
+ reasonOf,
252
274
  depositAddressRoutes: true
253
275
  });
254
276
  function chainflipVaultFor(chain) {
@@ -270,6 +292,7 @@ function describeChainflipActivity(tx, ctx) {
270
292
  }
271
293
 
272
294
  // src/provider.ts
295
+ var import_errors2 = require("@anywalletkit/protocol-swap2-core/errors");
273
296
  var import_util = require("@anywalletkit/protocol-swap2-core/util");
274
297
  var import_viem2 = require("viem");
275
298
  var QUOTE_TTL_MS = 6e4;
@@ -277,6 +300,25 @@ var RETRY_BLOCKS = 50;
277
300
  var MAX_RETRY_BLOCKS = 600;
278
301
  var ERC20 = (0, import_viem2.parseAbi)(["function transfer(address to, uint256 amount) returns (bool)"]);
279
302
  var INTENT_PREFIX = "cfvault:";
303
+ var notes2 = (0, import_errors2.refusalNotes)();
304
+ function backendRefusal(status, body, retryAfter) {
305
+ const http = (0, import_errors2.httpRefusal)(status, retryAfter);
306
+ const byStatus = http?.reason === "key_rejected" ? { reason: "upstream_unavailable", httpStatus: status } : http;
307
+ if (byStatus?.reason === "rate_limited") return byStatus;
308
+ const m = body?.message;
309
+ const said = typeof m === "string" ? backendReason(m) : void 0;
310
+ return said && said !== byStatus?.reason ? { reason: said } : byStatus;
311
+ }
312
+ function backendReason(message) {
313
+ if (/liquidity/i.test(message)) return "insufficient_liquidity";
314
+ if (/maximum (?:swap )?amount|above (?:the )?max|too (?:large|big|high)|exceeds? (?:the )?max/i.test(message)) return "amount_too_large";
315
+ if (/minimum (?:swap |deposit )?amount|below (?:the )?minimum|lower than (?:the )?(?:estimated|minimum)|too (?:small|low)/i.test(message)) return "amount_too_small";
316
+ if (/halt|paus|maintenance|disabled|suspend|not enabled/i.test(message)) return "chain_halted";
317
+ if (/rate.?limit|too many requests/i.test(message)) return "rate_limited";
318
+ if (/not supported|unsupported|unknown asset|invalid asset/i.test(message)) return "unsupported_pair";
319
+ if (/no (?:route|quote)|could not find/i.test(message)) return "no_route";
320
+ return void 0;
321
+ }
280
322
  function chainflipProvider(config = {}) {
281
323
  config = Object.freeze({ ...config });
282
324
  const base = new URL(config.apiUrl ?? CF_API_DEFAULT).toString().replace(/\/$/, "");
@@ -288,18 +330,18 @@ function chainflipProvider(config = {}) {
288
330
  await new Promise((r) => setTimeout(r, 1200));
289
331
  continue;
290
332
  }
291
- return { ok: res.ok, status: res.status, body };
333
+ return { ok: res.ok, status: res.status, body, retryAfter: res.headers?.get?.("retry-after") ?? null };
292
334
  }
293
335
  };
294
336
  const checkParams = (p, want) => {
295
- if (!sameBytes(fromHex(p.refundAddress), want.refund)) return `refund address ${p.refundAddress} is not the sender`;
296
- if (p.minPriceX128 < want.minPriceX128) return "minimum price is below the one requested";
297
- if (p.retryDurationBlocks > MAX_RETRY_BLOCKS) return `retry window ${p.retryDurationBlocks} blocks is too long`;
298
- if (p.maxOraclePriceSlippageBps !== want.oracle) return "oracle slippage guard differs from the request";
299
- if (p.dca !== null) return "unexpected DCA parameters";
300
- if (p.boostFeeBps !== 0) return "unexpected boost fee";
301
- if (p.broker.feeBps !== 0) return `broker takes ${p.broker.feeBps} bps`;
302
- if (p.affiliates.length) return "unexpected affiliates";
337
+ if (!sameBytes(fromHex(p.refundAddress), want.refund)) return notes2.note(`refund address ${p.refundAddress} is not the sender`, "recipient_mismatch");
338
+ if (p.minPriceX128 < want.minPriceX128) return notes2.note("minimum price is below the one requested", "price_check_failed");
339
+ if (p.retryDurationBlocks > MAX_RETRY_BLOCKS) return notes2.note(`retry window ${p.retryDurationBlocks} blocks is too long`, "policy_refused");
340
+ if (p.maxOraclePriceSlippageBps !== want.oracle) return notes2.note("oracle slippage guard differs from the request", "price_check_failed");
341
+ if (p.dca !== null) return notes2.note("unexpected DCA parameters", "policy_refused");
342
+ if (p.boostFeeBps !== 0) return notes2.note("unexpected boost fee", "policy_refused");
343
+ if (p.broker.feeBps !== 0) return notes2.note(`broker takes ${p.broker.feeBps} bps`, "policy_refused");
344
+ if (p.affiliates.length) return notes2.note("unexpected affiliates", "policy_refused");
303
345
  return null;
304
346
  };
305
347
  return {
@@ -315,16 +357,19 @@ function chainflipProvider(config = {}) {
315
357
  },
316
358
  async getQuote(params, signal) {
317
359
  const dry = params.dry !== false;
318
- const refuse = (reason) => {
319
- params.onRefusal?.(`chainflip: ${reason}`);
360
+ const refuse = (reason, why) => {
361
+ const info = typeof why === "string" ? { reason: why } : why;
362
+ if (info) params.onRefusal?.(`chainflip: ${reason}`, info);
363
+ else params.onRefusal?.(`chainflip: ${reason}`);
320
364
  return null;
321
365
  };
322
- if (!chainflip.supports(params.sourceChain, params.destChain)) return refuse(`no vault swap ${params.sourceChain} \u2192 ${params.destChain}`);
366
+ const unreachable = () => signal?.aborted ? void 0 : "upstream_unavailable";
367
+ if (!chainflip.supports(params.sourceChain, params.destChain)) return refuse(`no vault swap ${params.sourceChain} \u2192 ${params.destChain}`, "unsupported_pair");
323
368
  const src = cfAsset(params.sourceChain, params.sourceToken);
324
369
  const dst = cfAsset(params.destChain, params.destToken);
325
- if (!src || !dst) return refuse(`asset not listed (${params.sourceToken} on ${params.sourceChain} \u2192 ${params.destToken} on ${params.destChain})`);
370
+ if (!src || !dst) return refuse(`asset not listed (${params.sourceToken} on ${params.sourceChain} \u2192 ${params.destToken} on ${params.destChain})`, "unsupported_pair");
326
371
  const amountIn = BigInt(params.amountIn);
327
- if (amountIn < src.minDeposit) return refuse(`below the minimum deposit (${src.minDeposit} ${src.asset})`);
372
+ if (amountIn < src.minDeposit) return refuse(`below the minimum deposit (${src.minDeposit} ${src.asset})`, "amount_too_small");
328
373
  let destBytes;
329
374
  try {
330
375
  destBytes = destAddressBytes(params.destChain, params.destAddress);
@@ -333,21 +378,23 @@ function chainflipProvider(config = {}) {
333
378
  }
334
379
  const qs = new URLSearchParams({ amount: params.amountIn, srcChain: src.chain, srcAsset: src.asset, destChain: dst.chain, destAsset: dst.asset, isVaultSwap: "true", isOnChain: "false", dcaV2Enabled: "false" });
335
380
  let q;
381
+ let answered;
336
382
  try {
337
383
  const r = await call(`/v2/quote?${qs.toString()}`, {}, signal);
338
- if (!r.ok) return refuse(`quote ${r.status}: ${String(r.body?.message ?? "").slice(0, 160)}`);
384
+ if (!r.ok) return refuse(`quote ${r.status}: ${String(r.body?.message ?? "").slice(0, 160)}`, backendRefusal(r.status, r.body, r.retryAfter));
385
+ answered = r.body;
339
386
  q = r.body?.find((x) => x.type === "REGULAR") ?? r.body?.[0];
340
387
  } catch (e) {
341
- return refuse(`quote failed: ${e.message.slice(0, 160)}`);
388
+ return refuse(`quote failed: ${e.message.slice(0, 160)}`, unreachable());
342
389
  }
343
- if (!q?.egressAmount || !/^\d+$/.test(q.egressAmount)) return refuse("quote without an egress amount");
344
- if (q.type && q.type !== "REGULAR") return refuse(`${q.type} quotes are not supported`);
390
+ if (!q?.egressAmount || !/^\d+$/.test(q.egressAmount)) return refuse("quote without an egress amount", Array.isArray(answered) && answered.length === 0 ? "no_route" : unreachable());
391
+ if (q.type && q.type !== "REGULAR") return refuse(`${q.type} quotes are not supported`, "unsupported_pair");
345
392
  const expected = BigInt(q.egressAmount);
346
393
  const slipBps = params.slippageBps ?? Math.round((q.recommendedSlippageTolerancePercent ?? 1) * 100);
347
394
  const byTolerance = expected * BigInt(1e4 - Math.min(9999, Math.max(0, slipBps))) / 10000n;
348
395
  const callerFloor = params.minAmountOut !== void 0 ? BigInt(params.minAmountOut) : void 0;
349
396
  const minOut = callerFloor === void 0 ? byTolerance : params.slippageBps !== void 0 && byTolerance > callerFloor ? byTolerance : callerFloor;
350
- if (minOut > expected) return refuse(`floor ${minOut} above the expected output ${expected}`);
397
+ if (minOut > expected) return refuse(`floor ${minOut} above the expected output ${expected}`, "price_check_failed");
351
398
  const minPriceX128 = minPriceX128For(amountIn, minOut);
352
399
  const guaranteed = minOutOf(amountIn, minPriceX128);
353
400
  const oracle = config.maxOraclePriceSlippageBps !== void 0 ? config.maxOraclePriceSlippageBps : q.recommendedLivePriceSlippageTolerancePercent !== void 0 ? Math.round(q.recommendedLivePriceSlippageTolerancePercent * 100) : null;
@@ -365,7 +412,7 @@ function chainflipProvider(config = {}) {
365
412
  validUntilMs: Date.now() + QUOTE_TTL_MS
366
413
  };
367
414
  if (dry) return { ...common, intentId: `dry:${crypto.randomUUID()}`, depositAddress: "", depositMemo: null };
368
- if (!params.refundAddress) return refuse("live quote without a refund address");
415
+ if (!params.refundAddress) return refuse("live quote without a refund address", "policy_refused");
369
416
  let refund;
370
417
  try {
371
418
  refund = destAddressBytes(params.sourceChain, params.refundAddress);
@@ -384,26 +431,26 @@ function chainflipProvider(config = {}) {
384
431
  let enc;
385
432
  try {
386
433
  const r = await call("/api/encodeVaultSwapData", { method: "POST", body: JSON.stringify(body) }, signal);
387
- if (!r.ok) return refuse(`encode ${r.status}: ${String(r.body?.message ?? "").slice(0, 160)}`);
434
+ if (!r.ok) return refuse(`encode ${r.status}: ${String(r.body?.message ?? "").slice(0, 160)}`, backendRefusal(r.status, r.body, r.retryAfter));
388
435
  enc = r.body ?? {};
389
436
  } catch (e) {
390
- return refuse(`encode failed: ${e.message.slice(0, 160)}`);
437
+ return refuse(`encode failed: ${e.message.slice(0, 160)}`, unreachable());
391
438
  }
392
439
  const want = { refund, minPriceX128, oracle };
393
440
  const vault = CF_VAULTS[params.sourceChain];
394
441
  const dstChain = CF_CHAINS[params.destChain];
395
442
  try {
396
443
  if (params.sourceChain.startsWith("eip155:")) {
397
- if (!enc.to || enc.to.toLowerCase() !== vault.toLowerCase()) return refuse(`encoder targets ${enc.to}, not the ${src.chain} Vault`);
444
+ if (!enc.to || enc.to.toLowerCase() !== vault.toLowerCase()) return refuse(`encoder targets ${enc.to}, not the ${src.chain} Vault`, !enc.to ? unreachable() : "vault_mismatch");
398
445
  const v = decodeEvmVaultCall(enc.calldata ?? "0x");
399
- if (v.dstChain !== dstChain.id || v.dstToken !== dst.id || !sameBytes(v.dstAddress, destBytes)) return refuse("encoded destination differs from the request");
446
+ if (v.dstChain !== dstChain.id || v.dstToken !== dst.id || !sameBytes(v.dstAddress, destBytes)) return refuse("encoded destination differs from the request", "recipient_mismatch");
400
447
  if (src.token === null) {
401
- if (!v.native || BigInt(enc.value ?? "0") !== amountIn) return refuse("native vault call with a different amount");
448
+ if (!v.native || BigInt(enc.value ?? "0") !== amountIn) return refuse("native vault call with a different amount", "policy_refused");
402
449
  } else if (v.native || v.srcToken?.toLowerCase() !== src.token.toLowerCase() || v.amount !== amountIn || BigInt(enc.value ?? "0") !== 0n) {
403
- return refuse("token vault call with a different token or amount");
450
+ return refuse("token vault call with a different token or amount", "policy_refused");
404
451
  }
405
452
  const bad2 = checkParams(v.params, want);
406
- if (bad2) return refuse(bad2);
453
+ if (bad2) return refuse(bad2, notes2.reasonOf(bad2));
407
454
  return {
408
455
  ...common,
409
456
  intentId: `${INTENT_PREFIX}${params.sourceChain}`,
@@ -413,17 +460,18 @@ function chainflipProvider(config = {}) {
413
460
  providerData: { kind: "evm", srcAsset: String(src.id), dstAsset: String(dst.id) }
414
461
  };
415
462
  }
416
- if (enc.to !== vault) return refuse(`encoder targets ${enc.to}, not the Tron Vault`);
417
- if (!enc.note) return refuse("Tron vault swap without a note");
463
+ if (enc.to !== vault) return refuse(`encoder targets ${enc.to}, not the Tron Vault`, !enc.to ? unreachable() : "vault_mismatch");
464
+ if (!enc.note) return refuse("Tron vault swap without a note", "recipient_unverifiable");
418
465
  if (src.token !== null) {
419
- if (enc.sourceTokenAddress !== src.token) return refuse(`source token ${enc.sourceTokenAddress} is not ${src.asset}`);
466
+ if (enc.sourceTokenAddress !== src.token) return refuse(`source token ${enc.sourceTokenAddress} is not ${src.asset}`, "policy_refused");
420
467
  const t = (0, import_viem2.decodeFunctionData)({ abi: ERC20, data: enc.calldata ?? "0x" });
421
- if (!sameBytes(fromHex(String(t.args[0])), tronToBytes(vault)) || t.args[1] !== amountIn) return refuse("TRC-20 transfer is not the amount to the Vault");
468
+ const toVault = sameBytes(fromHex(String(t.args[0])), tronToBytes(vault));
469
+ if (!toVault || t.args[1] !== amountIn) return refuse("TRC-20 transfer is not the amount to the Vault", toVault ? "policy_refused" : "vault_mismatch");
422
470
  }
423
471
  const n = decodeTronNote(enc.note);
424
- if (n.outputAsset !== dst.id || n.destTag !== dstChain.addrTag || !sameBytes(n.destAddress, destBytes)) return refuse("note names another destination");
472
+ if (n.outputAsset !== dst.id || n.destTag !== dstChain.addrTag || !sameBytes(n.destAddress, destBytes)) return refuse("note names another destination", "recipient_mismatch");
425
473
  const bad = checkParams(n.params, want);
426
- if (bad) return refuse(bad);
474
+ if (bad) return refuse(bad, notes2.reasonOf(bad));
427
475
  return {
428
476
  ...common,
429
477
  intentId: `${INTENT_PREFIX}${params.sourceChain}`,
@@ -432,20 +480,20 @@ function chainflipProvider(config = {}) {
432
480
  providerData: { kind: "tron", memoEncoding: "hex", srcAsset: String(src.id), dstAsset: String(dst.id) }
433
481
  };
434
482
  } catch (e) {
435
- return refuse(`encoder output does not decode: ${e.message.slice(0, 160)}`);
483
+ return refuse(`encoder output does not decode: ${e.message.slice(0, 160)}`, "upstream_unavailable");
436
484
  }
437
485
  },
438
486
  async buildDeposit(quote) {
439
487
  if (quote.providerData?.kind === "evm") {
440
488
  const tx = quote.txRequest;
441
- if (!tx?.to || !tx.data) throw new Error("chainflip: quote has no vault call");
489
+ if (!tx?.to || !tx.data) throw (0, import_errors2.refusal)("policy_refused", "chainflip: quote has no vault call");
442
490
  return { chain: "", to: tx.to, value: tx.value ?? "0", data: tx.data, ...tx.chainId !== void 0 ? { chainId: tx.chainId } : {}, ...tx.approvalAddress ? { approvalAddress: tx.approvalAddress } : {} };
443
491
  }
444
492
  if (quote.providerData?.kind === "tron") {
445
- if (!quote.depositAddress || !quote.depositMemo) throw new Error("chainflip: Tron quote without vault or note");
493
+ if (!quote.depositAddress || !quote.depositMemo) throw (0, import_errors2.refusal)("policy_refused", "chainflip: Tron quote without vault or note");
446
494
  return { chain: "", to: quote.depositAddress, value: quote.sourceAmount, memo: quote.depositMemo, memoEncoding: "hex" };
447
495
  }
448
- throw new Error("chainflip: not a live vault-swap quote");
496
+ throw (0, import_errors2.refusal)("policy_refused", "chainflip: not a live vault-swap quote");
449
497
  },
450
498
  async trackIntent(ref) {
451
499
  if (!ref.sourceTxHash) return { state: "pending" };