@anywalletkit/protocol-swap2-core 0.4.1 → 0.5.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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/dist/{chunk-4H2JVYXT.js → chunk-3Y6HV2OL.js} +2 -2
  3. package/dist/{chunk-Y3VUEWYI.js → chunk-IC3MZIMB.js} +68 -13
  4. package/dist/chunk-IC3MZIMB.js.map +1 -0
  5. package/dist/{chunk-SKV5BVXK.js → chunk-MNJTMSFN.js} +8 -2
  6. package/dist/chunk-MNJTMSFN.js.map +1 -0
  7. package/dist/{chunk-GDRKGFLH.js → chunk-OWO27YBK.js} +2 -2
  8. package/dist/index.cjs +79 -21
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +7 -5
  13. package/dist/remote.cjs +1 -0
  14. package/dist/remote.cjs.map +1 -1
  15. package/dist/remote.js +2 -2
  16. package/dist/server.cjs +8 -4
  17. package/dist/server.cjs.map +1 -1
  18. package/dist/server.d.cts +7 -0
  19. package/dist/server.d.ts +7 -0
  20. package/dist/server.js +8 -5
  21. package/dist/server.js.map +1 -1
  22. package/dist/swap2/base.cjs +63 -11
  23. package/dist/swap2/base.cjs.map +1 -1
  24. package/dist/swap2/base.d.cts +21 -1
  25. package/dist/swap2/base.d.ts +21 -1
  26. package/dist/swap2/base.js +1 -1
  27. package/dist/swap2/single.cjs +63 -11
  28. package/dist/swap2/single.cjs.map +1 -1
  29. package/dist/swap2/single.d.cts +1 -0
  30. package/dist/swap2/single.d.ts +1 -0
  31. package/dist/swap2/single.js +2 -2
  32. package/dist/types.cjs.map +1 -1
  33. package/dist/types.d.cts +6 -0
  34. package/dist/types.d.ts +6 -0
  35. package/dist/util.cjs +6 -0
  36. package/dist/util.cjs.map +1 -1
  37. package/dist/util.d.cts +12 -1
  38. package/dist/util.d.ts +12 -1
  39. package/dist/util.js +3 -1
  40. package/package.json +6 -6
  41. package/dist/chunk-SKV5BVXK.js.map +0 -1
  42. package/dist/chunk-Y3VUEWYI.js.map +0 -1
  43. /package/dist/{chunk-4H2JVYXT.js.map → chunk-3Y6HV2OL.js.map} +0 -0
  44. /package/dist/{chunk-GDRKGFLH.js.map → chunk-OWO27YBK.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { Swap2Protocol, Swap2ProtocolConfig, Swap2Options, Swap2Fee, Swap2Quote, Swap2Result, Swap2StatusOptions, Swap2StatusResult, BridgeOptions, BridgeResult, SwapOptions, SwapResult, Swap2SupportedChain, Swap2SupportedTokensOptions, Swap2SupportedToken } from '@anywalletkit/core';
2
+ import { CcAsset } from '@anywalletkit/swap2-assets';
2
3
  import { QuoteParams, Quote, CrossChainProvider } from '../types.cjs';
3
4
 
4
5
  /**
@@ -89,6 +90,15 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
89
90
  * called again with `requoted: true`, and throwing then revokes the allowance.
90
91
  */
91
92
  reviewExecution?: (plan: Swap2ExecutionPlan) => Promise<void> | void;
93
+ /**
94
+ * Accept tokens the swap catalog does not list, named by their contract (EVM) or mint (Solana):
95
+ * tokens the user added at their own risk. Off by default. The source token's decimals are read
96
+ * with the account's `getTokenInfo` (a `fromTokenDecimals` that disagrees is refused); an account
97
+ * without it must be given `fromTokenDecimals`. Only providers that trade any token (LI.FI,
98
+ * Uniswap, Balancer, Fynd, Jupiter) quote them, and they have no USD value: the router cannot
99
+ * judge their price, so show the user the amounts.
100
+ */
101
+ allowUnlistedTokens?: boolean;
92
102
  }
93
103
  /** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
94
104
  interface Swap2ExecutionPlan {
@@ -202,8 +212,18 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
202
212
  protected protocolEnabled(): boolean;
203
213
  /** Pair-level capability used when discovery specifies both endpoints. */
204
214
  protected pairSupported(fromChain: string, toChain: string): boolean;
215
+ private unlistedAllowed;
216
+ /** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
217
+ protected sourceAsset(options: Swap2Options): CcAsset | undefined;
218
+ private readonly unlistedDecimals;
219
+ /**
220
+ * Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
221
+ * read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
222
+ * disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
223
+ */
224
+ protected prepareOptions(options: Swap2Options): Promise<Swap2Options>;
205
225
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
206
- protected toParams(options: Swap2Options, dry: boolean): QuoteParams;
226
+ protected toParams(given: Swap2Options, dry: boolean): QuoteParams;
207
227
  private assertAccountChain;
208
228
  protected assertFeeConfig(config?: Swap2ProtocolConfig): void;
