@mixerx/oracles 0.5.4 → 2.0.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/README.md +76 -109
- package/dist/application/ports/ILogger.d.ts +0 -1
- package/dist/application/ports/ILogger.js.map +1 -1
- package/dist/application/ports/index.d.ts +0 -1
- package/dist/application/ports/index.js.map +1 -1
- package/dist/domain/config/TokenDefaults.d.ts +0 -32
- package/dist/domain/config/TokenDefaults.js +7 -74
- package/dist/domain/config/TokenDefaults.js.map +1 -1
- package/dist/domain/config/index.d.ts +1 -2
- package/dist/domain/config/index.js +1 -13
- package/dist/domain/config/index.js.map +1 -1
- package/dist/domain/exceptions.d.ts +8 -4
- package/dist/domain/exceptions.js +12 -3
- package/dist/domain/exceptions.js.map +1 -1
- package/dist/domain/repositories.d.ts +3 -1
- package/dist/domain/repositories.js +10 -0
- package/dist/domain/repositories.js.map +1 -1
- package/dist/factory.d.ts +10 -31
- package/dist/factory.js +14 -24
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/blockchain/staticProvider.d.ts +2 -3
- package/dist/infrastructure/blockchain/staticProvider.js +33 -8
- package/dist/infrastructure/blockchain/staticProvider.js.map +1 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.d.ts +0 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.js +2 -0
- package/dist/infrastructure/config/ProtocolTokenConfig.js.map +1 -1
- package/dist/infrastructure/config/gasOracleConfigs.d.ts +2 -13
- package/dist/infrastructure/config/gasOracleConfigs.js +7 -33
- package/dist/infrastructure/config/gasOracleConfigs.js.map +1 -1
- package/dist/infrastructure/config/index.d.ts +0 -1
- package/dist/infrastructure/config/index.js.map +1 -1
- package/dist/infrastructure/gas/GasPriceOracle.d.ts +12 -2
- package/dist/infrastructure/gas/GasPriceOracle.js +76 -37
- package/dist/infrastructure/gas/GasPriceOracle.js.map +1 -1
- package/dist/infrastructure/mixerx/MixerXFeeOracle.d.ts +36 -41
- package/dist/infrastructure/mixerx/MixerXFeeOracle.js +146 -151
- package/dist/infrastructure/mixerx/MixerXFeeOracle.js.map +1 -1
- package/dist/infrastructure/multicall/MulticallProvider.d.ts +4 -3
- package/dist/infrastructure/multicall/MulticallProvider.js +44 -9
- package/dist/infrastructure/multicall/MulticallProvider.js.map +1 -1
- package/dist/infrastructure/token-price/TokenPriceOracle.d.ts +11 -45
- package/dist/infrastructure/token-price/TokenPriceOracle.js +68 -100
- package/dist/infrastructure/token-price/TokenPriceOracle.js.map +1 -1
- package/dist/infrastructure/token-price/index.d.ts +2 -2
- package/dist/infrastructure/token-price/index.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.js.map +1 -1
- package/package.json +56 -41
- package/dist/application/ports/ILogger.d.ts.map +0 -1
- package/dist/application/ports/index.d.ts.map +0 -1
- package/dist/application/services/TokenPriceService.d.ts +0 -60
- package/dist/application/services/TokenPriceService.d.ts.map +0 -1
- package/dist/application/services/TokenPriceService.js +0 -137
- package/dist/application/services/TokenPriceService.js.map +0 -1
- package/dist/domain/config/TokenDefaults.d.ts.map +0 -1
- package/dist/domain/config/index.d.ts.map +0 -1
- package/dist/domain/exceptions.d.ts.map +0 -1
- package/dist/domain/repositories.d.ts.map +0 -1
- package/dist/factory.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/infrastructure/blockchain/contracts.d.ts +0 -20
- package/dist/infrastructure/blockchain/contracts.d.ts.map +0 -1
- package/dist/infrastructure/blockchain/contracts.js +0 -10
- package/dist/infrastructure/blockchain/contracts.js.map +0 -1
- package/dist/infrastructure/blockchain/staticProvider.d.ts.map +0 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.d.ts.map +0 -1
- package/dist/infrastructure/config/gasOracleConfigs.d.ts.map +0 -1
- package/dist/infrastructure/config/index.d.ts.map +0 -1
- package/dist/infrastructure/gas/GasPriceOracle.d.ts.map +0 -1
- package/dist/infrastructure/mixerx/MixerXFeeOracle.d.ts.map +0 -1
- package/dist/infrastructure/multicall/MulticallProvider.d.ts.map +0 -1
- package/dist/infrastructure/token-price/TokenPriceOracle.d.ts.map +0 -1
- package/dist/infrastructure/token-price/index.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
|
@@ -1,200 +1,200 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MixerXFeeOracle = void 0;
|
|
4
|
-
const GasPriceOracle_1 = require("../gas/GasPriceOracle");
|
|
5
|
-
const TokenPriceOracle_1 = require("../token-price/TokenPriceOracle");
|
|
6
4
|
const ILogger_1 = require("../../application/ports/ILogger");
|
|
7
5
|
const TokenDefaults_1 = require("../../domain/config/TokenDefaults");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* - Refund calculation for non-ETH tokens
|
|
16
|
-
* - EIP-1559 and legacy support
|
|
17
|
-
*/
|
|
6
|
+
const exceptions_1 = require("../../domain/exceptions");
|
|
7
|
+
const repositories_1 = require("../../domain/repositories");
|
|
8
|
+
const GasPriceOracle_1 = require("../gas/GasPriceOracle");
|
|
9
|
+
const TokenPriceOracle_1 = require("../token-price/TokenPriceOracle");
|
|
10
|
+
const DEFAULT_MAX_PRICE_AGE_MS = 120_000;
|
|
11
|
+
const DEFAULT_MAX_REFUND_WEI = 1000000000000000000n;
|
|
12
|
+
const FEE_SCALE = 1000000n;
|
|
18
13
|
class MixerXFeeOracle {
|
|
19
14
|
chainId;
|
|
20
|
-
|
|
15
|
+
relayerFeePartsPerMillion;
|
|
21
16
|
gasPriceOracle;
|
|
22
17
|
tokenPriceOracle;
|
|
23
18
|
priceRepository;
|
|
24
19
|
logger;
|
|
20
|
+
maxPriceAgeMs;
|
|
21
|
+
maxRefundWei;
|
|
22
|
+
supportedCurrencies;
|
|
25
23
|
constructor(options) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
rpcUrl: options.rpcUrl,
|
|
33
|
-
logger: options.logger ?? new ILogger_1.ConsoleLogger('MixerXGasOracle'),
|
|
34
|
-
});
|
|
35
|
-
if (options.tokenPriceOracle) {
|
|
36
|
-
this.tokenPriceOracle = options.tokenPriceOracle;
|
|
37
|
-
}
|
|
38
|
-
else if (options.rpcUrl) {
|
|
39
|
-
this.tokenPriceOracle = new TokenPriceOracle_1.TokenPriceOracle(options.rpcUrl, options.chainId, options.offchainOracleAddress, options.multicallAddress);
|
|
24
|
+
if (!Number.isFinite(options.relayerFeePercent) || options.relayerFeePercent < 0 || options.relayerFeePercent > 0.1) {
|
|
25
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'relayerFeePercent must be between 0 and 0.1');
|
|
26
|
+
}
|
|
27
|
+
this.maxPriceAgeMs = options.maxPriceAgeMs ?? DEFAULT_MAX_PRICE_AGE_MS;
|
|
28
|
+
if (!Number.isInteger(this.maxPriceAgeMs) || this.maxPriceAgeMs < 1_000 || this.maxPriceAgeMs > 3_600_000) {
|
|
29
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'maxPriceAgeMs must be between 1000 and 3600000');
|
|
40
30
|
}
|
|
31
|
+
this.maxRefundWei = options.maxRefundWei ?? DEFAULT_MAX_REFUND_WEI;
|
|
32
|
+
if (this.maxRefundWei < 0n) {
|
|
33
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'maxRefundWei cannot be negative');
|
|
34
|
+
}
|
|
35
|
+
this.chainId = options.chainId;
|
|
36
|
+
this.relayerFeePartsPerMillion = BigInt(Math.round(options.relayerFeePercent * Number(FEE_SCALE)));
|
|
37
|
+
this.logger = options.logger ?? new ILogger_1.NullLogger();
|
|
38
|
+
this.gasPriceOracle = options.gasPriceOracle ?? new GasPriceOracle_1.GasPriceOracle({
|
|
39
|
+
chainId: options.chainId,
|
|
40
|
+
rpcUrl: options.rpcUrl,
|
|
41
|
+
logger: this.logger,
|
|
42
|
+
rpcTimeoutMs: options.rpcTimeoutMs,
|
|
43
|
+
maxGasPriceGwei: options.maxGasPriceGwei,
|
|
44
|
+
});
|
|
45
|
+
this.tokenPriceOracle = options.tokenPriceOracle ?? new TokenPriceOracle_1.TokenPriceOracle(options.rpcUrl, options.chainId, options.offchainOracleAddress, options.multicallAddress, undefined, this.logger, options.rpcTimeoutMs);
|
|
46
|
+
this.supportedCurrencies = new Set([
|
|
47
|
+
'eth',
|
|
48
|
+
...this.tokenPriceOracle.getSupportedTokens().map((token) => token.symbol),
|
|
49
|
+
]);
|
|
41
50
|
this.priceRepository = options.priceRepository;
|
|
42
|
-
this.logger = options.logger ?? new ILogger_1.ConsoleLogger('MixerXFeeOracle');
|
|
43
51
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Calculate withdrawal fee using the fee quote/validation gas policy.
|
|
46
|
-
*/
|
|
47
52
|
async calculateWithdrawalFee(input) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const gasParams = await this.getGasParametersForTx({
|
|
51
|
-
const gasLimit = this.getGasLimit(currencyLower, txType);
|
|
52
|
-
const gasPriceWei = this.extractGasPriceWei(gasParams);
|
|
53
|
-
const gasCostWei = this.calculateGasCost(gasParams, currencyLower, txType);
|
|
54
|
-
const relayerFee = this.calculateRelayerFee(amount);
|
|
55
|
-
this.logger.debug('MixerXFeeOracle fee inputs', {
|
|
56
|
-
chainId: this.chainId,
|
|
57
|
-
currency: currencyLower,
|
|
58
|
-
amount: amount.toString(),
|
|
59
|
-
decimals,
|
|
60
|
-
refund,
|
|
53
|
+
const currency = this.validateFeeInput(input);
|
|
54
|
+
const txType = input.txType ?? 'relayer_withdrawal';
|
|
55
|
+
const gasParams = await this.getGasParametersForTx({
|
|
61
56
|
txType,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
maxFeePerGas: gasParams.maxFeePerGas,
|
|
65
|
-
maxPriorityFeePerGas: gasParams.maxPriorityFeePerGas,
|
|
66
|
-
gasPrice: gasParams.gasPrice,
|
|
67
|
-
relayerFeePercent: this.relayerFeePercent,
|
|
68
|
-
bumpPercent: gasParams.bumpPercent,
|
|
69
|
-
gasPurpose: purpose,
|
|
57
|
+
purpose: this.normalizePurpose(input.purpose ?? 'fee'),
|
|
58
|
+
isLegacy: false,
|
|
70
59
|
});
|
|
71
|
-
|
|
60
|
+
const gasLimit = input.gasLimit;
|
|
61
|
+
const gasPriceWei = this.effectiveGasPrice(gasParams);
|
|
62
|
+
const gasCostWei = gasPriceWei * BigInt(gasLimit);
|
|
63
|
+
const relayerFee = (input.amount * this.relayerFeePartsPerMillion) / FEE_SCALE;
|
|
64
|
+
if (currency === 'eth') {
|
|
65
|
+
if (input.refund !== undefined && this.parseRefund(input.refund) !== 0n) {
|
|
66
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'refund must be zero for native ETH withdrawals');
|
|
67
|
+
}
|
|
72
68
|
const totalFee = gasCostWei + relayerFee;
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
relayerFee: relayerFee.toString(),
|
|
76
|
-
totalFee: totalFee.toString(),
|
|
77
|
-
currency: 'eth',
|
|
78
|
-
});
|
|
79
|
-
return this.withGasDetails({
|
|
69
|
+
this.assertTotalFee(totalFee, input.amount);
|
|
70
|
+
return this.result({
|
|
80
71
|
gasCost: gasCostWei,
|
|
81
72
|
relayerFee,
|
|
82
73
|
refundAmount: 0n,
|
|
83
74
|
totalFee,
|
|
84
|
-
currency
|
|
75
|
+
currency,
|
|
85
76
|
gasLimit,
|
|
86
77
|
gasPriceWei,
|
|
87
78
|
gasParams,
|
|
88
79
|
txType,
|
|
89
|
-
|
|
80
|
+
priceSource: 'native',
|
|
90
81
|
});
|
|
91
82
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
const totalFee = gasCostInToken + relayerFeeInToken + refundInToken;
|
|
105
|
-
this.logger.debug('MixerXFeeOracle fee breakdown', {
|
|
106
|
-
tokenPriceInEth: tokenPriceInEth.toString(),
|
|
107
|
-
gasCostInToken: gasCostInToken.toString(),
|
|
108
|
-
relayerFeeInToken: relayerFeeInToken.toString(),
|
|
109
|
-
refundInToken: refundInToken.toString(),
|
|
110
|
-
totalFee: totalFee.toString(),
|
|
111
|
-
});
|
|
112
|
-
return this.withGasDetails({
|
|
113
|
-
gasCost: gasCostInToken,
|
|
114
|
-
relayerFee: relayerFeeInToken,
|
|
115
|
-
refundAmount: refundInToken,
|
|
83
|
+
const quote = await this.getFreshPrice(currency);
|
|
84
|
+
const decimalsFactor = 10n ** BigInt(input.decimals);
|
|
85
|
+
const gasCost = (gasCostWei * decimalsFactor) / quote.priceInEthWei;
|
|
86
|
+
const refundWei = this.parseRefund(input.refund ?? '0');
|
|
87
|
+
const refundAmount = (refundWei * decimalsFactor) / quote.priceInEthWei;
|
|
88
|
+
const totalFee = gasCost + relayerFee + refundAmount;
|
|
89
|
+
this.assertTotalFee(totalFee, input.amount);
|
|
90
|
+
return this.result({
|
|
91
|
+
gasCost,
|
|
92
|
+
relayerFee,
|
|
93
|
+
refundAmount,
|
|
116
94
|
totalFee,
|
|
117
|
-
currency
|
|
95
|
+
currency,
|
|
118
96
|
gasLimit,
|
|
119
97
|
gasPriceWei,
|
|
120
98
|
gasParams,
|
|
121
99
|
txType,
|
|
122
|
-
|
|
100
|
+
tokenPriceInEth: quote.priceInEthWei,
|
|
101
|
+
priceSource: quote.source,
|
|
102
|
+
priceObservedAtMs: quote.observedAtMs,
|
|
123
103
|
});
|
|
124
104
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Get gas parameters for a protocol transaction and purpose.
|
|
127
|
-
*/
|
|
128
105
|
async getGasParametersForTx(input = {}) {
|
|
129
106
|
const txType = input.txType ?? 'relayer_withdrawal';
|
|
130
|
-
const purpose = input.purpose ?? 'send';
|
|
131
|
-
const bumpPercent =
|
|
107
|
+
const purpose = this.normalizePurpose(input.purpose ?? 'send');
|
|
108
|
+
const bumpPercent = purpose === 'send' ? 0 : (0, TokenDefaults_1.getBumpPercent)(txType);
|
|
132
109
|
const gasParams = await this.gasPriceOracle.getTxGasParams({
|
|
133
110
|
isLegacy: input.isLegacy ?? false,
|
|
134
111
|
bumpPercent,
|
|
135
112
|
legacySpeed: 'fast',
|
|
136
113
|
});
|
|
137
|
-
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
114
|
+
return { ...gasParams, txType, purpose, bumpPercent };
|
|
115
|
+
}
|
|
116
|
+
async assertReady() {
|
|
117
|
+
await this.tokenPriceOracle.fetchPrices();
|
|
118
|
+
}
|
|
119
|
+
async getGasParameters(isLegacy = false) {
|
|
120
|
+
return this.gasPriceOracle.getTxGasParams({ isLegacy });
|
|
143
121
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const gasPriceWei = this.extractGasPriceWei(gasParams);
|
|
147
|
-
return gasPriceWei * BigInt(gasLimit);
|
|
122
|
+
getChainId() {
|
|
123
|
+
return this.chainId;
|
|
148
124
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
125
|
+
validateFeeInput(input) {
|
|
126
|
+
const currency = input.currency.trim().toLowerCase();
|
|
127
|
+
if (!/^[a-z0-9]{2,16}$/.test(currency)) {
|
|
128
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'currency must be a valid protocol token symbol');
|
|
129
|
+
}
|
|
130
|
+
if (!this.supportedCurrencies.has(currency)) {
|
|
131
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', `Unsupported MixerX currency: ${currency}`);
|
|
132
|
+
}
|
|
133
|
+
if (input.amount <= 0n) {
|
|
134
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'amount must be greater than zero');
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isInteger(input.decimals) || input.decimals < 0 || input.decimals > 255) {
|
|
137
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'decimals must be an integer between 0 and 255');
|
|
138
|
+
}
|
|
139
|
+
if (!Number.isInteger(input.gasLimit) || input.gasLimit < 21_000 || input.gasLimit > 5_000_000) {
|
|
140
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'gasLimit must be an integer between 21000 and 5000000');
|
|
141
|
+
}
|
|
142
|
+
return currency;
|
|
152
143
|
}
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
parseRefund(raw) {
|
|
145
|
+
if (!/^\d+$/.test(raw)) {
|
|
146
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'refund must be an unsigned base-10 integer string');
|
|
147
|
+
}
|
|
148
|
+
const refund = BigInt(raw);
|
|
149
|
+
if (refund > this.maxRefundWei) {
|
|
150
|
+
throw new exceptions_1.OracleError('ECONOMIC_LIMIT_EXCEEDED', 'refund exceeds maxRefundWei');
|
|
157
151
|
}
|
|
158
|
-
return
|
|
152
|
+
return refund;
|
|
159
153
|
}
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
154
|
+
async getFreshPrice(symbol) {
|
|
155
|
+
if (!this.priceRepository) {
|
|
156
|
+
throw new exceptions_1.OracleError('TOKEN_PRICE_UNAVAILABLE', `No fresh price repository configured for ${symbol}`);
|
|
163
157
|
}
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
const [price, observedAtMs] = await Promise.all([
|
|
159
|
+
this.priceRepository.getPrice(symbol),
|
|
160
|
+
this.priceRepository.getLastUpdateTimestamp(symbol),
|
|
161
|
+
]);
|
|
162
|
+
const quote = (0, repositories_1.toPriceQuote)(price, observedAtMs);
|
|
163
|
+
if (!quote) {
|
|
164
|
+
throw new exceptions_1.OracleError('TOKEN_PRICE_UNAVAILABLE', `Price unavailable for ${symbol}`);
|
|
166
165
|
}
|
|
167
|
-
|
|
166
|
+
if (Date.now() - quote.observedAtMs > this.maxPriceAgeMs || quote.observedAtMs > Date.now() + 5_000) {
|
|
167
|
+
throw new exceptions_1.OracleError('TOKEN_PRICE_STALE', `Price is stale for ${symbol}`);
|
|
168
|
+
}
|
|
169
|
+
return quote;
|
|
168
170
|
}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
return BigInt(
|
|
171
|
+
effectiveGasPrice(params) {
|
|
172
|
+
if (params.gasPrice) {
|
|
173
|
+
return BigInt(params.gasPrice);
|
|
172
174
|
}
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
+
if (params.baseFeePerGas && params.maxFeePerGas && params.maxPriorityFeePerGas) {
|
|
176
|
+
const maxFee = BigInt(params.maxFeePerGas);
|
|
177
|
+
const likelyEffective = BigInt(params.baseFeePerGas) + BigInt(params.maxPriorityFeePerGas);
|
|
178
|
+
return likelyEffective < maxFee ? likelyEffective : maxFee;
|
|
175
179
|
}
|
|
176
|
-
throw new
|
|
180
|
+
throw new exceptions_1.OracleError('RPC_REQUEST_FAILED', 'Gas price response is incomplete');
|
|
177
181
|
}
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
const prices = await this.tokenPriceOracle.fetchPrices();
|
|
187
|
-
const price = prices[symbol.toLowerCase()];
|
|
188
|
-
if (price)
|
|
189
|
-
return BigInt(price);
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
// Ignore error, will return null.
|
|
193
|
-
}
|
|
182
|
+
assertTotalFee(totalFee, amount) {
|
|
183
|
+
if (totalFee < 0n || totalFee > amount) {
|
|
184
|
+
throw new exceptions_1.OracleError('ECONOMIC_LIMIT_EXCEEDED', 'Total fee must be between zero and withdrawal amount');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
normalizePurpose(purpose) {
|
|
188
|
+
if (purpose === 'fee_quote' || purpose === 'fee_validation') {
|
|
189
|
+
return 'fee';
|
|
194
190
|
}
|
|
195
|
-
return
|
|
191
|
+
return purpose;
|
|
196
192
|
}
|
|
197
|
-
|
|
193
|
+
result(input) {
|
|
194
|
+
this.logger.debug('Withdrawal fee calculated', {
|
|
195
|
+
priceSource: input.priceSource,
|
|
196
|
+
gasPurpose: 'fee',
|
|
197
|
+
});
|
|
198
198
|
return {
|
|
199
199
|
gasCost: input.gasCost,
|
|
200
200
|
relayerFee: input.relayerFee,
|
|
@@ -203,23 +203,18 @@ class MixerXFeeOracle {
|
|
|
203
203
|
currency: input.currency,
|
|
204
204
|
gasLimit: input.gasLimit,
|
|
205
205
|
gasPriceWei: input.gasPriceWei,
|
|
206
|
+
baseFeePerGas: input.gasParams.baseFeePerGas,
|
|
206
207
|
maxFeePerGas: input.gasParams.maxFeePerGas,
|
|
207
208
|
maxPriorityFeePerGas: input.gasParams.maxPriorityFeePerGas,
|
|
208
209
|
gasPrice: input.gasParams.gasPrice,
|
|
210
|
+
tokenPriceInEth: input.tokenPriceInEth,
|
|
211
|
+
priceSource: input.priceSource,
|
|
212
|
+
priceObservedAtMs: input.priceObservedAtMs,
|
|
209
213
|
bumpPercent: input.gasParams.bumpPercent,
|
|
210
214
|
txType: input.txType,
|
|
211
|
-
gasPurpose:
|
|
215
|
+
gasPurpose: 'fee',
|
|
212
216
|
};
|
|
213
217
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Get gas parameters without protocol policy. Prefer getGasParametersForTx for relayer operations.
|
|
216
|
-
*/
|
|
217
|
-
async getGasParameters(isLegacy = false) {
|
|
218
|
-
return this.gasPriceOracle.getTxGasParams({ isLegacy });
|
|
219
|
-
}
|
|
220
|
-
getChainId() {
|
|
221
|
-
return this.chainId;
|
|
222
|
-
}
|
|
223
218
|
}
|
|
224
219
|
exports.MixerXFeeOracle = MixerXFeeOracle;
|
|
225
220
|
//# sourceMappingURL=MixerXFeeOracle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MixerXFeeOracle.js","sourceRoot":"","sources":["../../../src/infrastructure/mixerx/MixerXFeeOracle.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MixerXFeeOracle.js","sourceRoot":"","sources":["../../../src/infrastructure/mixerx/MixerXFeeOracle.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ILogger } from '../../application/ports/ILogger';\r\nimport { NullLogger } from '../../application/ports/ILogger';\r\nimport { getBumpPercent } from '../../domain/config/TokenDefaults';\r\nimport { OracleError } from '../../domain/exceptions';\r\nimport { toPriceQuote, type ITokenPriceRepository } from '../../domain/repositories';\r\nimport type { TokenPriceQuote } from '../../types';\r\nimport { GasPriceOracle, type GasPriceParams } from '../gas/GasPriceOracle';\r\nimport type { ChainId } from '../config/gasOracleConfigs';\r\nimport { TokenPriceOracle } from '../token-price/TokenPriceOracle';\r\n\r\nconst DEFAULT_MAX_PRICE_AGE_MS = 120_000;\r\nconst DEFAULT_MAX_REFUND_WEI = 1_000_000_000_000_000_000n;\r\nconst FEE_SCALE = 1_000_000n;\r\n\r\nexport type MixerXTxType = 'relayer_withdrawal' | 'user_withdrawal' | 'relayer_withdrawal_check_v4';\r\nexport type MixerXGasPurpose = 'fee' | 'send' | 'replacement';\r\nexport type LegacyFeePurpose = 'fee_quote' | 'fee_validation';\r\n\r\nexport interface MixerXFeeOracleOptions {\r\n chainId: ChainId;\r\n rpcUrl: string;\r\n relayerFeePercent: number;\r\n gasPriceOracle?: GasPriceOracle;\r\n tokenPriceOracle?: TokenPriceOracle;\r\n priceRepository?: ITokenPriceRepository;\r\n logger?: ILogger;\r\n multicallAddress?: string;\r\n offchainOracleAddress?: string;\r\n rpcTimeoutMs?: number;\r\n maxGasPriceGwei?: number;\r\n maxPriceAgeMs?: number;\r\n maxRefundWei?: bigint;\r\n}\r\n\r\nexport interface MixerXWithdrawalFeeInput {\r\n currency: string;\r\n amount: bigint;\r\n decimals: number;\r\n /** Gas limit obtained from a simulation or eth_estimateGas for this exact transaction. */\r\n gasLimit: number;\r\n refund?: string;\r\n txType?: MixerXTxType;\r\n purpose?: 'fee' | LegacyFeePurpose;\r\n}\r\n\r\nexport interface MixerXGasParametersForTxInput {\r\n txType?: MixerXTxType;\r\n purpose?: MixerXGasPurpose | LegacyFeePurpose;\r\n isLegacy?: boolean;\r\n}\r\n\r\nexport interface MixerXGasParametersForTxResult extends GasPriceParams {\r\n txType: MixerXTxType;\r\n purpose: MixerXGasPurpose;\r\n bumpPercent: number;\r\n}\r\n\r\nexport interface MixerXFeeCalculationResult {\r\n gasCost: bigint;\r\n relayerFee: bigint;\r\n refundAmount: bigint;\r\n totalFee: bigint;\r\n currency: string;\r\n gasLimit: number;\r\n gasPriceWei: bigint;\r\n baseFeePerGas?: string;\r\n maxFeePerGas?: string;\r\n maxPriorityFeePerGas?: string;\r\n gasPrice?: string;\r\n tokenPriceInEth?: bigint;\r\n priceSource: 'native' | 'repository';\r\n priceObservedAtMs?: number;\r\n bumpPercent: number;\r\n txType: MixerXTxType;\r\n gasPurpose: 'fee';\r\n}\r\n\r\nexport type WithdrawalFeeInput = MixerXWithdrawalFeeInput;\r\nexport type FeeCalculationResult = MixerXFeeCalculationResult;\r\n\r\nexport class MixerXFeeOracle {\r\n private readonly chainId: ChainId;\r\n private readonly relayerFeePartsPerMillion: bigint;\r\n private readonly gasPriceOracle: GasPriceOracle;\r\n private readonly tokenPriceOracle: TokenPriceOracle;\r\n private readonly priceRepository?: ITokenPriceRepository;\r\n private readonly logger: ILogger;\r\n private readonly maxPriceAgeMs: number;\r\n private readonly maxRefundWei: bigint;\r\n private readonly supportedCurrencies: ReadonlySet<string>;\r\n\r\n constructor(options: MixerXFeeOracleOptions) {\r\n if (!Number.isFinite(options.relayerFeePercent) || options.relayerFeePercent < 0 || options.relayerFeePercent > 0.1) {\r\n throw new OracleError('INVALID_CONFIG', 'relayerFeePercent must be between 0 and 0.1');\r\n }\r\n this.maxPriceAgeMs = options.maxPriceAgeMs ?? DEFAULT_MAX_PRICE_AGE_MS;\r\n if (!Number.isInteger(this.maxPriceAgeMs) || this.maxPriceAgeMs < 1_000 || this.maxPriceAgeMs > 3_600_000) {\r\n throw new OracleError('INVALID_CONFIG', 'maxPriceAgeMs must be between 1000 and 3600000');\r\n }\r\n this.maxRefundWei = options.maxRefundWei ?? DEFAULT_MAX_REFUND_WEI;\r\n if (this.maxRefundWei < 0n) {\r\n throw new OracleError('INVALID_CONFIG', 'maxRefundWei cannot be negative');\r\n }\r\n\r\n this.chainId = options.chainId;\r\n this.relayerFeePartsPerMillion = BigInt(Math.round(options.relayerFeePercent * Number(FEE_SCALE)));\r\n this.logger = options.logger ?? new NullLogger();\r\n this.gasPriceOracle = options.gasPriceOracle ?? new GasPriceOracle({\r\n chainId: options.chainId,\r\n rpcUrl: options.rpcUrl,\r\n logger: this.logger,\r\n rpcTimeoutMs: options.rpcTimeoutMs,\r\n maxGasPriceGwei: options.maxGasPriceGwei,\r\n });\r\n this.tokenPriceOracle = options.tokenPriceOracle ?? new TokenPriceOracle(\r\n options.rpcUrl,\r\n options.chainId,\r\n options.offchainOracleAddress,\r\n options.multicallAddress,\r\n undefined,\r\n this.logger,\r\n options.rpcTimeoutMs,\r\n );\r\n this.supportedCurrencies = new Set([\r\n 'eth',\r\n ...this.tokenPriceOracle.getSupportedTokens().map((token) => token.symbol),\r\n ]);\r\n this.priceRepository = options.priceRepository;\r\n }\r\n\r\n async calculateWithdrawalFee(input: MixerXWithdrawalFeeInput): Promise<MixerXFeeCalculationResult> {\r\n const currency = this.validateFeeInput(input);\r\n const txType = input.txType ?? 'relayer_withdrawal';\r\n const gasParams = await this.getGasParametersForTx({\r\n txType,\r\n purpose: this.normalizePurpose(input.purpose ?? 'fee'),\r\n isLegacy: false,\r\n });\r\n const gasLimit = input.gasLimit;\r\n const gasPriceWei = this.effectiveGasPrice(gasParams);\r\n const gasCostWei = gasPriceWei * BigInt(gasLimit);\r\n const relayerFee = (input.amount * this.relayerFeePartsPerMillion) / FEE_SCALE;\r\n\r\n if (currency === 'eth') {\r\n if (input.refund !== undefined && this.parseRefund(input.refund) !== 0n) {\r\n throw new OracleError('INVALID_INPUT', 'refund must be zero for native ETH withdrawals');\r\n }\r\n const totalFee = gasCostWei + relayerFee;\r\n this.assertTotalFee(totalFee, input.amount);\r\n return this.result({\r\n gasCost: gasCostWei,\r\n relayerFee,\r\n refundAmount: 0n,\r\n totalFee,\r\n currency,\r\n gasLimit,\r\n gasPriceWei,\r\n gasParams,\r\n txType,\r\n priceSource: 'native',\r\n });\r\n }\r\n\r\n const quote = await this.getFreshPrice(currency);\r\n const decimalsFactor = 10n ** BigInt(input.decimals);\r\n const gasCost = (gasCostWei * decimalsFactor) / quote.priceInEthWei;\r\n const refundWei = this.parseRefund(input.refund ?? '0');\r\n const refundAmount = (refundWei * decimalsFactor) / quote.priceInEthWei;\r\n const totalFee = gasCost + relayerFee + refundAmount;\r\n this.assertTotalFee(totalFee, input.amount);\r\n\r\n return this.result({\r\n gasCost,\r\n relayerFee,\r\n refundAmount,\r\n totalFee,\r\n currency,\r\n gasLimit,\r\n gasPriceWei,\r\n gasParams,\r\n txType,\r\n tokenPriceInEth: quote.priceInEthWei,\r\n priceSource: quote.source,\r\n priceObservedAtMs: quote.observedAtMs,\r\n });\r\n }\r\n\r\n async getGasParametersForTx(\r\n input: MixerXGasParametersForTxInput = {},\r\n ): Promise<MixerXGasParametersForTxResult> {\r\n const txType = input.txType ?? 'relayer_withdrawal';\r\n const purpose = this.normalizePurpose(input.purpose ?? 'send');\r\n const bumpPercent = purpose === 'send' ? 0 : getBumpPercent(txType);\r\n const gasParams = await this.gasPriceOracle.getTxGasParams({\r\n isLegacy: input.isLegacy ?? false,\r\n bumpPercent,\r\n legacySpeed: 'fast',\r\n });\r\n return { ...gasParams, txType, purpose, bumpPercent };\r\n }\r\n\r\n async assertReady(): Promise<void> {\r\n await this.tokenPriceOracle.fetchPrices();\r\n }\r\n\r\n async getGasParameters(isLegacy: boolean = false): Promise<GasPriceParams> {\r\n return this.gasPriceOracle.getTxGasParams({ isLegacy });\r\n }\r\n\r\n getChainId(): ChainId {\r\n return this.chainId;\r\n }\r\n\r\n private validateFeeInput(input: MixerXWithdrawalFeeInput): string {\r\n const currency = input.currency.trim().toLowerCase();\r\n if (!/^[a-z0-9]{2,16}$/.test(currency)) {\r\n throw new OracleError('INVALID_INPUT', 'currency must be a valid protocol token symbol');\r\n }\r\n if (!this.supportedCurrencies.has(currency)) {\r\n throw new OracleError('INVALID_INPUT', `Unsupported MixerX currency: ${currency}`);\r\n }\r\n if (input.amount <= 0n) {\r\n throw new OracleError('INVALID_INPUT', 'amount must be greater than zero');\r\n }\r\n if (!Number.isInteger(input.decimals) || input.decimals < 0 || input.decimals > 255) {\r\n throw new OracleError('INVALID_INPUT', 'decimals must be an integer between 0 and 255');\r\n }\r\n if (!Number.isInteger(input.gasLimit) || input.gasLimit < 21_000 || input.gasLimit > 5_000_000) {\r\n throw new OracleError('INVALID_INPUT', 'gasLimit must be an integer between 21000 and 5000000');\r\n }\r\n return currency;\r\n }\r\n\r\n private parseRefund(raw: string): bigint {\r\n if (!/^\\d+$/.test(raw)) {\r\n throw new OracleError('INVALID_INPUT', 'refund must be an unsigned base-10 integer string');\r\n }\r\n const refund = BigInt(raw);\r\n if (refund > this.maxRefundWei) {\r\n throw new OracleError('ECONOMIC_LIMIT_EXCEEDED', 'refund exceeds maxRefundWei');\r\n }\r\n return refund;\r\n }\r\n\r\n private async getFreshPrice(symbol: string): Promise<TokenPriceQuote> {\r\n if (!this.priceRepository) {\r\n throw new OracleError('TOKEN_PRICE_UNAVAILABLE', `No fresh price repository configured for ${symbol}`);\r\n }\r\n const [price, observedAtMs] = await Promise.all([\r\n this.priceRepository.getPrice(symbol),\r\n this.priceRepository.getLastUpdateTimestamp(symbol),\r\n ]);\r\n const quote = toPriceQuote(price, observedAtMs);\r\n if (!quote) {\r\n throw new OracleError('TOKEN_PRICE_UNAVAILABLE', `Price unavailable for ${symbol}`);\r\n }\r\n if (Date.now() - quote.observedAtMs > this.maxPriceAgeMs || quote.observedAtMs > Date.now() + 5_000) {\r\n throw new OracleError('TOKEN_PRICE_STALE', `Price is stale for ${symbol}`);\r\n }\r\n return quote;\r\n }\r\n\r\n private effectiveGasPrice(params: GasPriceParams): bigint {\r\n if (params.gasPrice) {\r\n return BigInt(params.gasPrice);\r\n }\r\n if (params.baseFeePerGas && params.maxFeePerGas && params.maxPriorityFeePerGas) {\r\n const maxFee = BigInt(params.maxFeePerGas);\r\n const likelyEffective = BigInt(params.baseFeePerGas) + BigInt(params.maxPriorityFeePerGas);\r\n return likelyEffective < maxFee ? likelyEffective : maxFee;\r\n }\r\n throw new OracleError('RPC_REQUEST_FAILED', 'Gas price response is incomplete');\r\n }\r\n\r\n private assertTotalFee(totalFee: bigint, amount: bigint): void {\r\n if (totalFee < 0n || totalFee > amount) {\r\n throw new OracleError('ECONOMIC_LIMIT_EXCEEDED', 'Total fee must be between zero and withdrawal amount');\r\n }\r\n }\r\n\r\n private normalizePurpose(purpose: MixerXGasPurpose | LegacyFeePurpose): MixerXGasPurpose {\r\n if (purpose === 'fee_quote' || purpose === 'fee_validation') {\r\n return 'fee';\r\n }\r\n return purpose;\r\n }\r\n\r\n private result(input: {\r\n gasCost: bigint;\r\n relayerFee: bigint;\r\n refundAmount: bigint;\r\n totalFee: bigint;\r\n currency: string;\r\n gasLimit: number;\r\n gasPriceWei: bigint;\r\n gasParams: MixerXGasParametersForTxResult;\r\n txType: MixerXTxType;\r\n tokenPriceInEth?: bigint;\r\n priceSource: 'native' | 'repository';\r\n priceObservedAtMs?: number;\r\n }): MixerXFeeCalculationResult {\r\n this.logger.debug('Withdrawal fee calculated', {\r\n priceSource: input.priceSource,\r\n gasPurpose: 'fee',\r\n });\r\n return {\r\n gasCost: input.gasCost,\r\n relayerFee: input.relayerFee,\r\n refundAmount: input.refundAmount,\r\n totalFee: input.totalFee,\r\n currency: input.currency,\r\n gasLimit: input.gasLimit,\r\n gasPriceWei: input.gasPriceWei,\r\n baseFeePerGas: input.gasParams.baseFeePerGas,\r\n maxFeePerGas: input.gasParams.maxFeePerGas,\r\n maxPriorityFeePerGas: input.gasParams.maxPriorityFeePerGas,\r\n gasPrice: input.gasParams.gasPrice,\r\n tokenPriceInEth: input.tokenPriceInEth,\r\n priceSource: input.priceSource,\r\n priceObservedAtMs: input.priceObservedAtMs,\r\n bumpPercent: input.gasParams.bumpPercent,\r\n txType: input.txType,\r\n gasPurpose: 'fee',\r\n };\r\n }\r\n}\r\n"]}
|
|
@@ -9,7 +9,8 @@ export interface CallResult {
|
|
|
9
9
|
}
|
|
10
10
|
export declare class MulticallProvider {
|
|
11
11
|
private readonly provider;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
private readonly multicall;
|
|
13
|
+
private readonly chainId;
|
|
14
|
+
constructor(rpcUrl: string, multicallAddress: string | undefined, chainId: ChainId | number, rpcTimeoutMs?: number);
|
|
15
|
+
aggregate(calls: readonly Call[]): Promise<CallResult[]>;
|
|
14
16
|
}
|
|
15
|
-
//# sourceMappingURL=MulticallProvider.d.ts.map
|
|
@@ -1,22 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MulticallProvider = void 0;
|
|
4
|
+
const ethers_1 = require("ethers");
|
|
5
|
+
const exceptions_1 = require("../../domain/exceptions");
|
|
4
6
|
const staticProvider_1 = require("../blockchain/staticProvider");
|
|
7
|
+
const gasOracleConfigs_1 = require("../config/gasOracleConfigs");
|
|
8
|
+
const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
9
|
+
const MAX_CALLS = 64;
|
|
10
|
+
const MULTICALL3_ABI = [
|
|
11
|
+
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',
|
|
12
|
+
];
|
|
5
13
|
class MulticallProvider {
|
|
6
14
|
provider;
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
multicall;
|
|
16
|
+
chainId;
|
|
17
|
+
constructor(rpcUrl, multicallAddress = MULTICALL3_ADDRESS, chainId, rpcTimeoutMs) {
|
|
18
|
+
(0, gasOracleConfigs_1.assertSepoliaChainId)(chainId);
|
|
19
|
+
if (!ethers_1.ethers.isAddress(multicallAddress)) {
|
|
20
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'multicallAddress must be a valid EVM address');
|
|
21
|
+
}
|
|
22
|
+
this.chainId = chainId;
|
|
23
|
+
this.provider = (0, staticProvider_1.createVerifiedJsonRpcProvider)(rpcUrl, chainId, { batchMaxCount: 1 }, rpcTimeoutMs);
|
|
24
|
+
this.multicall = new ethers_1.ethers.Contract(multicallAddress, MULTICALL3_ABI, this.provider);
|
|
9
25
|
}
|
|
10
26
|
async aggregate(calls) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
if (calls.length > MAX_CALLS) {
|
|
28
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', `Multicall supports at most ${String(MAX_CALLS)} calls`);
|
|
29
|
+
}
|
|
30
|
+
if (calls.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
for (const call of calls) {
|
|
34
|
+
if (!ethers_1.ethers.isAddress(call.target) || !ethers_1.ethers.isHexString(call.callData)) {
|
|
35
|
+
throw new exceptions_1.OracleError('INVALID_INPUT', 'Every multicall target and calldata value must be valid');
|
|
15
36
|
}
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await (0, staticProvider_1.assertProviderNetwork)(this.provider, this.chainId);
|
|
40
|
+
const rawResults = (await this.multicall.aggregate3.staticCall(calls.map((call) => ({ ...call, allowFailure: true }))));
|
|
41
|
+
if (rawResults.length !== calls.length) {
|
|
42
|
+
throw new exceptions_1.OracleError('RPC_REQUEST_FAILED', 'Multicall returned an unexpected number of results');
|
|
18
43
|
}
|
|
19
|
-
|
|
44
|
+
return rawResults.map((result) => ({
|
|
45
|
+
success: result.success,
|
|
46
|
+
returnData: result.returnData,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof exceptions_1.OracleError) {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
throw new exceptions_1.OracleError('RPC_REQUEST_FAILED', 'Multicall RPC request failed');
|
|
54
|
+
}
|
|
20
55
|
}
|
|
21
56
|
}
|
|
22
57
|
exports.MulticallProvider = MulticallProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MulticallProvider.js","sourceRoot":"","sources":["../../../src/infrastructure/multicall/MulticallProvider.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"MulticallProvider.js","sourceRoot":"","sources":["../../../src/infrastructure/multicall/MulticallProvider.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAChC,wDAAsD;AAEtD,iEAGsC;AACtC,iEAAkE;AAElE,MAAM,kBAAkB,GAAG,4CAA4C,CAAC;AACxE,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,cAAc,GAAG;IACrB,4JAA4J;CACpJ,CAAC;AAYX,MAAa,iBAAiB;IACX,QAAQ,CAAyB;IACjC,SAAS,CAAkB;IAC3B,OAAO,CAAS;IAEjC,YACE,MAAc,EACd,mBAA2B,kBAAkB,EAC7C,OAAyB,EACzB,YAAqB;QAErB,IAAA,uCAAoB,EAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,eAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,wBAAW,CAAC,gBAAgB,EAAE,8CAA8C,CAAC,CAAC;QAC1F,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,IAAA,8CAA6B,EAAC,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;QACnG,IAAI,CAAC,SAAS,GAAG,IAAI,eAAM,CAAC,QAAQ,CAAC,gBAAgB,EAAE,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxF,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAsB;QACpC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,wBAAW,CAAC,eAAe,EAAE,8BAA8B,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClG,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,eAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzE,MAAM,IAAI,wBAAW,CAAC,eAAe,EAAE,yDAAyD,CAAC,CAAC;YACpG,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAA,sCAAqB,EAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACzD,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CACvD,CAAwD,CAAC;YAE1D,IAAI,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;gBACvC,MAAM,IAAI,wBAAW,CAAC,oBAAoB,EAAE,oDAAoD,CAAC,CAAC;YACpG,CAAC;YAED,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBACjC,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CAAC,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,wBAAW,EAAE,CAAC;gBACjC,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,wBAAW,CAAC,oBAAoB,EAAE,8BAA8B,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;CACF;AAxDD,8CAwDC","sourcesContent":["import { ethers } from 'ethers';\r\nimport { OracleError } from '../../domain/exceptions';\r\nimport type { ChainId } from '../../types';\r\nimport {\r\n assertProviderNetwork,\r\n createVerifiedJsonRpcProvider,\r\n} from '../blockchain/staticProvider';\r\nimport { assertSepoliaChainId } from '../config/gasOracleConfigs';\r\n\r\nconst MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';\r\nconst MAX_CALLS = 64;\r\nconst MULTICALL3_ABI = [\r\n 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) payable returns (tuple(bool success, bytes returnData)[] returnData)',\r\n] as const;\r\n\r\nexport interface Call {\r\n target: string;\r\n callData: string;\r\n}\r\n\r\nexport interface CallResult {\r\n success: boolean;\r\n returnData: string;\r\n}\r\n\r\nexport class MulticallProvider {\r\n private readonly provider: ethers.JsonRpcProvider;\r\n private readonly multicall: ethers.Contract;\r\n private readonly chainId: number;\r\n\r\n constructor(\r\n rpcUrl: string,\r\n multicallAddress: string = MULTICALL3_ADDRESS,\r\n chainId: ChainId | number,\r\n rpcTimeoutMs?: number,\r\n ) {\r\n assertSepoliaChainId(chainId);\r\n if (!ethers.isAddress(multicallAddress)) {\r\n throw new OracleError('INVALID_CONFIG', 'multicallAddress must be a valid EVM address');\r\n }\r\n\r\n this.chainId = chainId;\r\n this.provider = createVerifiedJsonRpcProvider(rpcUrl, chainId, { batchMaxCount: 1 }, rpcTimeoutMs);\r\n this.multicall = new ethers.Contract(multicallAddress, MULTICALL3_ABI, this.provider);\r\n }\r\n\r\n async aggregate(calls: readonly Call[]): Promise<CallResult[]> {\r\n if (calls.length > MAX_CALLS) {\r\n throw new OracleError('INVALID_INPUT', `Multicall supports at most ${String(MAX_CALLS)} calls`);\r\n }\r\n if (calls.length === 0) {\r\n return [];\r\n }\r\n\r\n for (const call of calls) {\r\n if (!ethers.isAddress(call.target) || !ethers.isHexString(call.callData)) {\r\n throw new OracleError('INVALID_INPUT', 'Every multicall target and calldata value must be valid');\r\n }\r\n }\r\n\r\n try {\r\n await assertProviderNetwork(this.provider, this.chainId);\r\n const rawResults = (await this.multicall.aggregate3.staticCall(\r\n calls.map((call) => ({ ...call, allowFailure: true })),\r\n )) as readonly { success: boolean; returnData: string }[];\r\n\r\n if (rawResults.length !== calls.length) {\r\n throw new OracleError('RPC_REQUEST_FAILED', 'Multicall returned an unexpected number of results');\r\n }\r\n\r\n return rawResults.map((result) => ({\r\n success: result.success,\r\n returnData: result.returnData,\r\n }));\r\n } catch (error) {\r\n if (error instanceof OracleError) {\r\n throw error;\r\n }\r\n throw new OracleError('RPC_REQUEST_FAILED', 'Multicall RPC request failed');\r\n }\r\n }\r\n}\r\n"]}
|
|
@@ -1,49 +1,15 @@
|
|
|
1
|
-
import { ILogger } from '../../application/ports/ILogger';
|
|
2
|
-
import { ChainId, Token } from '../../types';
|
|
3
|
-
export interface TokenPrices {
|
|
4
|
-
[symbol: string]: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* TokenPriceOracle
|
|
8
|
-
* Fetches token prices from OffchainOracle using Multicall
|
|
9
|
-
*
|
|
10
|
-
* Uses protocol-configured OffchainOracle from @mixerx/config
|
|
11
|
-
* Returns price rate to ETH in wei (how much ETH 1 token = X wei)
|
|
12
|
-
*
|
|
13
|
-
* NOTE: Uses centralized TokenDefaults to avoid duplication
|
|
14
|
-
*/
|
|
1
|
+
import type { ILogger } from '../../application/ports/ILogger';
|
|
2
|
+
import type { ChainId, Token, TokenPrices } from '../../types';
|
|
15
3
|
export declare class TokenPriceOracle {
|
|
16
|
-
private multicall;
|
|
17
|
-
private offchainOracleAddress;
|
|
18
|
-
private
|
|
4
|
+
private readonly multicall;
|
|
5
|
+
private readonly offchainOracleAddress;
|
|
6
|
+
private readonly interface;
|
|
7
|
+
private readonly logger;
|
|
19
8
|
private defaultTokens;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Fetch prices for tokens
|
|
25
|
-
* Uses multicall to batch requests
|
|
26
|
-
*/
|
|
27
|
-
fetchPrices(tokens?: Token[]): Promise<TokenPrices>;
|
|
28
|
-
/**
|
|
29
|
-
* Fetch price for single token
|
|
30
|
-
*/
|
|
31
|
-
fetchPrice(token: Token): Promise<string | null>;
|
|
32
|
-
/**
|
|
33
|
-
* Get default prices
|
|
34
|
-
*/
|
|
35
|
-
getDefaultPrices(): TokenPrices;
|
|
36
|
-
/**
|
|
37
|
-
* Get supported tokens
|
|
38
|
-
*/
|
|
9
|
+
constructor(rpcUrl: string, chainId: ChainId | number, offchainOracleAddress?: string, multicallAddress?: string, tokens?: Token[], logger?: ILogger, rpcTimeoutMs?: number);
|
|
10
|
+
fetchPrices(tokens?: readonly Token[]): Promise<TokenPrices>;
|
|
11
|
+
fetchPrice(token: Token): Promise<string>;
|
|
39
12
|
getSupportedTokens(): Token[];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
|
-
addTokens(tokens: Token[]): void;
|
|
44
|
-
/**
|
|
45
|
-
* Encode getRateToEth call
|
|
46
|
-
*/
|
|
47
|
-
private encodeGetRateToEth;
|
|
13
|
+
addTokens(tokens: readonly Token[]): void;
|
|
14
|
+
private normalizeTokens;
|
|
48
15
|
}
|
|
49
|
-
//# sourceMappingURL=TokenPriceOracle.d.ts.map
|