@anywalletkit/protocol-swap2-core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,12 +111,14 @@ var inFlightKey = (fromChain, o) => JSON.stringify(
111
111
  [fromChain, o.fromToken, o.toToken, o.toChain, o.recipient, o.refundAddress, o.fromTokenAmount, o.toTokenAmount, o.minAmountOut, o.slippage],
112
112
  (_k, v) => typeof v === "bigint" ? v.toString() : v
113
113
  );
114
+ var LOST_ANSWER = /socket hang up|ECONNRESET|ECONNABORTED|ETIMEDOUT|EPIPE|ENETUNREACH|EHOSTUNREACH|EAI_AGAIN|UND_ERR_[A-Z_]+|timed? ?out|TimeoutError|request aborted|AbortError|connection (?:closed|reset)|fetch failed|failed to fetch|network request failed|other side closed/i;
114
115
  function maybeSent(error) {
115
- for (let e = error, i = 0; e && typeof e === "object" && i < 5; e = e.cause, i++) {
116
- if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
117
- }
118
- const text = error instanceof Error ? error.message : String(error);
119
- return /socket hang up|ECONNRESET|ETIMEDOUT|EPIPE|timed? ?out|request aborted|connection (?:closed|reset)/i.test(text) ? {} : null;
116
+ const chain = [];
117
+ for (let e = error, i = 0; e && typeof e === "object" && i < 6; e = e.cause, i++) chain.push(e);
118
+ for (const e of chain) if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
119
+ if (chain.some((e) => e.answered === true)) return null;
120
+ const texts = chain.length ? chain.map((e) => [e.message, e.code, e.name].filter((x) => typeof x === "string").join(" ")) : [String(error)];
121
+ return texts.some((t) => LOST_ANSWER.test(t)) ? {} : null;
120
122
  }
121
123
  var NATIVE_DECIMALS = Object.freeze({
122
124
  "eip155:5042": 18
@@ -200,22 +202,24 @@ function reviewedIdentity(plan) {
200
202
  return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
201
203
  }
202
204
  var SWAP2_ID_PREFIX = "awk1";
203
- function encodeId(provider, intentId, sourceTxHash) {
205
+ function encodeId(provider, intentId, sourceTxHash, destTxHash) {
204
206
  const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
205
- return `${SWAP2_ID_PREFIX}.${[provider, intentId, sourceTxHash].filter((v) => v != null).map(enc).join(".")}`;
207
+ const parts = [provider, intentId, ...sourceTxHash != null ? [sourceTxHash, ...destTxHash != null ? [destTxHash] : []] : []];
208
+ return `${SWAP2_ID_PREFIX}.${parts.map(enc).join(".")}`;
206
209
  }
207
210
  function decodeId(id) {
208
211
  try {
209
212
  if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) throw new Error("unsupported id version");
210
213
  const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
211
- if (parts.length < 2 || parts.length > 3 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
214
+ if (parts.length < 2 || parts.length > 4 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
212
215
  const provider = decodeURIComponent(parts[0]);
213
216
  const intentId = decodeURIComponent(parts[1]);
214
217
  const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
215
- if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash) {
218
+ const destTxHash = parts[3] ? decodeURIComponent(parts[3]) : void 0;
219
+ if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash || parts[3] !== void 0 && !destTxHash) {
216
220
  throw new Error("invalid id contents");
217
221
  }
218
- return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {} };
222
+ return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {} };
219
223
  } catch {
220
224
  throw new Swap2ValidationError("swap2: malformed swap2 status id");
221
225
  }
@@ -267,13 +271,19 @@ function toCaip2(chain, fallback) {
267
271
  }
268
272
  return swap2ChainId(chain);
269
273
  }
274
+ function nonEmptyRef(ref, side) {
275
+ if (typeof ref !== "string" || ref.trim() === "") throw new Swap2ValidationError(`swap2: ${side} token must not be empty (pass the symbol or the contract address)`);
276
+ return ref;
277
+ }
270
278
  function toSymbol(caip2, ref) {
279
+ nonEmptyRef(ref, "source");
271
280
  const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets.ccAssetByToken)(caip2, ref) : void 0;
272
281
  if (byToken) return byToken.symbol;
273
282
  if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
274
283
  return ref;
275
284
  }