209
229
  protected assertFeeCaps(fees: readonly Swap2Fee[] | undefined, sourceAmount: bigint, sourceToken: string, config?: Swap2ProtocolConfig): void;
@@ -1,4 +1,5 @@
1
1
  import { Swap2Protocol, Swap2ProtocolConfig, Swap2Options, Swap2Fee, Swap2Quote, Swap2Result, Swap2StatusOptions, Swap2StatusResult, BridgeOptions, BridgeResult, SwapOptions, SwapResult, Swap2SupportedChain, Swap2SupportedTokensOptions, Swap2SupportedToken } from '@anywalletkit/core';
2
+ import { CcAsset } from '@anywalletkit/swap2-assets';
2
3
  import { QuoteParams, Quote, CrossChainProvider } from '../types.js';
3
4
 
4
5
  /**
@@ -89,6 +90,15 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
89
90
  * called again with `requoted: true`, and throwing then revokes the allowance.
90
91
  */
91
92
  reviewExecution?: (plan: Swap2ExecutionPlan) => Promise<void> | void;
93
+ /**
94
+ * Accept tokens the swap catalog does not list, named by their contract (EVM) or mint (Solana):
95
+ * tokens the user added at their own risk. Off by default. The source token's decimals are read
96
+ * with the account's `getTokenInfo` (a `fromTokenDecimals` that disagrees is refused); an account
97
+ * without it must be given `fromTokenDecimals`. Only providers that trade any token (LI.FI,
98
+ * Uniswap, Balancer, Fynd, Jupiter) quote them, and they have no USD value: the router cannot
99
+ * judge their price, so show the user the amounts.
100
+ */
101
+ allowUnlistedTokens?: boolean;
92
102
  }
93
103
  /** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
94
104
  interface Swap2ExecutionPlan {
@@ -202,8 +212,18 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
202
212
  protected protocolEnabled(): boolean;
203
213
  /** Pair-level capability used when discovery specifies both endpoints. */
204
214
  protected pairSupported(fromChain: string, toChain: string): boolean;
215
+ private unlistedAllowed;
216
+ /** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
217
+ protected sourceAsset(options: Swap2Options): CcAsset | undefined;
218
+ private readonly unlistedDecimals;
219
+ /**
220
+ * Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
221
+ * read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
222
+ * disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
223
+ */
224
+ protected prepareOptions(options: Swap2Options): Promise<Swap2Options>;
205
225
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
206
- protected toParams(options: Swap2Options, dry: boolean): QuoteParams;
226
+ protected toParams(given: Swap2Options, dry: boolean): QuoteParams;
207
227
  private assertAccountChain;
208
228
  protected assertFeeConfig(config?: Swap2ProtocolConfig): void;
209
229
  protected assertFeeCaps(fees: readonly Swap2Fee[] | undefined, sourceAmount: bigint, sourceToken: string, config?: Swap2ProtocolConfig): void;
@@ -7,7 +7,7 @@ import {
7
7
  nativeUnits,
8
8
  normalizeTypedDomain,
9
9
  swap2ChainId
10
- } from "../chunk-Y3VUEWYI.js";
10
+ } from "../chunk-IC3MZIMB.js";
11
11
  import "../chunk-IVR3SREQ.js";
