@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.
package/dist/index.cjs CHANGED
@@ -266,7 +266,8 @@ function remoteProvider(opts) {
266
266
  },
267
267
  ...opts.supportsChain ? { supportsChain: opts.supportsChain.bind(opts) } : {},
268
268
  ...opts.supportsChainPair ? { supportsChainPair: opts.supportsChainPair.bind(opts) } : {},
269
- ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {}
269
+ ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {},
270
+ ...opts.claimTargets ? { claimTargets: opts.claimTargets } : {}
270
271
  };
271
272
  }
272
273
 
@@ -297,12 +298,14 @@ var inFlightKey = (fromChain, o) => JSON.stringify(
297
298
  [fromChain, o.fromToken, o.toToken, o.toChain, o.recipient, o.refundAddress, o.fromTokenAmount, o.toTokenAmount, o.minAmountOut, o.slippage],
298
299
  (_k, v) => typeof v === "bigint" ? v.toString() : v
299
300
  );
301
+ 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;
300
302
  function maybeSent(error) {
301
- for (let e = error, i = 0; e && typeof e === "object" && i < 5; e = e.cause, i++) {
302
- if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
303
- }
304
- const text = error instanceof Error ? error.message : String(error);
305
- return /socket hang up|ECONNRESET|ETIMEDOUT|EPIPE|timed? ?out|request aborted|connection (?:closed|reset)/i.test(text) ? {} : null;
303
+ const chain = [];
304
+ for (let e = error, i = 0; e && typeof e === "object" && i < 6; e = e.cause, i++) chain.push(e);
305
+ for (const e of chain) if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
306
+ if (chain.some((e) => e.answered === true)) return null;
307
+ const texts = chain.length ? chain.map((e) => [e.message, e.code, e.name].filter((x) => typeof x === "string").join(" ")) : [String(error)];
308
+ return texts.some((t) => LOST_ANSWER.test(t)) ? {} : null;
306
309
  }
307
310
  var NATIVE_DECIMALS = Object.freeze({
308
311
  "eip155:5042": 18
@@ -386,22 +389,24 @@ function reviewedIdentity(plan) {
386
389
  return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
387
390
  }
388
391
  var SWAP2_ID_PREFIX = "awk1";
389
- function encodeId(provider, intentId, sourceTxHash) {
392
+ function encodeId(provider, intentId, sourceTxHash, destTxHash) {
390
393
  const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
391
- return `${SWAP2_ID_PREFIX}.${[provider, intentId, sourceTxHash].filter((v) => v != null).map(enc).join(".")}`;
394
+ const parts = [provider, intentId, ...sourceTxHash != null ? [sourceTxHash, ...destTxHash != null ? [destTxHash] : []] : []];
395
+ return `${SWAP2_ID_PREFIX}.${parts.map(enc).join(".")}`;
392
396
  }
393
397
  function decodeId(id) {
394
398
  try {
395
399
  if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) throw new Error("unsupported id version");
396
400
  const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
397
- if (parts.length < 2 || parts.length > 3 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
401
+ if (parts.length < 2 || parts.length > 4 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
398
402
  const provider = decodeURIComponent(parts[0]);
399
403
  const intentId = decodeURIComponent(parts[1]);
400
404
  const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
401
- if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash) {
405
+ const destTxHash = parts[3] ? decodeURIComponent(parts[3]) : void 0;
406
+ if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash || parts[3] !== void 0 && !destTxHash) {
402
407
  throw new Error("invalid id contents");
403
408
  }
404
- return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {} };
409
+ return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {} };
405
410
  } catch {
406
411
  throw new Swap2ValidationError("swap2: malformed swap2 status id");
407
412
  }
@@ -453,13 +458,19 @@ function toCaip2(chain, fallback) {
453
458
  }
454
459
  return swap2ChainId(chain);
455
460
  }
461
+ function nonEmptyRef(ref, side) {
462
+ if (typeof ref !== "string" || ref.trim() === "") throw new Swap2ValidationError(`swap2: ${side} token must not be empty (pass the symbol or the contract address)`);
463
+ return ref;
464
+ }
456
465
  function toSymbol(caip2, ref) {
466
+ nonEmptyRef(ref, "source");
457
467
  const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) : void 0;
458
468
  if (byToken) return byToken.symbol;
459
469
  if ((0, import_swap2_assets2.ccAsset)(caip2, ref)) return ref;
460
470
  return ref;
461
471
  }
