@anywalletkit/protocol-swap2-core 0.4.0 → 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 +2 -1
  2. package/dist/{chunk-4H2JVYXT.js → chunk-3Y6HV2OL.js} +2 -2
  3. package/dist/{chunk-W2PHJINC.js → chunk-IC3MZIMB.js} +95 -19
  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-5RR76RKH.js → chunk-OWO27YBK.js} +2 -2
  8. package/dist/index.cjs +106 -27
  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 +50 -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 +14 -5
  21. package/dist/server.js.map +1 -1
  22. package/dist/swap2/base.cjs +90 -17
  23. package/dist/swap2/base.cjs.map +1 -1
  24. package/dist/swap2/base.d.cts +31 -2
  25. package/dist/swap2/base.d.ts +31 -2
  26. package/dist/swap2/base.js +1 -1
  27. package/dist/swap2/single.cjs +90 -17
  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-W2PHJINC.js.map +0 -1
  43. /package/dist/{chunk-4H2JVYXT.js.map → chunk-3Y6HV2OL.js.map} +0 -0
  44. /package/dist/{chunk-5RR76RKH.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
  /**
@@ -83,9 +84,21 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
83
84
  * check has passed, BEFORE any approval or signature. It receives a copy of
84
85
  * exactly what will be signed; throwing (the user cancelled) aborts with
85
86
  * nothing sent. A quote that expires while the gate is open is refused
86
- * before the first approval.
87
+ * before the first approval. When the quote expires while the engine's own
88
+ * approval is being mined, the engine re-quotes once; if the fresh quote
89
+ * differs from the reviewed one (payload, gas, a worse output) the gate is
90
+ * called again with `requoted: true`, and throwing then revokes the allowance.
87
91
  */
88
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;
89
102
  }
90
103
  /** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
91
104
  interface Swap2ExecutionPlan {
@@ -108,6 +121,12 @@ interface Swap2ExecutionPlan {
108
121
  amount: bigint;
109
122
  chainId?: number;
110
123
  }[];
124
+ /**
125
+ * `true` on a second review: the first quote expired while its approval was being mined, and
126
+ * the fresh one differs from what was reviewed (another payload, a worse output). The allowance
127
+ * is already in place, so `approvals` is empty; cancelling revokes it.
128
+ */
129
+ requoted?: true;
111
130
  /** The source leg exactly as the account will receive it. */
112
131
  source: {
113
132
  kind: "call";
@@ -193,8 +212,18 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
193
212
  protected protocolEnabled(): boolean;
194
213
  /** Pair-level capability used when discovery specifies both endpoints. */
195
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>;
196
225
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
197
- protected toParams(options: Swap2Options, dry: boolean): QuoteParams;
226
+ protected toParams(given: Swap2Options, dry: boolean): QuoteParams;
198
227
  private assertAccountChain;
199
228
  protected assertFeeConfig(config?: Swap2ProtocolConfig): void;
200
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
  /**
@@ -83,9 +84,21 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
83
84
  * check has passed, BEFORE any approval or signature. It receives a copy of
84
85
  * exactly what will be signed; throwing (the user cancelled) aborts with
85
86
  * nothing sent. A quote that expires while the gate is open is refused
86
- * before the first approval.
87
+ * before the first approval. When the quote expires while the engine's own
88
+ * approval is being mined, the engine re-quotes once; if the fresh quote
89
+ * differs from the reviewed one (payload, gas, a worse output) the gate is
90
+ * called again with `requoted: true`, and throwing then revokes the allowance.
87
91
  */
88
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;
89
102
  }
90
103
  /** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
91
104
  interface Swap2ExecutionPlan {
@@ -108,6 +121,12 @@ interface Swap2ExecutionPlan {
108
121
  amount: bigint;
109
122
  chainId?: number;
110
123
  }[];
124
+ /**
125
+ * `true` on a second review: the first quote expired while its approval was being mined, and
126
+ * the fresh one differs from what was reviewed (another payload, a worse output). The allowance
127
+ * is already in place, so `approvals` is empty; cancelling revokes it.
128
+ */
129
+ requoted?: true;
111
130
  /** The source leg exactly as the account will receive it. */