12
12
  export {
13
13
  BaseSwap2Protocol,
@@ -273,10 +273,11 @@ function toSymbol(caip2, ref) {
273
273
  if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
274
274
  return ref;
275
275
  }
276
- function toDestAddress(caip2, ref) {
276
+ function toDestAddress(caip2, ref, allowUnlisted = false) {
277
277
  if (ref.startsWith("0x")) return ref;
278
278
  const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
279
279
  if (!asset) {
280
+ if (allowUnlisted && (0, import_swap2_assets.isTokenAddress)(caip2, ref)) return ref;
280
281
  throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
281
282
  }
282
283
  return asset.contract ?? ZERO;
@@ -358,6 +359,15 @@ function mapStatus(s) {
358
359
  const status = s.state === "completed" ? "completed" : s.state === "action-required" ? "action-required" : s.state === "refund-pending" ? "refund-pending" : s.state === "partial" ? "partial" : s.state === "failed" ? "failed" : s.state === "refunded" ? "refunded" : s.state === "cancelled" ? "cancelled" : s.state === "expired" ? "expired" : "pending";
359
360
  return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
360
361
  }
362
+ function blankToUndefined(v) {
363
+ return typeof v === "string" && v.trim() === "" ? void 0 : v;
364
+ }
365
+ function withoutBlankAddresses(o) {
366
+ const { recipient, refundAddress, ...rest } = o;
367
+ const r = blankToUndefined(recipient);
368
+ const f = blankToUndefined(refundAddress);
369
+ return { ...rest, ...r !== void 0 ? { recipient: r } : {}, ...f !== void 0 ? { refundAddress: f } : {} };
370
+ }
361
371
  var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
362
372
  fromChain;
363
373
  constructor(account, config) {
@@ -390,19 +400,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
390
400
  pairSupported(fromChain, toChain) {
391
401
  return this.chainSupported(fromChain) && this.chainSupported(toChain);
392
402
  }
403
+ unlistedAllowed() {
404
+ return this._config.allowUnlistedTokens === true;
405
+ }
406
+ /** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
407
+ sourceAsset(options) {
408
+ const decimals = options.fromTokenDecimals;
409
+ return (0, import_swap2_assets.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
410
+ allowUnlisted: this.unlistedAllowed(),
411
+ ...decimals !== void 0 ? { decimals } : {}
412
+ });
413
+ }
414
+ unlistedDecimals = /* @__PURE__ */ new Map();
415
+ /**
416
+ * Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
417
+ * read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
418
+ * disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
419
+ */
420
+ async prepareOptions(options) {
421
+ if (!this.unlistedAllowed()) return options;
422
+ const ref = toSymbol(this.fromChain, options.fromToken);
423
+ if ((0, import_swap2_assets.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets.isTokenAddress)(this.fromChain, ref)) return options;
424
+ const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
425
+ let decimals = this.unlistedDecimals.get(key);
426
+ if (decimals === void 0) {
427
+ const reader = this._account;
428
+ if (typeof reader?.getTokenInfo !== "function") {
429
+ if (options.fromTokenDecimals === void 0) {
430
+ throw new Swap2ValidationError(`swap2: '${ref}' is not in the catalog \u2014 pass fromTokenDecimals, or use an account that can read token info`);
431
+ }
432
+ return options;
433
+ }
434
+ decimals = (await reader.getTokenInfo(ref)).decimals;
435
+ this.unlistedDecimals.set(key, decimals);
436
+ }
437
+ if (options.fromTokenDecimals !== void 0 && options.fromTokenDecimals !== decimals) {
438
+ throw new Swap2ValidationError(`swap2: fromTokenDecimals ${options.fromTokenDecimals} does not match the token's ${decimals}`);
439
+ }
440
+ return snapshotOptions({ ...options, fromTokenDecimals: decimals });
441
+ }
393
442
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
394
- toParams(options, dry) {
443
+ toParams(given, dry) {
444
+ const options = withoutBlankAddresses(given);
395
445
  if (options.fromTokenAmount == null) {
396
446
  throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
397
447
  }
398
448
  const destChain = toCaip2(options.toChain, this.fromChain);
399
449
  const constraints = callerConstraints(options);
400
450
  const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
451
+ const source = this.sourceAsset(options);
401
452
  const params = {
402
453
  sourceChain: this.fromChain,
403
- sourceToken: toSymbol(this.fromChain, options.fromToken),
454
+ sourceToken: (0, import_swap2_assets.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
455
+ ...(0, import_swap2_assets.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
404
456
  destChain,
405
- destToken: toDestAddress(destChain, options.toToken),
457
+ destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
406
458
  amountIn: amountIn.toString(),
407
459
  destAddress: options.recipient ?? "",
408
460
  dry,
@@ -458,7 +510,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
458
510
  }
459
511
  }
460
512
  assertQuoteFeeCaps(q, options, config) {
461
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken));
513
+ const source = this.sourceAsset(options);
462
514
  if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
463
515
  const token = source.contract ?? source.assetId;
464
516
  const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
@@ -503,7 +555,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
503
555
  * gave one.
504
556
  */
505
557
  async quoteSwap2(options) {
506
- const snapshot = snapshotOptions(options);
558
+ const snapshot = await this.prepareOptions(snapshotOptions(options));
507
559
  const q = await this.resolveQuote(this.toParams(snapshot, true));
508
560
  if (!q) {
509
561
  const detail = this.noRouteDetail();
@@ -545,9 +597,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
545
597
  this.assertFeeConfig(configSnapshot);
546
598
  await this.assertAccountChain(signer);
547
599
  const selfAddress = await signer.getAddress();
548
- const recipient = snapshot.recipient ?? selfAddress;
549
- const refundAddress = snapshot.refundAddress ?? selfAddress;
550
- const executionOptions = snapshotOptions({ ...snapshot, recipient, refundAddress });
600
+ const recipient = blankToUndefined(snapshot.recipient) ?? selfAddress;
601
+ const refundAddress = blankToUndefined(snapshot.refundAddress) ?? selfAddress;
602
+ const executionOptions = await this.prepareOptions(snapshotOptions({ ...snapshot, recipient, refundAddress }));
551
603
  const params = this.toParams(executionOptions, false);
552
604
  const resolved = await this.resolveQuote(params);
553
605
  if (!resolved) {
@@ -628,7 +680,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
628
680
  }
629
681
  let tokenId;
630
682
  if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
631
- const src = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
683
+ const src = this.sourceAsset(executionOptions);
632
684
  if (src?.contract) {
633
685
  if (this.fromChain.startsWith("eip155:")) {
634
686
  unsigned = {
@@ -654,7 +706,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
654
706
  if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
655
707
  throw new Error("swap2: token deposit with memo/cell payload is not implemented");
656
708
  }
657
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
709
+ const source = this.sourceAsset(executionOptions);
658
710
  if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
659
711
  const executionValue = BigInt(unsigned.value);
660
712
  if (serialized) {