276
285
  function toDestAddress(caip2, ref, allowUnlisted = false) {
286
+ nonEmptyRef(ref, "destination");
277
287
  if (ref.startsWith("0x")) return ref;
278
288
  const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
279
289
  if (!asset) {
@@ -641,7 +651,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
641
651
  throw new Error("swap2: provider build does not match the validated quote transaction");
642
652
  }
643
653
  } else if (!unsigned.bodyBoc && !unsigned.typedData) {
644
- if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo) {
654
+ if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo || unsigned.data !== void 0 || unsigned.approvalAddress !== void 0 || unsigned.serializedTx !== void 0) {
645
655
  throw new Error("swap2: provider build does not match the validated quote deposit");
646
656
  }
647
657
  }
@@ -996,10 +1006,10 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
996
1006
  }
997
1007
  /** Status of a swap by the id `swap2()` returned, asked from the provider that executed it. */
998
1008
  async getSwap2Status(id, _options) {
999
- const { provider: name, intentId, sourceTxHash } = decodeId(id);
1009
+ const { provider: name, intentId, sourceTxHash, destTxHash } = decodeId(id);
1000
1010
  const provider = this.providerFor(name);
1001
1011
  if (!provider) throw new Error(`swap2: no swap2 found for id '${id}'`);
1002
- return mapStatus(await provider.trackIntent(sourceTxHash == null ? { intentId } : { intentId, sourceTxHash }));
1012
+ return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash } : {} }));
1003
1013
  }
1004
1014
  /**
1005
1015
  * Two-phase routes (CCTP-style burn-and-mint): once `getSwap2Status` reports
@@ -1045,6 +1055,17 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1045
1055
  if (unsigned.chainId !== void 0 && String(unsigned.chainId) !== expectedId) {
1046
1056
  throw new Error(`swap2: claim chainId ${unsigned.chainId} does not match ${unsigned.chain}`);
1047
1057
  }
1058
+ if (typeof provider.claimTargets !== "function") {
1059
+ throw new Swap2UnsupportedOperationError(`swap2: provider '${name}' declares no claimTargets \u2014 a claim may only call contracts the provider names for its chain`);
1060
+ }
1061
+ const targets = provider.claimTargets(unsigned.chain).map((a) => a.toLowerCase());
1062
+ if (!targets.includes(unsigned.to.toLowerCase())) {
1063
+ throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
1064
+ }
1065
+ const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
1066
+ if (claimCall.transfer || claimCall.approval) {
1067
+ throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
1068
+ }
1048
1069
  const actual = await readAccountChain(signer);
1049
1070
  if (actual == null) throw new Error(`swap2: destination account chain identity is unavailable for ${unsigned.chain}`);
1050
1071
  const actualText = typeof actual === "string" ? actual : String(actual);
@@ -1060,7 +1081,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1060
1081
  });
1061
1082
  if (!sent.hash) throw new Swap2ExecutionError("swap2: account did not return a claim transaction hash");
1062
1083
  this.emitPhase("broadcast", { hash: sent.hash });
1063
- return { hash: sent.hash, chain: unsigned.chain, id };
1084
+ return { hash: sent.hash, chain: unsigned.chain, id: encodeId(name, intentId, sourceTxHash, sent.hash) };
1064
1085
  }
1065
1086
  /** The inherited bridge() has no floor field: quote first, then bind execution
1066
1087
  * to that quote's minimum so the inherited convenience API stays safe. */
@@ -1120,7 +1141,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1120
1141
  if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
1121
1142
  for (const a of c.assets) {
1122
1143
  out.push({
1123
- token: a.assetId,
1144
+ // What quoteSwap2 takes back: the contract, or the symbol of the native coin — never the
1145
+ // provider-specific assetId, which is "" for most catalog entries (round 3, M25).
1146
+ token: a.contract ?? a.symbol,
1124
1147
  chain: c.caip2,
1125
1148
  symbol: a.symbol,
1126
1149
  decimals: a.decimals,