112
131
  source: {
113
132
  kind: "call";
@@ -193,8 +212,18 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
193
212
  protected protocolEnabled(): boolean;
194
213
  /** Pair-level capability used when discovery specifies both endpoints. */
195
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>;
196
225
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
197
- protected toParams(options: Swap2Options, dry: boolean): QuoteParams;
226
+ protected toParams(given: Swap2Options, dry: boolean): QuoteParams;
198
227
  private assertAccountChain;
199
228
  protected assertFeeConfig(config?: Swap2ProtocolConfig): void;
200
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-W2PHJINC.js";
10
+ } from "../chunk-IC3MZIMB.js";
11
11
  import "../chunk-IVR3SREQ.js";
12
12
  export {
13
13
  BaseSwap2Protocol,
@@ -195,6 +195,10 @@ function normalizeTypedDomain(domain) {
195
195
  return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
196
196
  }
197
197
  var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
198
+ function reviewedIdentity(plan) {
199
+ const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
200
+ return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
201
+ }
198
202
  var SWAP2_ID_PREFIX = "awk1";
199
203
  function encodeId(provider, intentId, sourceTxHash) {
200
204
  const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
@@ -269,10 +273,11 @@ function toSymbol(caip2, ref) {
269
273
  if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
270
274
  return ref;
271
275
  }
272
- function toDestAddress(caip2, ref) {
276
+ function toDestAddress(caip2, ref, allowUnlisted = false) {
273
277
  if (ref.startsWith("0x")) return ref;
274
278
  const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
275
279
  if (!asset) {
280
+ if (allowUnlisted && (0, import_swap2_assets.isTokenAddress)(caip2, ref)) return ref;
276
281
  throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
277
282
  }
278
283
  return asset.contract ?? ZERO;
@@ -354,6 +359,15 @@ function mapStatus(s) {
354
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";
355
360
  return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
356
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
+ }
357
371
  var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
358
372
  fromChain;
359
373
  constructor(account, config) {
@@ -386,19 +400,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
386
400
  pairSupported(fromChain, toChain) {
387
401
  return this.chainSupported(fromChain) && this.chainSupported(toChain);
388
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
+ }
389
442
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
390
- toParams(options, dry) {
443
+ toParams(given, dry) {
444
+ const options = withoutBlankAddresses(given);
391
445
  if (options.fromTokenAmount == null) {
392
446
  throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
393
447
  }
394
448
  const destChain = toCaip2(options.toChain, this.fromChain);
395
449
  const constraints = callerConstraints(options);
396
450
  const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
451
+ const source = this.sourceAsset(options);
397
452
  const params = {
398
453
  sourceChain: this.fromChain,
399
- 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 } : {},
400
456
  destChain,
401
- destToken: toDestAddress(destChain, options.toToken),
457
+ destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
402
458
  amountIn: amountIn.toString(),
403
459
  destAddress: options.recipient ?? "",
404
460
  dry,
@@ -454,7 +510,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
454
510
  }
455
511
  }
456
512
  assertQuoteFeeCaps(q, options, config) {
457
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken));
513
+ const source = this.sourceAsset(options);
458
514
  if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
459
515
  const token = source.contract ?? source.assetId;
460
516
  const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
@@ -499,7 +555,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
499
555
  * gave one.
500
556
  */
501
557
  async quoteSwap2(options) {
502
- const snapshot = snapshotOptions(options);
558
+ const snapshot = await this.prepareOptions(snapshotOptions(options));
503
559
  const q = await this.resolveQuote(this.toParams(snapshot, true));
504
560
  if (!q) {
505
561
  const detail = this.noRouteDetail();
@@ -541,9 +597,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
541
597
  this.assertFeeConfig(configSnapshot);
542
598
  await this.assertAccountChain(signer);
543
599
  const selfAddress = await signer.getAddress();
544
- const recipient = snapshot.recipient ?? selfAddress;
545
- const refundAddress = snapshot.refundAddress ?? selfAddress;
546
- 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 }));
547
603
  const params = this.toParams(executionOptions, false);
548
604
  const resolved = await this.resolveQuote(params);
549
605
  if (!resolved) {
@@ -624,7 +680,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
624
680
  }
625
681
  let tokenId;
626
682
  if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
627
- const src = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
683
+ const src = this.sourceAsset(executionOptions);
628
684
  if (src?.contract) {
629
685
  if (this.fromChain.startsWith("eip155:")) {
630
686
  unsigned = {
@@ -650,7 +706,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
650
706
  if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
651
707
  throw new Error("swap2: token deposit with memo/cell payload is not implemented");
652
708
  }
653
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
709
+ const source = this.sourceAsset(executionOptions);
654
710
  if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
655
711
  const executionValue = BigInt(unsigned.value);
656
712
  if (serialized) {
@@ -706,7 +762,8 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
706
762
  approvalAmounts = current === approvalAmount ? [] : [...usdtMainnet && current !== 0n ? [0n] : [], approvalAmount];
707
763
  }
708
764
  const review = this._config.reviewExecution;
709
- if (review && !carried) {
765
+ let reviewed;
766
+ if (review && (!carried || carried.reviewed)) {
710
767
  const chainId = unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {};
711
768
  const approvals = approvalAmounts.map((amount) => ({ token: source.contract, spender: unsigned.approvalAddress, amount, ...chainId }));
712
769
  const plan = {
@@ -734,12 +791,28 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
734
791
  ...cellBody !== void 0 ? { cellBody: true } : {}
735
792
  }
736
793
  };
737
- await review(deepFreeze(structuredCloneSafe(plan)));
738
- if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
739
- throw new Swap2QuoteError("swap2: quote expired during review \u2014 nothing was signed");
794
+ const previous = carried?.reviewed;
795
+ if (!previous || reviewedIdentity(previous) !== reviewedIdentity(plan) || plan.destAmount < previous.destAmount) {
796
+ try {
797
+ reviewed = deepFreeze(structuredCloneSafe(previous ? { ...plan, requoted: true } : plan));
798
+ await review(reviewed);
799
+ if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
800
+ throw new Swap2QuoteError(`swap2: quote expired during review \u2014 ${previous ? "the allowance was revoked" : "nothing was signed"}`);
801
+ }
802
+ } catch (error) {
803
+ if (previous) await revokeApproval?.().catch(() => {
804
+ });
805
+ throw error;
806
+ }
740
807
  }
741
808
  }
742
- await this.assertNativeGas(signer, unsigned, approvalAmounts, source.contract ?? void 0, !!unsigned.typedData || serialized || !!tokenId);
809
+ try {
810
+ await this.assertNativeGas(signer, unsigned, approvalAmounts, source.contract ?? void 0, !!unsigned.typedData || serialized || !!tokenId);
811
+ } catch (error) {
812
+ if (carried) await revokeApproval?.().catch(() => {
813
+ });
814
+ throw error;
815
+ }
743
816
  if (approvalAmounts.length) {
744
817
  const approve = async (amount) => {
745
818
  const sent2 = await signer.sendTransaction({
@@ -768,7 +841,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
768
841
  if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
769
842
  if (!carried && approvalAmounts.length && revokeApproval) {
770
843
  this.emitPhase("requoting");
771
- const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false };
844
+ const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false, ...reviewed ? { reviewed } : {} };
772
845
  try {
773
846
  return await this.executeSwap2(signer, snapshot, config, handoff);
774
847
  } catch (error) {