@metamask-previews/transaction-pay-controller 29.0.0-preview-db287901b → 29.0.1-preview-d3f9964be
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/CHANGELOG.md +12 -1
- package/dist/strategy/relay/constants.d.ts +1 -0
- package/dist/strategy/relay/constants.d.ts.map +1 -1
- package/dist/strategy/relay/constants.js +1 -0
- package/dist/strategy/relay/constants.js.map +1 -1
- package/dist/strategy/relay/{relay-max-gas-station.d.ts → relay-max.d.ts} +12 -1
- package/dist/strategy/relay/relay-max.d.ts.map +1 -0
- package/dist/strategy/relay/{relay-max-gas-station.js → relay-max.js} +141 -2
- package/dist/strategy/relay/relay-max.js.map +1 -0
- package/dist/strategy/relay/relay-quotes.d.ts.map +1 -1
- package/dist/strategy/relay/relay-quotes.js +31 -10
- package/dist/strategy/relay/relay-quotes.js.map +1 -1
- package/dist/strategy/relay/relay-submit-execute.d.ts +1 -0
- package/dist/strategy/relay/relay-submit-execute.d.ts.map +1 -1
- package/dist/strategy/relay/relay-submit-execute.js +3 -3
- package/dist/strategy/relay/relay-submit-execute.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/feature-flags.d.ts +25 -1
- package/dist/utils/feature-flags.d.ts.map +1 -1
- package/dist/utils/feature-flags.js +33 -1
- package/dist/utils/feature-flags.js.map +1 -1
- package/package.json +2 -2
- package/dist/strategy/relay/relay-max-gas-station.d.ts.map +0 -1
- package/dist/strategy/relay/relay-max-gas-station.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,7 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Changed
|
|
11
11
|
|
|
12
|
+
- Support subsidized max Relay deposits using atomic `EXACT_OUTPUT` quotes with transaction calls embedded, gated by `payStrategies.relay.atomicMaxEnabled` (disabled by default, with per-transaction-type overrides). ([#10224](https://github.com/MetaMask/core/pull/10224))
|
|
13
|
+
- Honor the client's `atomic` hint: atomic max quotes use the source-token budget adjusted to destination decimals for 1:1 subsidized stablecoin routes, without a discovery quote or reusing the original deposit amount. Unsubsidized responses are re-quoted non-atomically.
|
|
14
|
+
- Non-atomic hints start with `EXACT_INPUT` and upgrade to atomic execution when subsidized.
|
|
15
|
+
- Atomic promotion errors retain the `Atomic promotion failed` prefix through standard quote error handling and strategy fallback.
|
|
16
|
+
|
|
17
|
+
## [29.0.1]
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
12
21
|
- Bump `@metamask/transaction-controller` from `^70.0.1` to `^70.1.0` ([#10262](https://github.com/MetaMask/core/pull/10262))
|
|
22
|
+
- Bump `@metamask/ramps-controller` from `^23.0.0` to `^24.0.0` ([#10326](https://github.com/MetaMask/core/pull/10326))
|
|
13
23
|
|
|
14
24
|
## [29.0.0]
|
|
15
25
|
|
|
@@ -1567,7 +1577,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1567
1577
|
|
|
1568
1578
|
- Initial release ([#6820](https://github.com/MetaMask/core/pull/6820))
|
|
1569
1579
|
|
|
1570
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@29.0.
|
|
1580
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@29.0.1...HEAD
|
|
1581
|
+
[29.0.1]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@29.0.0...@metamask/transaction-pay-controller@29.0.1
|
|
1571
1582
|
[29.0.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@28.0.2...@metamask/transaction-pay-controller@29.0.0
|
|
1572
1583
|
[28.0.2]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@28.0.1...@metamask/transaction-pay-controller@28.0.2
|
|
1573
1584
|
[28.0.1]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@28.0.0...@metamask/transaction-pay-controller@28.0.1
|
|
@@ -2,6 +2,7 @@ import { TransactionType } from '@metamask/transaction-controller';
|
|
|
2
2
|
import type { Hex } from '@metamask/utils';
|
|
3
3
|
import type { RelayStatus } from './types.js';
|
|
4
4
|
export declare const FALLBACK_HASH: Hex;
|
|
5
|
+
export declare const ATOMIC_PROMOTION_FAILURE_PREFIX = "Atomic promotion failed: ";
|
|
5
6
|
export declare const RELAY_URL_BASE = "https://api.relay.link";
|
|
6
7
|
export declare const RELAY_AUTHORIZE_URL = "https://api.relay.link/authorize";
|
|
7
8
|
export declare const RELAY_EXECUTE_URL = "https://api.relay.link/execute";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/strategy/relay/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,aAAa,EAAY,GAAG,CAAC;AAE1C,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,mBAAmB,qCAAgC,CAAC;AACjE,eAAO,MAAM,iBAAiB,mCAA8B,CAAC;AAC7D,eAAO,MAAM,eAAe,iCAA4B,CAAC;AACzD,eAAO,MAAM,gBAAgB,6CAAwC,CAAC;AACtE,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAClE,eAAO,MAAM,oBAAoB,qCAAiC,CAAC;AACnE,eAAO,MAAM,wBAAwB,yCAAqC,CAAC;AAC3E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,sBAAsB,EAAE,WAAW,EAI/C,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,WAAW,EAM/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAS/D,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/strategy/relay/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,aAAa,EAAY,GAAG,CAAC;AAE1C,eAAO,MAAM,+BAA+B,8BAA8B,CAAC;AAE3E,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,mBAAmB,qCAAgC,CAAC;AACjE,eAAO,MAAM,iBAAiB,mCAA8B,CAAC;AAC7D,eAAO,MAAM,eAAe,iCAA4B,CAAC;AACzD,eAAO,MAAM,gBAAgB,6CAAwC,CAAC;AACtE,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAClE,eAAO,MAAM,oBAAoB,qCAAiC,CAAC;AACnE,eAAO,MAAM,wBAAwB,yCAAqC,CAAC;AAC3E,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,sBAAsB,EAAE,WAAW,EAI/C,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,WAAW,EAM/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAS/D,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TransactionType } from '@metamask/transaction-controller';
|
|
2
2
|
import { PERPS_DEPOSIT_TYPES } from '../../constants.js';
|
|
3
3
|
export const FALLBACK_HASH = '0x0';
|
|
4
|
+
export const ATOMIC_PROMOTION_FAILURE_PREFIX = 'Atomic promotion failed: ';
|
|
4
5
|
export const RELAY_URL_BASE = 'https://api.relay.link';
|
|
5
6
|
export const RELAY_AUTHORIZE_URL = `${RELAY_URL_BASE}/authorize`;
|
|
6
7
|
export const RELAY_EXECUTE_URL = `${RELAY_URL_BASE}/execute`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/strategy/relay/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAGnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAY,CAAC;AAE1C,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,cAAc,YAAY,CAAC;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,cAAc,UAAU,CAAC;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,cAAc,QAAQ,CAAC;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,cAAc,oBAAoB,CAAC;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAClE,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,oBAAoB,OAAO,CAAC;AACnE,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,oBAAoB,WAAW,CAAC;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC,CAAC,WAAW;AACvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAErD,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,SAAS;IACT,QAAQ;IACR,UAAU;CACX,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,SAAS;IACT,YAAY;IACZ,SAAS;IACT,WAAW;IACX,SAAS;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAoC;IAClE,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB;IAClE,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,mBAAmB;IACrE,GAAG,MAAM,CAAC,WAAW,CACnB,mBAAmB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI;QACJ,eAAe,CAAC,iBAAiB;KAClC,CAAC,CACH;CACF,CAAC","sourcesContent":["import { TransactionType } from '@metamask/transaction-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { PERPS_DEPOSIT_TYPES } from '../../constants.js';\nimport type { RelayStatus } from './types.js';\n\nexport const FALLBACK_HASH = '0x0' as Hex;\n\nexport const RELAY_URL_BASE = 'https://api.relay.link';\nexport const RELAY_AUTHORIZE_URL = `${RELAY_URL_BASE}/authorize`;\nexport const RELAY_EXECUTE_URL = `${RELAY_URL_BASE}/execute`;\nexport const RELAY_QUOTE_URL = `${RELAY_URL_BASE}/quote`;\nexport const RELAY_STATUS_URL = `${RELAY_URL_BASE}/intents/status/v3`;\nexport const HYPERLIQUID_URL_BASE = 'https://api.hyperliquid.xyz';\nexport const HYPERLIQUID_INFO_URL = `${HYPERLIQUID_URL_BASE}/info`;\nexport const HYPERLIQUID_EXCHANGE_URL = `${HYPERLIQUID_URL_BASE}/exchange`;\nexport const RELAY_POLLING_INTERVAL = 1000; // 1 Second\nexport const TOKEN_TRANSFER_FOUR_BYTE = '0xa9059cbb';\n\nexport const RELAY_FAILURE_STATUSES: RelayStatus[] = [\n 'failure',\n 'refund',\n 'refunded',\n];\n\nexport const RELAY_PENDING_STATUSES: RelayStatus[] = [\n 'delayed',\n 'depositing',\n 'pending',\n 'submitted',\n 'waiting',\n];\n\nexport const RELAY_DEPOSIT_TYPES: Record<string, TransactionType> = {\n [TransactionType.musdConversion]: TransactionType.musdRelayDeposit,\n [TransactionType.predictDeposit]: TransactionType.predictRelayDeposit,\n ...Object.fromEntries(\n PERPS_DEPOSIT_TYPES.map((type) => [\n type,\n TransactionType.perpsRelayDeposit,\n ]),\n ),\n};\n"]}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/strategy/relay/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAGnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAY,CAAC;AAE1C,MAAM,CAAC,MAAM,+BAA+B,GAAG,2BAA2B,CAAC;AAE3E,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,cAAc,YAAY,CAAC;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,cAAc,UAAU,CAAC;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,cAAc,QAAQ,CAAC;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,cAAc,oBAAoB,CAAC;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAClE,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,oBAAoB,OAAO,CAAC;AACnE,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,oBAAoB,WAAW,CAAC;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC,CAAC,WAAW;AACvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAErD,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,SAAS;IACT,QAAQ;IACR,UAAU;CACX,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,SAAS;IACT,YAAY;IACZ,SAAS;IACT,WAAW;IACX,SAAS;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAoC;IAClE,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB;IAClE,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,eAAe,CAAC,mBAAmB;IACrE,GAAG,MAAM,CAAC,WAAW,CACnB,mBAAmB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI;QACJ,eAAe,CAAC,iBAAiB;KAClC,CAAC,CACH;CACF,CAAC","sourcesContent":["import { TransactionType } from '@metamask/transaction-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport { PERPS_DEPOSIT_TYPES } from '../../constants.js';\nimport type { RelayStatus } from './types.js';\n\nexport const FALLBACK_HASH = '0x0' as Hex;\n\nexport const ATOMIC_PROMOTION_FAILURE_PREFIX = 'Atomic promotion failed: ';\n\nexport const RELAY_URL_BASE = 'https://api.relay.link';\nexport const RELAY_AUTHORIZE_URL = `${RELAY_URL_BASE}/authorize`;\nexport const RELAY_EXECUTE_URL = `${RELAY_URL_BASE}/execute`;\nexport const RELAY_QUOTE_URL = `${RELAY_URL_BASE}/quote`;\nexport const RELAY_STATUS_URL = `${RELAY_URL_BASE}/intents/status/v3`;\nexport const HYPERLIQUID_URL_BASE = 'https://api.hyperliquid.xyz';\nexport const HYPERLIQUID_INFO_URL = `${HYPERLIQUID_URL_BASE}/info`;\nexport const HYPERLIQUID_EXCHANGE_URL = `${HYPERLIQUID_URL_BASE}/exchange`;\nexport const RELAY_POLLING_INTERVAL = 1000; // 1 Second\nexport const TOKEN_TRANSFER_FOUR_BYTE = '0xa9059cbb';\n\nexport const RELAY_FAILURE_STATUSES: RelayStatus[] = [\n 'failure',\n 'refund',\n 'refunded',\n];\n\nexport const RELAY_PENDING_STATUSES: RelayStatus[] = [\n 'delayed',\n 'depositing',\n 'pending',\n 'submitted',\n 'waiting',\n];\n\nexport const RELAY_DEPOSIT_TYPES: Record<string, TransactionType> = {\n [TransactionType.musdConversion]: TransactionType.musdRelayDeposit,\n [TransactionType.predictDeposit]: TransactionType.predictRelayDeposit,\n ...Object.fromEntries(\n PERPS_DEPOSIT_TYPES.map((type) => [\n type,\n TransactionType.perpsRelayDeposit,\n ]),\n ),\n};\n"]}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { PayStrategyGetQuotesRequest, QuoteRequest, TransactionPayQuote } from '../../types.js';
|
|
2
2
|
import type { RelayQuote } from './types.js';
|
|
3
3
|
type GetSingleQuoteFn = (request: QuoteRequest, fullRequest: PayStrategyGetQuotesRequest) => Promise<TransactionPayQuote<RelayQuote>>;
|
|
4
|
+
/**
|
|
5
|
+
* Fetch a max quote using the client's atomic hint for subsidized routes.
|
|
6
|
+
*
|
|
7
|
+
* @param request - Max quote request.
|
|
8
|
+
* @param fullRequest - Full quote context.
|
|
9
|
+
* @param getSingleQuote - Fetcher for a single Relay quote.
|
|
10
|
+
* @returns An atomic subsidized quote or a non-atomic max quote.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getRelayMaxQuote(request: QuoteRequest, fullRequest: PayStrategyGetQuotesRequest, getSingleQuote: GetSingleQuoteFn): Promise<TransactionPayQuote<RelayQuote>>;
|
|
4
13
|
/**
|
|
5
14
|
* Returns a Relay max-amount quote using a two-phase gas-station fallback.
|
|
6
15
|
*
|
|
@@ -18,5 +27,7 @@ type GetSingleQuoteFn = (request: QuoteRequest, fullRequest: PayStrategyGetQuote
|
|
|
18
27
|
* @returns The validated adjusted phase-2 quote, or the original phase-1 quote on fallback.
|
|
19
28
|
*/
|
|
20
29
|
export declare function getRelayMaxGasStationQuote(request: QuoteRequest, fullRequest: PayStrategyGetQuotesRequest, getSingleQuote: GetSingleQuoteFn): Promise<TransactionPayQuote<RelayQuote>>;
|
|
30
|
+
export declare function throwAtomicPromotionFailed(error: unknown): never;
|
|
31
|
+
export declare function isSubsidizedAtomicMaxQuote(quote: TransactionPayQuote<RelayQuote>): boolean;
|
|
21
32
|
export {};
|
|
22
|
-
//# sourceMappingURL=relay-max
|
|
33
|
+
//# sourceMappingURL=relay-max.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-max.d.ts","sourceRoot":"","sources":["../../../src/strategy/relay/relay-max.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,2BAA2B,EAC3B,YAAY,EAEZ,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAexB,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,YAAY,CAAC;AAuBnE,KAAK,gBAAgB,GAAG,CACtB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,2BAA2B,KACrC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC;AAS9C;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,2BAA2B,EACxC,cAAc,EAAE,gBAAgB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAyE1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,2BAA2B,EACxC,cAAc,EAAE,gBAAgB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAgJ1C;AAuRD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAOhE;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,mBAAmB,CAAC,UAAU,CAAC,GACrC,OAAO,CAMT"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { toHex } from '@metamask/controller-utils';
|
|
1
2
|
import { createModuleLogger } from '@metamask/utils';
|
|
2
3
|
import { BigNumber } from 'bignumber.js';
|
|
3
4
|
import { projectLogger } from '../../logger.js';
|
|
5
|
+
import { prefixError } from '../../utils/error-prefix.js';
|
|
6
|
+
import { isAtomicMaxEnabled } from '../../utils/feature-flags.js';
|
|
4
7
|
import { getGasStationEligibility, getGasStationCostInSourceTokenRaw, } from '../../utils/gas-station.js';
|
|
5
8
|
import { getNativeToken, getTokenBalance, getTokenInfo, } from '../../utils/token.js';
|
|
6
|
-
|
|
9
|
+
import { QuoteError } from '../../utils/validation.js';
|
|
10
|
+
import { ATOMIC_PROMOTION_FAILURE_PREFIX } from './constants.js';
|
|
11
|
+
import { isSubsidizedRelayQuote } from './relay-submit-execute.js';
|
|
12
|
+
const log = createModuleLogger(projectLogger, 'relay-max');
|
|
7
13
|
const PROBE_AMOUNT_PERCENTAGE = 0.25;
|
|
8
14
|
var GasCostEstimateSource;
|
|
9
15
|
(function (GasCostEstimateSource) {
|
|
@@ -11,6 +17,62 @@ var GasCostEstimateSource;
|
|
|
11
17
|
GasCostEstimateSource["Probe"] = "probe";
|
|
12
18
|
GasCostEstimateSource["Quote"] = "quote";
|
|
13
19
|
})(GasCostEstimateSource || (GasCostEstimateSource = {}));
|
|
20
|
+
/**
|
|
21
|
+
* Fetch a max quote using the client's atomic hint for subsidized routes.
|
|
22
|
+
*
|
|
23
|
+
* @param request - Max quote request.
|
|
24
|
+
* @param fullRequest - Full quote context.
|
|
25
|
+
* @param getSingleQuote - Fetcher for a single Relay quote.
|
|
26
|
+
* @returns An atomic subsidized quote or a non-atomic max quote.
|
|
27
|
+
*/
|
|
28
|
+
export async function getRelayMaxQuote(request, fullRequest, getSingleQuote) {
|
|
29
|
+
const { messenger, transaction } = fullRequest;
|
|
30
|
+
const { atomic, isPostQuote } = request;
|
|
31
|
+
if (isPostQuote || !isAtomicMaxEnabled(messenger, transaction)) {
|
|
32
|
+
return getRelayMaxGasStationQuote(request, fullRequest, getSingleQuote);
|
|
33
|
+
}
|
|
34
|
+
if (atomic !== false) {
|
|
35
|
+
const sourceToken = getTokenInfo(messenger, request.sourceTokenAddress, request.sourceChainId);
|
|
36
|
+
const targetToken = getTokenInfo(messenger, request.targetTokenAddress, request.targetChainId);
|
|
37
|
+
if (!sourceToken || !targetToken) {
|
|
38
|
+
throw new Error('Token decimals not found for atomic max quote');
|
|
39
|
+
}
|
|
40
|
+
// Fixed-spread subsidized routes exchange stablecoins 1:1 without fees.
|
|
41
|
+
// Use the source budget, not the required amount from before Max.
|
|
42
|
+
const targetAmount = new BigNumber(request.sourceTokenAmount)
|
|
43
|
+
.shiftedBy(targetToken.decimals - sourceToken.decimals)
|
|
44
|
+
.toFixed(0, BigNumber.ROUND_DOWN);
|
|
45
|
+
const quote = await getAtomicMaxQuote({
|
|
46
|
+
amount: targetAmount,
|
|
47
|
+
fullRequest,
|
|
48
|
+
getSingleQuote,
|
|
49
|
+
request,
|
|
50
|
+
});
|
|
51
|
+
if (isSubsidizedRelayQuote(quote.original)) {
|
|
52
|
+
return quote;
|
|
53
|
+
}
|
|
54
|
+
return getRelayMaxGasStationQuote({ ...request, atomic: false }, fullRequest, getSingleQuote);
|
|
55
|
+
}
|
|
56
|
+
const quote = await getRelayMaxGasStationQuote(request, fullRequest, getSingleQuote);
|
|
57
|
+
if (!isSubsidizedRelayQuote(quote.original)) {
|
|
58
|
+
return quote;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const promotedQuote = await getAtomicMaxQuote({
|
|
62
|
+
amount: quote.original.details.currencyOut.amount,
|
|
63
|
+
fullRequest,
|
|
64
|
+
getSingleQuote,
|
|
65
|
+
request,
|
|
66
|
+
});
|
|
67
|
+
if (!isSubsidizedRelayQuote(promotedQuote.original)) {
|
|
68
|
+
throw new Error('Promoted quote lost subsidy');
|
|
69
|
+
}
|
|
70
|
+
return promotedQuote;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return throwAtomicPromotionFailed(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
14
76
|
/**
|
|
15
77
|
* Returns a Relay max-amount quote using a two-phase gas-station fallback.
|
|
16
78
|
*
|
|
@@ -239,4 +301,81 @@ function markQuoteAsMaxGasStation(quote) {
|
|
|
239
301
|
isMaxGasStation: true,
|
|
240
302
|
};
|
|
241
303
|
}
|
|
242
|
-
|
|
304
|
+
async function getAtomicMaxQuote({ amount, fullRequest, getSingleQuote, request, }) {
|
|
305
|
+
const targetAmount = validatePositiveIntegerString(amount);
|
|
306
|
+
const atomicTransaction = await applyAmountDataUpdates({
|
|
307
|
+
amount: targetAmount,
|
|
308
|
+
messenger: fullRequest.messenger,
|
|
309
|
+
transaction: cloneTransactionForPromotion(fullRequest.transaction),
|
|
310
|
+
});
|
|
311
|
+
return getSingleQuote({
|
|
312
|
+
...request,
|
|
313
|
+
atomic: true,
|
|
314
|
+
targetAmountMinimum: targetAmount,
|
|
315
|
+
}, { ...fullRequest, transaction: atomicTransaction });
|
|
316
|
+
}
|
|
317
|
+
export function throwAtomicPromotionFailed(error) {
|
|
318
|
+
const prefixed = prefixError(error, ATOMIC_PROMOTION_FAILURE_PREFIX);
|
|
319
|
+
throw new QuoteError({
|
|
320
|
+
detail: [prefixed.message],
|
|
321
|
+
message: prefixed.message,
|
|
322
|
+
reason: 'no-quotes',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export function isSubsidizedAtomicMaxQuote(quote) {
|
|
326
|
+
return (quote.request.isMaxAmount === true &&
|
|
327
|
+
quote.request.atomic === true &&
|
|
328
|
+
isSubsidizedRelayQuote(quote.original));
|
|
329
|
+
}
|
|
330
|
+
function validatePositiveIntegerString(value) {
|
|
331
|
+
const amount = new BigNumber(value);
|
|
332
|
+
if (!amount.isFinite() || !amount.isInteger() || !amount.isGreaterThan(0)) {
|
|
333
|
+
throw new Error(`Invalid target amount: ${value}`);
|
|
334
|
+
}
|
|
335
|
+
return amount.toFixed(0);
|
|
336
|
+
}
|
|
337
|
+
function cloneTransactionForPromotion(transaction) {
|
|
338
|
+
return {
|
|
339
|
+
...transaction,
|
|
340
|
+
nestedTransactions: transaction.nestedTransactions?.map((nestedTransaction) => ({
|
|
341
|
+
...nestedTransaction,
|
|
342
|
+
})),
|
|
343
|
+
requiredAssets: transaction.requiredAssets?.map((requiredAsset) => ({
|
|
344
|
+
...requiredAsset,
|
|
345
|
+
})),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
async function applyAmountDataUpdates({ amount, messenger, transaction, }) {
|
|
349
|
+
const { updates } = await messenger.call('TransactionPayController:getAmountData', {
|
|
350
|
+
amount,
|
|
351
|
+
transaction,
|
|
352
|
+
});
|
|
353
|
+
if (!updates.length) {
|
|
354
|
+
throw new Error('getAmountData returned no updates for atomic promotion');
|
|
355
|
+
}
|
|
356
|
+
const nestedTransactions = transaction.nestedTransactions?.map((nestedTransaction) => ({
|
|
357
|
+
...nestedTransaction,
|
|
358
|
+
}));
|
|
359
|
+
if (!nestedTransactions?.length) {
|
|
360
|
+
throw new Error('Missing nested transactions for atomic promotion');
|
|
361
|
+
}
|
|
362
|
+
for (const { nestedTransactionIndex, data } of updates) {
|
|
363
|
+
if (!nestedTransactions[nestedTransactionIndex]) {
|
|
364
|
+
throw new Error('getAmountData returned an unusable nested transaction update');
|
|
365
|
+
}
|
|
366
|
+
nestedTransactions[nestedTransactionIndex].data = data;
|
|
367
|
+
}
|
|
368
|
+
const requiredAssets = transaction.requiredAssets?.map((requiredAsset) => ({
|
|
369
|
+
...requiredAsset,
|
|
370
|
+
}));
|
|
371
|
+
if (!requiredAssets?.[0]) {
|
|
372
|
+
throw new Error('Missing required assets for atomic promotion');
|
|
373
|
+
}
|
|
374
|
+
requiredAssets[0].amount = toHex(BigInt(amount));
|
|
375
|
+
return {
|
|
376
|
+
...transaction,
|
|
377
|
+
nestedTransactions,
|
|
378
|
+
requiredAssets,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=relay-max.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-max.js","sourceRoot":"","sources":["../../../src/strategy/relay/relay-max.ts"],"names":[],"mappings":"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","sourcesContent":["import { toHex } from '@metamask/controller-utils';\nimport type { TransactionMeta } from '@metamask/transaction-controller';\nimport { createModuleLogger } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport { projectLogger } from '../../logger.js';\nimport type {\n PayStrategyGetQuotesRequest,\n QuoteRequest,\n TransactionPayControllerMessenger,\n TransactionPayQuote,\n} from '../../types.js';\nimport { prefixError } from '../../utils/error-prefix.js';\nimport { isAtomicMaxEnabled } from '../../utils/feature-flags.js';\nimport {\n getGasStationEligibility,\n getGasStationCostInSourceTokenRaw,\n} from '../../utils/gas-station.js';\nimport {\n getNativeToken,\n getTokenBalance,\n getTokenInfo,\n} from '../../utils/token.js';\nimport { QuoteError } from '../../utils/validation.js';\nimport { ATOMIC_PROMOTION_FAILURE_PREFIX } from './constants.js';\nimport { isSubsidizedRelayQuote } from './relay-submit-execute.js';\nimport type { RelayQuote, RelayTransactionStep } from './types.js';\n\nconst log = createModuleLogger(projectLogger, 'relay-max');\n\nconst PROBE_AMOUNT_PERCENTAGE = 0.25;\n\nenum GasCostEstimateSource {\n GasStation = 'gas-station',\n Probe = 'probe',\n Quote = 'quote',\n}\n\ntype GasCostEstimate = {\n amount: BigNumber;\n source: GasCostEstimateSource;\n};\n\ntype NativeBalanceCheckResult = {\n hasEnoughNativeBalance: boolean;\n nativeBalance?: string;\n nativeGasCostRaw?: string;\n};\n\ntype GetSingleQuoteFn = (\n request: QuoteRequest,\n fullRequest: PayStrategyGetQuotesRequest,\n) => Promise<TransactionPayQuote<RelayQuote>>;\n\ntype MaxAmountQuoteContext = {\n fullRequest: PayStrategyGetQuotesRequest;\n getSingleQuote: GetSingleQuoteFn;\n messenger: TransactionPayControllerMessenger;\n request: QuoteRequest;\n};\n\n/**\n * Fetch a max quote using the client's atomic hint for subsidized routes.\n *\n * @param request - Max quote request.\n * @param fullRequest - Full quote context.\n * @param getSingleQuote - Fetcher for a single Relay quote.\n * @returns An atomic subsidized quote or a non-atomic max quote.\n */\nexport async function getRelayMaxQuote(\n request: QuoteRequest,\n fullRequest: PayStrategyGetQuotesRequest,\n getSingleQuote: GetSingleQuoteFn,\n): Promise<TransactionPayQuote<RelayQuote>> {\n const { messenger, transaction } = fullRequest;\n const { atomic, isPostQuote } = request;\n\n if (isPostQuote || !isAtomicMaxEnabled(messenger, transaction)) {\n return getRelayMaxGasStationQuote(request, fullRequest, getSingleQuote);\n }\n\n if (atomic !== false) {\n const sourceToken = getTokenInfo(\n messenger,\n request.sourceTokenAddress,\n request.sourceChainId,\n );\n const targetToken = getTokenInfo(\n messenger,\n request.targetTokenAddress,\n request.targetChainId,\n );\n\n if (!sourceToken || !targetToken) {\n throw new Error('Token decimals not found for atomic max quote');\n }\n\n // Fixed-spread subsidized routes exchange stablecoins 1:1 without fees.\n // Use the source budget, not the required amount from before Max.\n const targetAmount = new BigNumber(request.sourceTokenAmount)\n .shiftedBy(targetToken.decimals - sourceToken.decimals)\n .toFixed(0, BigNumber.ROUND_DOWN);\n const quote = await getAtomicMaxQuote({\n amount: targetAmount,\n fullRequest,\n getSingleQuote,\n request,\n });\n\n if (isSubsidizedRelayQuote(quote.original)) {\n return quote;\n }\n\n return getRelayMaxGasStationQuote(\n { ...request, atomic: false },\n fullRequest,\n getSingleQuote,\n );\n }\n\n const quote = await getRelayMaxGasStationQuote(\n request,\n fullRequest,\n getSingleQuote,\n );\n\n if (!isSubsidizedRelayQuote(quote.original)) {\n return quote;\n }\n\n try {\n const promotedQuote = await getAtomicMaxQuote({\n amount: quote.original.details.currencyOut.amount,\n fullRequest,\n getSingleQuote,\n request,\n });\n\n if (!isSubsidizedRelayQuote(promotedQuote.original)) {\n throw new Error('Promoted quote lost subsidy');\n }\n\n return promotedQuote;\n } catch (error) {\n return throwAtomicPromotionFailed(error);\n }\n}\n\n/**\n * Returns a Relay max-amount quote using a two-phase gas-station fallback.\n *\n * It first requests a standard max quote (phase 1), then when needed estimates\n * gas in source-token units (directly or via a probe quote), requests an\n * adjusted max quote (phase 2), and accepts phase 2 only if validation passes\n * (source gas fee token selected, gas-limit checks, affordability).\n *\n * If any step fails or validation is unsafe, it safely falls back to phase 1.\n * Successful phase-2 quotes are tagged with `metamask.isMaxGasStation = true`.\n *\n * @param request - Relay quote request for a max-amount flow.\n * @param fullRequest - Full quote request context including messenger and transaction.\n * @param getSingleQuote - Quote fetcher used for phase-1, phase-2, and probe quotes.\n * @returns The validated adjusted phase-2 quote, or the original phase-1 quote on fallback.\n */\nexport async function getRelayMaxGasStationQuote(\n request: QuoteRequest,\n fullRequest: PayStrategyGetQuotesRequest,\n getSingleQuote: GetSingleQuoteFn,\n): Promise<TransactionPayQuote<RelayQuote>> {\n const { messenger } = fullRequest;\n const { sourceChainId, sourceTokenAmount } = request;\n const context: MaxAmountQuoteContext = {\n fullRequest,\n getSingleQuote,\n messenger,\n request,\n };\n\n const phase1Quote = await getSingleQuote(request, fullRequest);\n\n if (phase1Quote.original.metamask?.isExecute) {\n return phase1Quote;\n }\n\n const nativeBalanceCheck = checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(\n phase1Quote,\n messenger,\n request,\n );\n\n if (nativeBalanceCheck.hasEnoughNativeBalance) {\n return fallbackToPhase1(\n phase1Quote,\n 'Native balance is sufficient for gas',\n {\n nativeBalance: nativeBalanceCheck.nativeBalance,\n nativeGasCost: nativeBalanceCheck.nativeGasCostRaw,\n },\n );\n }\n\n const gasStationEligibility = getGasStationEligibility(\n messenger,\n sourceChainId,\n );\n\n if (!gasStationEligibility.isEligible) {\n return fallbackToPhase1(\n phase1Quote,\n 'Gas station is disabled or unsupported',\n );\n }\n\n const sourceAmountBN = new BigNumber(sourceTokenAmount);\n const probeCost = await getProbeGasCostInSourceTokenRaw(context);\n\n if (!probeCost) {\n return fallbackToPhase1(\n phase1Quote,\n 'Unable to estimate gas-station source token cost',\n );\n }\n\n const initialGasEstimate: GasCostEstimate = {\n amount: probeCost,\n source: GasCostEstimateSource.Probe,\n };\n\n const adjustedSourceAmount = getAdjustedSourceAmount(\n sourceAmountBN,\n initialGasEstimate.amount,\n );\n\n if (!adjustedSourceAmount.isGreaterThan(0)) {\n return fallbackToPhase1(\n phase1Quote,\n 'Insufficient balance for gas station after max adjustment',\n );\n }\n\n const phase2Quote = await getAdjustedPhase2Quote(\n adjustedSourceAmount,\n initialGasEstimate,\n context,\n );\n\n if (!phase2Quote) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted phase-2 quote request failed',\n );\n }\n\n if (!phase2Quote.fees.isSourceGasFeeToken) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote did not return source gas fee token pricing',\n );\n }\n\n const phase1GasLimits = phase1Quote.original.metamask.gasLimits ?? [];\n const phase2GasLimits = phase2Quote.original.metamask.gasLimits ?? [];\n\n if (!hasMatchingGasLimitShape(phase1GasLimits, phase2GasLimits)) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote gas limit shape changed between phases',\n {\n phase1GasLimits,\n phase2GasLimits,\n },\n );\n }\n\n const phase1TotalGasLimit = getTotalGasLimit(phase1GasLimits);\n const phase2TotalGasLimit = getTotalGasLimit(phase2GasLimits);\n\n if (phase2TotalGasLimit > phase1TotalGasLimit) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote total gas limit increased between phases',\n {\n phase1TotalGasLimit,\n phase2TotalGasLimit,\n },\n );\n }\n\n const validationGasEstimate = new BigNumber(\n phase2Quote.fees.sourceNetwork.max.raw,\n );\n\n if (\n !isAdjustedAmountAffordable(\n adjustedSourceAmount,\n validationGasEstimate,\n sourceAmountBN,\n )\n ) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote fails affordability validation',\n {\n adjustedSourceAmount: adjustedSourceAmount.toString(10),\n validationGasCost: validationGasEstimate.toString(10),\n },\n );\n }\n\n markQuoteAsMaxGasStation(phase2Quote);\n\n return phase2Quote;\n}\n\nfunction checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(\n quote: TransactionPayQuote<RelayQuote>,\n messenger: TransactionPayControllerMessenger,\n request: QuoteRequest,\n): NativeBalanceCheckResult {\n if (quote.fees.isSourceGasFeeToken) {\n return { hasEnoughNativeBalance: false };\n }\n\n const nativeGasCostRaw = quote.fees.sourceNetwork.max.raw;\n const nativeBalance = getTokenBalance(\n messenger,\n request.from,\n request.sourceChainId,\n getNativeToken(request.sourceChainId),\n );\n\n return {\n hasEnoughNativeBalance: new BigNumber(nativeBalance).isGreaterThanOrEqualTo(\n nativeGasCostRaw,\n ),\n nativeBalance,\n nativeGasCostRaw,\n };\n}\n\nfunction getAdjustedSourceAmount(\n sourceAmount: BigNumber,\n estimatedGasCost: BigNumber,\n): BigNumber {\n return sourceAmount\n .minus(estimatedGasCost)\n .integerValue(BigNumber.ROUND_DOWN);\n}\n\nfunction isAdjustedAmountAffordable(\n adjustedSourceAmount: BigNumber,\n gasCost: BigNumber,\n originalSourceAmount: BigNumber,\n): boolean {\n return adjustedSourceAmount\n .plus(gasCost)\n .isLessThanOrEqualTo(originalSourceAmount);\n}\n\nasync function getAdjustedPhase2Quote(\n adjustedSourceAmount: BigNumber,\n initialGasEstimate: GasCostEstimate,\n context: MaxAmountQuoteContext,\n): Promise<TransactionPayQuote<RelayQuote> | undefined> {\n const { fullRequest, getSingleQuote, request } = context;\n\n log('Requesting adjusted max quote', {\n adjustedAmount: adjustedSourceAmount.toString(10),\n gasCostInSourceToken: initialGasEstimate.amount.toString(10),\n gasEstimateSource: initialGasEstimate.source,\n originalAmount: request.sourceTokenAmount,\n });\n\n try {\n return await getSingleQuote(\n {\n ...request,\n sourceTokenAmount: adjustedSourceAmount.toFixed(\n 0,\n BigNumber.ROUND_DOWN,\n ),\n },\n fullRequest,\n );\n } catch (error) {\n log('Adjusted quote request failed, falling back to phase-1 quote', {\n error,\n });\n return undefined;\n }\n}\n\nasync function getGasCostFromQuoteOrGasStation(\n quote: TransactionPayQuote<RelayQuote>,\n messenger: TransactionPayControllerMessenger,\n request: QuoteRequest,\n): Promise<GasCostEstimate | undefined> {\n const gasCost = quote.fees.sourceNetwork.max;\n\n if (quote.fees.isSourceGasFeeToken) {\n log('Gas cost already in source token units', { raw: gasCost.raw });\n return {\n amount: new BigNumber(gasCost.raw),\n source: GasCostEstimateSource.Quote,\n };\n }\n\n const firstTxStep = quote.original.steps.find(\n (step): step is RelayTransactionStep => step.kind === 'transaction',\n );\n const firstStepData = firstTxStep?.items[0]?.data;\n\n if (!firstStepData) {\n return undefined;\n }\n\n const totalItemCount = quote.original.steps.reduce(\n (count, step) => count + step.items.length,\n 0,\n );\n\n const totalGasEstimate = (quote.original.metamask.gasLimits ?? []).reduce(\n (acc, gasLimit) => acc + gasLimit,\n 0,\n );\n\n const gasStationCost = await getGasStationCostInSourceTokenRaw({\n firstStepData,\n messenger,\n request: {\n from: request.from,\n sourceChainId: request.sourceChainId,\n sourceTokenAddress: request.sourceTokenAddress,\n },\n totalGasEstimate,\n totalItemCount,\n });\n\n if (!gasStationCost) {\n return undefined;\n }\n\n return {\n amount: new BigNumber(gasStationCost.raw),\n source: GasCostEstimateSource.GasStation,\n };\n}\n\nasync function getProbeGasCostInSourceTokenRaw(\n context: MaxAmountQuoteContext,\n): Promise<BigNumber | undefined> {\n const { fullRequest, getSingleQuote, messenger, request } = context;\n\n const sourceTokenInfo = getTokenInfo(\n messenger,\n request.sourceTokenAddress,\n request.sourceChainId,\n );\n\n if (!sourceTokenInfo) {\n return undefined;\n }\n\n const probeAmount = getProbeSourceAmountRaw(\n request.sourceTokenAmount,\n sourceTokenInfo.decimals,\n );\n\n if (!probeAmount || probeAmount === request.sourceTokenAmount) {\n return undefined;\n }\n\n log('Requesting probe quote for gas station estimation', {\n originalSourceAmount: request.sourceTokenAmount,\n probeAmount,\n });\n\n let probeQuote: TransactionPayQuote<RelayQuote> | undefined;\n\n try {\n probeQuote = await getSingleQuote(\n {\n ...request,\n sourceTokenAmount: probeAmount,\n },\n fullRequest,\n );\n } catch (error) {\n log('Probe quote request failed', { error });\n return undefined;\n }\n\n if (!probeQuote) {\n return undefined;\n }\n\n const probeEstimate = await getGasCostFromQuoteOrGasStation(\n probeQuote,\n messenger,\n request,\n );\n\n return probeEstimate?.amount;\n}\n\nfunction getProbeSourceAmountRaw(\n sourceAmountRaw: string,\n sourceDecimals: number,\n): string | undefined {\n const sourceAmount = new BigNumber(sourceAmountRaw);\n\n if (sourceAmount.isLessThanOrEqualTo(0)) {\n return undefined;\n }\n\n const probeRawAmount = sourceAmount\n .multipliedBy(PROBE_AMOUNT_PERCENTAGE)\n .integerValue(BigNumber.ROUND_FLOOR);\n\n // Minimum probe size: ~0.01 token for tokens with >=2 decimals,\n // otherwise one raw unit for low-decimal tokens.\n const minimumProbeRaw = new BigNumber(1).shiftedBy(\n Math.max(sourceDecimals - 2, 0),\n );\n\n const probeRaw = BigNumber.minimum(\n sourceAmount,\n BigNumber.maximum(probeRawAmount, minimumProbeRaw),\n ).integerValue(BigNumber.ROUND_FLOOR);\n\n if (probeRaw.isLessThanOrEqualTo(0)) {\n return undefined;\n }\n\n return probeRaw.toFixed(0);\n}\n\nfunction fallbackToPhase1(\n phase1Quote: TransactionPayQuote<RelayQuote>,\n message: string,\n paramsToLog?: Record<string, unknown>,\n): TransactionPayQuote<RelayQuote> {\n log(message, paramsToLog);\n return phase1Quote;\n}\n\nfunction hasMatchingGasLimitShape(phase1: number[], phase2: number[]): boolean {\n return phase1.length === phase2.length;\n}\n\nfunction getTotalGasLimit(gasLimits: number[]): number {\n return gasLimits.reduce((total, gasLimit) => total + gasLimit, 0);\n}\n\nfunction markQuoteAsMaxGasStation(\n quote: TransactionPayQuote<RelayQuote>,\n): void {\n quote.original.metamask = {\n ...quote.original.metamask,\n isMaxGasStation: true,\n };\n}\n\nasync function getAtomicMaxQuote({\n amount,\n fullRequest,\n getSingleQuote,\n request,\n}: {\n amount: string;\n fullRequest: PayStrategyGetQuotesRequest;\n getSingleQuote: GetSingleQuoteFn;\n request: QuoteRequest;\n}): Promise<TransactionPayQuote<RelayQuote>> {\n const targetAmount = validatePositiveIntegerString(amount);\n const atomicTransaction = await applyAmountDataUpdates({\n amount: targetAmount,\n messenger: fullRequest.messenger,\n transaction: cloneTransactionForPromotion(fullRequest.transaction),\n });\n\n return getSingleQuote(\n {\n ...request,\n atomic: true,\n targetAmountMinimum: targetAmount,\n },\n { ...fullRequest, transaction: atomicTransaction },\n );\n}\n\nexport function throwAtomicPromotionFailed(error: unknown): never {\n const prefixed = prefixError(error, ATOMIC_PROMOTION_FAILURE_PREFIX);\n throw new QuoteError({\n detail: [prefixed.message],\n message: prefixed.message,\n reason: 'no-quotes',\n });\n}\n\nexport function isSubsidizedAtomicMaxQuote(\n quote: TransactionPayQuote<RelayQuote>,\n): boolean {\n return (\n quote.request.isMaxAmount === true &&\n quote.request.atomic === true &&\n isSubsidizedRelayQuote(quote.original)\n );\n}\n\nfunction validatePositiveIntegerString(value: string): string {\n const amount = new BigNumber(value);\n\n if (!amount.isFinite() || !amount.isInteger() || !amount.isGreaterThan(0)) {\n throw new Error(`Invalid target amount: ${value}`);\n }\n\n return amount.toFixed(0);\n}\n\nfunction cloneTransactionForPromotion(\n transaction: TransactionMeta,\n): TransactionMeta {\n return {\n ...transaction,\n nestedTransactions: transaction.nestedTransactions?.map(\n (nestedTransaction) => ({\n ...nestedTransaction,\n }),\n ),\n requiredAssets: transaction.requiredAssets?.map((requiredAsset) => ({\n ...requiredAsset,\n })),\n };\n}\n\nasync function applyAmountDataUpdates({\n amount,\n messenger,\n transaction,\n}: {\n amount: string;\n messenger: TransactionPayControllerMessenger;\n transaction: TransactionMeta;\n}): Promise<TransactionMeta> {\n const { updates } = await messenger.call(\n 'TransactionPayController:getAmountData',\n {\n amount,\n transaction,\n },\n );\n\n if (!updates.length) {\n throw new Error('getAmountData returned no updates for atomic promotion');\n }\n\n const nestedTransactions = transaction.nestedTransactions?.map(\n (nestedTransaction) => ({\n ...nestedTransaction,\n }),\n );\n\n if (!nestedTransactions?.length) {\n throw new Error('Missing nested transactions for atomic promotion');\n }\n\n for (const { nestedTransactionIndex, data } of updates) {\n if (!nestedTransactions[nestedTransactionIndex]) {\n throw new Error(\n 'getAmountData returned an unusable nested transaction update',\n );\n }\n\n nestedTransactions[nestedTransactionIndex].data = data;\n }\n\n const requiredAssets = transaction.requiredAssets?.map((requiredAsset) => ({\n ...requiredAsset,\n }));\n\n if (!requiredAssets?.[0]) {\n throw new Error('Missing required assets for atomic promotion');\n }\n\n requiredAssets[0].amount = toHex(BigInt(amount));\n\n return {\n ...transaction,\n nestedTransactions,\n requiredAssets,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay-quotes.d.ts","sourceRoot":"","sources":["../../../src/strategy/relay/relay-quotes.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAGV,2BAA2B,EAG3B,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"relay-quotes.d.ts","sourceRoot":"","sources":["../../../src/strategy/relay/relay-quotes.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAGV,2BAA2B,EAG3B,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAuCxB,OAAO,KAAK,EACV,UAAU,EAIX,MAAM,YAAY,CAAC;AAuBpB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAsE5C"}
|
|
@@ -8,7 +8,7 @@ import { ARBITRUM_USDC_ADDRESS, CHAIN_ID_ARBITRUM, CHAIN_ID_HYPERCORE, CHAIN_ID_
|
|
|
8
8
|
import { TransactionPayStrategy } from '../../index.js';
|
|
9
9
|
import { projectLogger } from '../../logger.js';
|
|
10
10
|
import { getFiatValueFromUsd } from '../../utils/amounts.js';
|
|
11
|
-
import { getFeatureFlags, getRelayOriginGasOverhead, getSlippage, getStablecoins, isEIP7702Chain, isRelayExecuteEnabled, } from '../../utils/feature-flags.js';
|
|
11
|
+
import { getFeatureFlags, getRelayOriginGasOverhead, getSlippage, getStablecoins, isAtomicMaxEnabled, isEIP7702Chain, isRelayExecuteEnabled, } from '../../utils/feature-flags.js';
|
|
12
12
|
import { getGasStationCostInSourceTokenRaw, getGasStationEligibility, } from '../../utils/gas-station.js';
|
|
13
13
|
import { calculateGasCost } from '../../utils/gas.js';
|
|
14
14
|
import { estimateQuoteGasLimits } from '../../utils/quote-gas.js';
|
|
@@ -17,7 +17,7 @@ import { TOKEN_TRANSFER_FOUR_BYTE } from './constants.js';
|
|
|
17
17
|
import { applyHyperliquidActivationFee } from './hyperliquid-activation.js';
|
|
18
18
|
import { applyPolymarketDepositWalletOverrides, getPredictWithdrawSafeAddress, isPredictWithdraw, } from './polymarket/withdraw.js';
|
|
19
19
|
import { fetchRelayQuote } from './relay-api.js';
|
|
20
|
-
import {
|
|
20
|
+
import { getRelayMaxQuote, isSubsidizedAtomicMaxQuote, throwAtomicPromotionFailed, } from './relay-max.js';
|
|
21
21
|
import { validateRelayQuotes } from './relay-validation.js';
|
|
22
22
|
const log = createModuleLogger(projectLogger, 'relay-strategy');
|
|
23
23
|
// Buffer applied to the gas cost when reserving native tokens for gas in
|
|
@@ -48,12 +48,29 @@ export async function getRelayQuotes(request) {
|
|
|
48
48
|
.map((normalizedRequest) => applyHyperliquidActivationFee(normalizedRequest, request.messenger, request.transaction, request.signal)));
|
|
49
49
|
log('Normalized requests', normalizedRequests);
|
|
50
50
|
const quotes = await Promise.all(normalizedRequests.map((singleRequest) => getQuoteWithMaxAmountHandling(singleRequest, request)));
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
const atomicMaxQuotes = quotes.filter(isSubsidizedAtomicMaxQuote);
|
|
52
|
+
const otherQuotes = quotes.filter((quote) => !isSubsidizedAtomicMaxQuote(quote));
|
|
53
|
+
if (otherQuotes.length > 0) {
|
|
54
|
+
await validateRelayQuotes({
|
|
55
|
+
messenger: request.messenger,
|
|
56
|
+
quotes: otherQuotes,
|
|
57
|
+
signal: request.signal,
|
|
58
|
+
transaction: request.transaction,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (atomicMaxQuotes.length > 0) {
|
|
62
|
+
try {
|
|
63
|
+
await validateRelayQuotes({
|
|
64
|
+
messenger: request.messenger,
|
|
65
|
+
quotes: atomicMaxQuotes,
|
|
66
|
+
signal: request.signal,
|
|
67
|
+
transaction: request.transaction,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throwAtomicPromotionFailed(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
57
74
|
return quotes;
|
|
58
75
|
}
|
|
59
76
|
catch (error) {
|
|
@@ -66,7 +83,7 @@ async function getQuoteWithMaxAmountHandling(request, fullRequest) {
|
|
|
66
83
|
if (!isMaxAmount) {
|
|
67
84
|
return getQuoteWithPostQuoteGasHandling(request, fullRequest);
|
|
68
85
|
}
|
|
69
|
-
return
|
|
86
|
+
return getRelayMaxQuote(request, fullRequest, getSingleQuote);
|
|
70
87
|
}
|
|
71
88
|
/**
|
|
72
89
|
* For post-quote flows, fetch an initial quote to compute gas cost in source
|
|
@@ -292,7 +309,11 @@ async function processTransactions(transaction, request, requestBody, messenger)
|
|
|
292
309
|
log('Skipping delegation as token transfer or Hypercore deposit');
|
|
293
310
|
return true;
|
|
294
311
|
}
|
|
295
|
-
|
|
312
|
+
// Eligible atomic max requests carry a destination amount, so the calls
|
|
313
|
+
// below use EXACT_OUTPUT rather than the usual max EXACT_INPUT quote.
|
|
314
|
+
if (isMaxAmount &&
|
|
315
|
+
(request.isPostQuote === true ||
|
|
316
|
+
!isAtomicMaxEnabled(messenger, transaction))) {
|
|
296
317
|
throw new Error('Max amount quotes do not support included transactions');
|
|
297
318
|
}
|
|
298
319
|
const delegation = await messenger.call('TransactionPayController:getDelegationTransaction', { transaction });
|