462
472
  function toDestAddress(caip2, ref, allowUnlisted = false) {
473
+ nonEmptyRef(ref, "destination");
463
474
  if (ref.startsWith("0x")) return ref;
464
475
  const asset = (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets2.ccAsset)(caip2, ref);
465
476
  if (!asset) {
@@ -827,7 +838,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
827
838
  throw new Error("swap2: provider build does not match the validated quote transaction");
828
839
  }
829
840
  } else if (!unsigned.bodyBoc && !unsigned.typedData) {
830
- if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo) {
841
+ if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo || unsigned.data !== void 0 || unsigned.approvalAddress !== void 0 || unsigned.serializedTx !== void 0) {
831
842
  throw new Error("swap2: provider build does not match the validated quote deposit");
832
843
  }
833
844
  }
@@ -1182,10 +1193,10 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1182
1193
  }
1183
1194
  /** Status of a swap by the id `swap2()` returned, asked from the provider that executed it. */
1184
1195
  async getSwap2Status(id, _options) {
1185
- const { provider: name, intentId, sourceTxHash } = decodeId(id);
1196
+ const { provider: name, intentId, sourceTxHash, destTxHash } = decodeId(id);
1186
1197
  const provider = this.providerFor(name);
1187
1198
  if (!provider) throw new Error(`swap2: no swap2 found for id '${id}'`);
1188
- return mapStatus(await provider.trackIntent(sourceTxHash == null ? { intentId } : { intentId, sourceTxHash }));
1199
+ return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash } : {} }));
1189
1200
  }
1190
1201
  /**
1191
1202
  * Two-phase routes (CCTP-style burn-and-mint): once `getSwap2Status` reports
@@ -1231,6 +1242,17 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1231
1242
  if (unsigned.chainId !== void 0 && String(unsigned.chainId) !== expectedId) {
1232
1243
  throw new Error(`swap2: claim chainId ${unsigned.chainId} does not match ${unsigned.chain}`);
1233
1244
  }
1245
+ if (typeof provider.claimTargets !== "function") {
1246
+ throw new Swap2UnsupportedOperationError(`swap2: provider '${name}' declares no claimTargets \u2014 a claim may only call contracts the provider names for its chain`);
1247
+ }
1248
+ const targets = provider.claimTargets(unsigned.chain).map((a) => a.toLowerCase());
1249
+ if (!targets.includes(unsigned.to.toLowerCase())) {
1250
+ throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
1251
+ }
1252
+ const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
1253
+ if (claimCall.transfer || claimCall.approval) {
1254
+ throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
1255
+ }
1234
1256
  const actual = await readAccountChain(signer);
1235
1257
  if (actual == null) throw new Error(`swap2: destination account chain identity is unavailable for ${unsigned.chain}`);
1236
1258
  const actualText = typeof actual === "string" ? actual : String(actual);
@@ -1246,7 +1268,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1246
1268
  });
1247
1269
  if (!sent.hash) throw new Swap2ExecutionError("swap2: account did not return a claim transaction hash");
1248
1270
  this.emitPhase("broadcast", { hash: sent.hash });
1249
- return { hash: sent.hash, chain: unsigned.chain, id };
1271
+ return { hash: sent.hash, chain: unsigned.chain, id: encodeId(name, intentId, sourceTxHash, sent.hash) };
1250
1272
  }
1251
1273
  /** The inherited bridge() has no floor field: quote first, then bind execution
1252
1274
  * to that quote's minimum so the inherited convenience API stays safe. */
@@ -1306,7 +1328,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1306
1328
  if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
1307
1329
  for (const a of c.assets) {
1308
1330
  out.push({
1309
- token: a.assetId,
1331
+ // What quoteSwap2 takes back: the contract, or the symbol of the native coin — never the
1332
+ // provider-specific assetId, which is "" for most catalog entries (round 3, M25).
1333
+ token: a.contract ?? a.symbol,
1310
1334
  chain: c.caip2,
1311
1335
  symbol: a.symbol,
1312
1336
  decimals: a.decimals,