@mixerx/oracles 2.0.0 → 3.1.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 (52) hide show
  1. package/README.md +124 -106
  2. package/dist/application/ports/ILogger.js +0 -1
  3. package/dist/domain/config/TokenDefaults.js +0 -1
  4. package/dist/domain/exceptions.d.ts +1 -1
  5. package/dist/domain/exceptions.js +0 -1
  6. package/dist/domain/repositories.d.ts +1 -2
  7. package/dist/domain/repositories.js +0 -1
  8. package/dist/factory.d.ts +5 -15
  9. package/dist/factory.js +2 -2
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +0 -1
  12. package/dist/infrastructure/blockchain/staticProvider.js +1 -2
  13. package/dist/infrastructure/config/ProtocolTokenConfig.js +6 -6
  14. package/dist/infrastructure/config/gasOracleConfigs.d.ts +1 -2
  15. package/dist/infrastructure/config/gasOracleConfigs.js +5 -7
  16. package/dist/infrastructure/gas/GasPriceOracle.d.ts +5 -18
  17. package/dist/infrastructure/gas/GasPriceOracle.js +51 -45
  18. package/dist/infrastructure/mixerx/MixerXFeeOracle.d.ts +16 -5
  19. package/dist/infrastructure/mixerx/MixerXFeeOracle.js +110 -38
  20. package/dist/infrastructure/multicall/MulticallProvider.d.ts +5 -1
  21. package/dist/infrastructure/multicall/MulticallProvider.js +11 -8
  22. package/dist/infrastructure/token-price/TokenPriceOracle.d.ts +3 -5
  23. package/dist/infrastructure/token-price/TokenPriceOracle.js +11 -10
  24. package/dist/types.d.ts +6 -1
  25. package/dist/types.js +0 -1
  26. package/package.json +11 -5
  27. package/dist/application/ports/ILogger.js.map +0 -1
  28. package/dist/application/ports/index.d.ts +0 -5
  29. package/dist/application/ports/index.js +0 -18
  30. package/dist/application/ports/index.js.map +0 -1
  31. package/dist/domain/config/TokenDefaults.js.map +0 -1
  32. package/dist/domain/config/index.d.ts +0 -1
  33. package/dist/domain/config/index.js +0 -8
  34. package/dist/domain/config/index.js.map +0 -1
  35. package/dist/domain/exceptions.js.map +0 -1
  36. package/dist/domain/repositories.js.map +0 -1
  37. package/dist/factory.js.map +0 -1
  38. package/dist/index.js.map +0 -1
  39. package/dist/infrastructure/blockchain/staticProvider.js.map +0 -1
  40. package/dist/infrastructure/config/ProtocolTokenConfig.js.map +0 -1
  41. package/dist/infrastructure/config/gasOracleConfigs.js.map +0 -1
  42. package/dist/infrastructure/config/index.d.ts +0 -4
  43. package/dist/infrastructure/config/index.js +0 -11
  44. package/dist/infrastructure/config/index.js.map +0 -1
  45. package/dist/infrastructure/gas/GasPriceOracle.js.map +0 -1
  46. package/dist/infrastructure/mixerx/MixerXFeeOracle.js.map +0 -1
  47. package/dist/infrastructure/multicall/MulticallProvider.js.map +0 -1
  48. package/dist/infrastructure/token-price/TokenPriceOracle.js.map +0 -1
  49. package/dist/infrastructure/token-price/index.d.ts +0 -3
  50. package/dist/infrastructure/token-price/index.js +0 -6
  51. package/dist/infrastructure/token-price/index.js.map +0 -1
  52. package/dist/types.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import type { ILogger } from '../../application/ports/ILogger';
2
2
  import { type ITokenPriceRepository } from '../../domain/repositories';
3
3
  import { GasPriceOracle, type GasPriceParams } from '../gas/GasPriceOracle';
4
- import type { ChainId } from '../config/gasOracleConfigs';
4
+ import { type ChainId } from '../config/gasOracleConfigs';
5
5
  import { TokenPriceOracle } from '../token-price/TokenPriceOracle';
6
6
  export type MixerXTxType = 'relayer_withdrawal' | 'user_withdrawal' | 'relayer_withdrawal_check_v4';
7
7
  export type MixerXGasPurpose = 'fee' | 'send' | 'replacement';
@@ -20,6 +20,12 @@ export interface MixerXFeeOracleOptions {
20
20
  maxGasPriceGwei?: number;
21
21
  maxPriceAgeMs?: number;
22
22
  maxRefundWei?: bigint;
23
+ maxTotalFeeBps?: number;
24
+ priceBounds?: Readonly<Record<string, TokenPriceBounds>>;
25
+ }
26
+ export interface TokenPriceBounds {
27
+ minimumWeiPerToken: bigint;
28
+ maximumWeiPerToken: bigint;
23
29
  }
24
30
  export interface MixerXWithdrawalFeeInput {
25
31
  currency: string;
@@ -28,6 +34,7 @@ export interface MixerXWithdrawalFeeInput {
28
34
  /** Gas limit obtained from a simulation or eth_estimateGas for this exact transaction. */
29
35
  gasLimit: number;
30
36
  refund?: string;
37
+ refundGasMultiplier?: number;
31
38
  txType?: MixerXTxType;
32
39
  purpose?: 'fee' | LegacyFeePurpose;
33
40
  }
@@ -45,10 +52,12 @@ export interface MixerXFeeCalculationResult {
45
52
  gasCost: bigint;
46
53
  relayerFee: bigint;
47
54
  refundAmount: bigint;
55
+ refundWei: bigint;
48
56
  totalFee: bigint;
49
57
  currency: string;
50
58
  gasLimit: number;
51
59
  gasPriceWei: bigint;
60
+ gasSource: 'rpc';
52
61
  baseFeePerGas?: string;
53
62
  maxFeePerGas?: string;
54
63
  maxPriorityFeePerGas?: string;
@@ -63,7 +72,6 @@ export interface MixerXFeeCalculationResult {
63
72
  export type WithdrawalFeeInput = MixerXWithdrawalFeeInput;
64
73
  export type FeeCalculationResult = MixerXFeeCalculationResult;
65
74
  export declare class MixerXFeeOracle {
66
- private readonly chainId;
67
75
  private readonly relayerFeePartsPerMillion;
68
76
  private readonly gasPriceOracle;
69
77
  private readonly tokenPriceOracle;
@@ -71,18 +79,21 @@ export declare class MixerXFeeOracle {
71
79
  private readonly logger;
72
80
  private readonly maxPriceAgeMs;
73
81
  private readonly maxRefundWei;
74
- private readonly supportedCurrencies;
82
+ private readonly maxTotalFeeBps;
83
+ private readonly currencyDecimals;
84
+ private readonly priceBounds;
75
85
  constructor(options: MixerXFeeOracleOptions);
76
86
  calculateWithdrawalFee(input: MixerXWithdrawalFeeInput): Promise<MixerXFeeCalculationResult>;
77
87
  getGasParametersForTx(input?: MixerXGasParametersForTxInput): Promise<MixerXGasParametersForTxResult>;
78
88
  assertReady(): Promise<void>;
79
- getGasParameters(isLegacy?: boolean): Promise<GasPriceParams>;
80
- getChainId(): ChainId;
81
89
  private validateFeeInput;
82
90
  private parseRefund;
91
+ private resolveRefundWei;
83
92
  private getFreshPrice;
93
+ private validatePriceBounds;
84
94
  private effectiveGasPrice;
85
95
  private assertTotalFee;
86
96
  private normalizePurpose;
97
+ private validateTxType;
87
98
  private result;
88
99
  }
@@ -6,12 +6,27 @@ const TokenDefaults_1 = require("../../domain/config/TokenDefaults");
6
6
  const exceptions_1 = require("../../domain/exceptions");
7
7
  const repositories_1 = require("../../domain/repositories");
8
8
  const GasPriceOracle_1 = require("../gas/GasPriceOracle");
9
+ const gasOracleConfigs_1 = require("../config/gasOracleConfigs");
9
10
  const TokenPriceOracle_1 = require("../token-price/TokenPriceOracle");
10
11
  const DEFAULT_MAX_PRICE_AGE_MS = 120_000;
11
12
  const DEFAULT_MAX_REFUND_WEI = 1000000000000000000n;
13
+ const DEFAULT_MAX_TOTAL_FEE_BPS = 1_000;
14
+ const BASIS_POINTS_SCALE = 10000n;
12
15
  const FEE_SCALE = 1000000n;
16
+ const MAX_REFUND_DIGITS = 256;
17
+ const TX_TYPES = new Set([
18
+ 'relayer_withdrawal',
19
+ 'user_withdrawal',
20
+ 'relayer_withdrawal_check_v4',
21
+ ]);
22
+ const GAS_PURPOSES = new Set([
23
+ 'fee',
24
+ 'send',
25
+ 'replacement',
26
+ 'fee_quote',
27
+ 'fee_validation',
28
+ ]);
13
29
  class MixerXFeeOracle {
14
- chainId;
15
30
  relayerFeePartsPerMillion;
16
31
  gasPriceOracle;
17
32
  tokenPriceOracle;
@@ -19,11 +34,18 @@ class MixerXFeeOracle {
19
34
  logger;
20
35
  maxPriceAgeMs;
21
36
  maxRefundWei;
22
- supportedCurrencies;
37
+ maxTotalFeeBps;
38
+ currencyDecimals;
39
+ priceBounds;
23
40
  constructor(options) {
41
+ (0, gasOracleConfigs_1.assertSupportedChainId)(options.chainId);
24
42
  if (!Number.isFinite(options.relayerFeePercent) || options.relayerFeePercent < 0 || options.relayerFeePercent > 0.1) {
25
43
  throw new exceptions_1.OracleError('INVALID_CONFIG', 'relayerFeePercent must be between 0 and 0.1');
26
44
  }
45
+ const relayerFeePartsPerMillion = options.relayerFeePercent * Number(FEE_SCALE);
46
+ if (!Number.isInteger(relayerFeePartsPerMillion)) {
47
+ throw new exceptions_1.OracleError('INVALID_CONFIG', 'relayerFeePercent exceeds supported precision');
48
+ }
27
49
  this.maxPriceAgeMs = options.maxPriceAgeMs ?? DEFAULT_MAX_PRICE_AGE_MS;
28
50
  if (!Number.isInteger(this.maxPriceAgeMs) || this.maxPriceAgeMs < 1_000 || this.maxPriceAgeMs > 3_600_000) {
29
51
  throw new exceptions_1.OracleError('INVALID_CONFIG', 'maxPriceAgeMs must be between 1000 and 3600000');
@@ -32,8 +54,13 @@ class MixerXFeeOracle {
32
54
  if (this.maxRefundWei < 0n) {
33
55
  throw new exceptions_1.OracleError('INVALID_CONFIG', 'maxRefundWei cannot be negative');
34
56
  }
35
- this.chainId = options.chainId;
36
- this.relayerFeePartsPerMillion = BigInt(Math.round(options.relayerFeePercent * Number(FEE_SCALE)));
57
+ this.maxTotalFeeBps = options.maxTotalFeeBps ?? DEFAULT_MAX_TOTAL_FEE_BPS;
58
+ if (!Number.isInteger(this.maxTotalFeeBps) ||
59
+ this.maxTotalFeeBps < 1 ||
60
+ this.maxTotalFeeBps > DEFAULT_MAX_TOTAL_FEE_BPS) {
61
+ throw new exceptions_1.OracleError('INVALID_CONFIG', 'maxTotalFeeBps must be an integer between 1 and 1000');
62
+ }
63
+ this.relayerFeePartsPerMillion = BigInt(relayerFeePartsPerMillion);
37
64
  this.logger = options.logger ?? new ILogger_1.NullLogger();
38
65
  this.gasPriceOracle = options.gasPriceOracle ?? new GasPriceOracle_1.GasPriceOracle({
39
66
  chainId: options.chainId,
@@ -43,15 +70,20 @@ class MixerXFeeOracle {
43
70
  maxGasPriceGwei: options.maxGasPriceGwei,
44
71
  });
45
72
  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),
73
+ const supportedTokens = this.tokenPriceOracle.getSupportedTokens();
74
+ if (supportedTokens.some((token) => token.symbol.trim().toLowerCase() === 'eth')) {
75
+ throw new exceptions_1.OracleError('INVALID_CONFIG', 'Token symbol eth is reserved for native ETH');
76
+ }
77
+ this.currencyDecimals = new Map([
78
+ ['eth', 18],
79
+ ...supportedTokens.map((token) => [token.symbol, token.decimals]),
49
80
  ]);
81
+ this.priceBounds = this.validatePriceBounds(options.priceBounds ?? {});
50
82
  this.priceRepository = options.priceRepository;
51
83
  }
52
84
  async calculateWithdrawalFee(input) {
53
85
  const currency = this.validateFeeInput(input);
54
- const txType = input.txType ?? 'relayer_withdrawal';
86
+ const txType = this.validateTxType(input.txType ?? 'relayer_withdrawal');
55
87
  const gasParams = await this.getGasParametersForTx({
56
88
  txType,
57
89
  purpose: this.normalizePurpose(input.purpose ?? 'fee'),
@@ -62,7 +94,8 @@ class MixerXFeeOracle {
62
94
  const gasCostWei = gasPriceWei * BigInt(gasLimit);
63
95
  const relayerFee = (input.amount * this.relayerFeePartsPerMillion) / FEE_SCALE;
64
96
  if (currency === 'eth') {
65
- if (input.refund !== undefined && this.parseRefund(input.refund) !== 0n) {
97
+ if ((input.refund !== undefined && this.parseRefund(input.refund) !== 0n) ||
98
+ (input.refundGasMultiplier !== undefined && input.refundGasMultiplier !== 0)) {
66
99
  throw new exceptions_1.OracleError('INVALID_INPUT', 'refund must be zero for native ETH withdrawals');
67
100
  }
68
101
  const totalFee = gasCostWei + relayerFee;
@@ -71,6 +104,7 @@ class MixerXFeeOracle {
71
104
  gasCost: gasCostWei,
72
105
  relayerFee,
73
106
  refundAmount: 0n,
107
+ refundWei: 0n,
74
108
  totalFee,
75
109
  currency,
76
110
  gasLimit,
@@ -83,7 +117,7 @@ class MixerXFeeOracle {
83
117
  const quote = await this.getFreshPrice(currency);
84
118
  const decimalsFactor = 10n ** BigInt(input.decimals);
85
119
  const gasCost = (gasCostWei * decimalsFactor) / quote.priceInEthWei;
86
- const refundWei = this.parseRefund(input.refund ?? '0');
120
+ const refundWei = this.resolveRefundWei(input, gasCostWei);
87
121
  const refundAmount = (refundWei * decimalsFactor) / quote.priceInEthWei;
88
122
  const totalFee = gasCost + relayerFee + refundAmount;
89
123
  this.assertTotalFee(totalFee, input.amount);
@@ -91,6 +125,7 @@ class MixerXFeeOracle {
91
125
  gasCost,
92
126
  relayerFee,
93
127
  refundAmount,
128
+ refundWei,
94
129
  totalFee,
95
130
  currency,
96
131
  gasLimit,
@@ -103,31 +138,31 @@ class MixerXFeeOracle {
103
138
  });
104
139
  }
105
140
  async getGasParametersForTx(input = {}) {
106
- const txType = input.txType ?? 'relayer_withdrawal';
141
+ const txType = this.validateTxType(input.txType ?? 'relayer_withdrawal');
107
142
  const purpose = this.normalizePurpose(input.purpose ?? 'send');
108
143
  const bumpPercent = purpose === 'send' ? 0 : (0, TokenDefaults_1.getBumpPercent)(txType);
109
144
  const gasParams = await this.gasPriceOracle.getTxGasParams({
110
145
  isLegacy: input.isLegacy ?? false,
111
146
  bumpPercent,
112
- legacySpeed: 'fast',
113
147
  });
114
148
  return { ...gasParams, txType, purpose, bumpPercent };
115
149
  }
116
150
  async assertReady() {
117
- await this.tokenPriceOracle.fetchPrices();
118
- }
119
- async getGasParameters(isLegacy = false) {
120
- return this.gasPriceOracle.getTxGasParams({ isLegacy });
121
- }
122
- getChainId() {
123
- return this.chainId;
151
+ await Promise.all([
152
+ this.gasPriceOracle.getTxGasParams(),
153
+ this.tokenPriceOracle.fetchPrices(),
154
+ ]);
155
+ await Promise.all(this.tokenPriceOracle
156
+ .getSupportedTokens()
157
+ .map((token) => this.getFreshPrice(token.symbol)));
124
158
  }
125
159
  validateFeeInput(input) {
126
160
  const currency = input.currency.trim().toLowerCase();
127
161
  if (!/^[a-z0-9]{2,16}$/.test(currency)) {
128
162
  throw new exceptions_1.OracleError('INVALID_INPUT', 'currency must be a valid protocol token symbol');
129
163
  }
130
- if (!this.supportedCurrencies.has(currency)) {
164
+ const configuredDecimals = this.currencyDecimals.get(currency);
165
+ if (configuredDecimals === undefined) {
131
166
  throw new exceptions_1.OracleError('INVALID_INPUT', `Unsupported MixerX currency: ${currency}`);
132
167
  }
133
168
  if (input.amount <= 0n) {
@@ -136,13 +171,16 @@ class MixerXFeeOracle {
136
171
  if (!Number.isInteger(input.decimals) || input.decimals < 0 || input.decimals > 255) {
137
172
  throw new exceptions_1.OracleError('INVALID_INPUT', 'decimals must be an integer between 0 and 255');
138
173
  }
174
+ if (input.decimals !== configuredDecimals) {
175
+ throw new exceptions_1.OracleError('INVALID_INPUT', `${currency} must use configured decimals ${String(configuredDecimals)}`);
176
+ }
139
177
  if (!Number.isInteger(input.gasLimit) || input.gasLimit < 21_000 || input.gasLimit > 5_000_000) {
140
178
  throw new exceptions_1.OracleError('INVALID_INPUT', 'gasLimit must be an integer between 21000 and 5000000');
141
179
  }
142
180
  return currency;
143
181
  }
144
182
  parseRefund(raw) {
145
- if (!/^\d+$/.test(raw)) {
183
+ if (typeof raw !== 'string' || raw.length > MAX_REFUND_DIGITS || !/^\d+$/.test(raw)) {
146
184
  throw new exceptions_1.OracleError('INVALID_INPUT', 'refund must be an unsigned base-10 integer string');
147
185
  }
148
186
  const refund = BigInt(raw);
@@ -151,45 +189,78 @@ class MixerXFeeOracle {
151
189
  }
152
190
  return refund;
153
191
  }
192
+ resolveRefundWei(input, gasCostWei) {
193
+ if (input.refund !== undefined && input.refundGasMultiplier !== undefined) {
194
+ throw new exceptions_1.OracleError('INVALID_INPUT', 'Specify either refund or refundGasMultiplier, not both');
195
+ }
196
+ if (input.refundGasMultiplier === undefined) {
197
+ return this.parseRefund(input.refund ?? '0');
198
+ }
199
+ if (!Number.isSafeInteger(input.refundGasMultiplier) ||
200
+ input.refundGasMultiplier < 0 ||
201
+ input.refundGasMultiplier > 10) {
202
+ throw new exceptions_1.OracleError('INVALID_INPUT', 'refundGasMultiplier must be an integer between 0 and 10');
203
+ }
204
+ return this.parseRefund((gasCostWei * BigInt(input.refundGasMultiplier)).toString());
205
+ }
154
206
  async getFreshPrice(symbol) {
155
207
  if (!this.priceRepository) {
156
208
  throw new exceptions_1.OracleError('TOKEN_PRICE_UNAVAILABLE', `No fresh price repository configured for ${symbol}`);
157
209
  }
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);
210
+ const storedQuote = await this.priceRepository.getQuote(symbol);
211
+ const quote = (0, repositories_1.toPriceQuote)(storedQuote?.priceInEthWei, storedQuote?.observedAtMs);
163
212
  if (!quote) {
164
213
  throw new exceptions_1.OracleError('TOKEN_PRICE_UNAVAILABLE', `Price unavailable for ${symbol}`);
165
214
  }
166
215
  if (Date.now() - quote.observedAtMs > this.maxPriceAgeMs || quote.observedAtMs > Date.now() + 5_000) {
167
216
  throw new exceptions_1.OracleError('TOKEN_PRICE_STALE', `Price is stale for ${symbol}`);
168
217
  }
218
+ const bounds = this.priceBounds.get(symbol);
219
+ if (bounds &&
220
+ (quote.priceInEthWei < bounds.minimumWeiPerToken ||
221
+ quote.priceInEthWei > bounds.maximumWeiPerToken)) {
222
+ throw new exceptions_1.OracleError('TOKEN_PRICE_OUT_OF_RANGE', `Price is outside configured bounds for ${symbol}`);
223
+ }
169
224
  return quote;
170
225
  }
171
- effectiveGasPrice(params) {
172
- if (params.gasPrice) {
173
- return BigInt(params.gasPrice);
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;
226
+ validatePriceBounds(configuredBounds) {
227
+ const validated = new Map();
228
+ for (const [rawSymbol, bounds] of Object.entries(configuredBounds)) {
229
+ const symbol = rawSymbol.trim().toLowerCase();
230
+ if (!this.currencyDecimals.has(symbol) ||
231
+ bounds.minimumWeiPerToken <= 0n ||
232
+ bounds.maximumWeiPerToken <= bounds.minimumWeiPerToken) {
233
+ throw new exceptions_1.OracleError('INVALID_CONFIG', `Invalid token price bounds for ${symbol}`);
234
+ }
235
+ validated.set(symbol, Object.freeze({ ...bounds }));
179
236
  }
180
- throw new exceptions_1.OracleError('RPC_REQUEST_FAILED', 'Gas price response is incomplete');
237
+ return validated;
238
+ }
239
+ effectiveGasPrice(params) {
240
+ return BigInt(params.effectiveGasPriceWei);
181
241
  }
182
242
  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');
243
+ const maximumFee = (amount * BigInt(this.maxTotalFeeBps)) / BASIS_POINTS_SCALE;
244
+ if (totalFee <= 0n || totalFee > maximumFee) {
245
+ const maximumPercent = (this.maxTotalFeeBps / 100).toFixed(2);
246
+ throw new exceptions_1.OracleError('ECONOMIC_LIMIT_EXCEEDED', `Total fee must be greater than zero and no more than ${maximumPercent}% of the withdrawal amount`);
185
247
  }
186
248
  }
187
249
  normalizePurpose(purpose) {
250
+ if (typeof purpose !== 'string' || !GAS_PURPOSES.has(purpose)) {
251
+ throw new exceptions_1.OracleError('INVALID_INPUT', 'purpose is not supported');
252
+ }
188
253
  if (purpose === 'fee_quote' || purpose === 'fee_validation') {
189
254
  return 'fee';
190
255
  }
191
256
  return purpose;
192
257
  }
258
+ validateTxType(txType) {
259
+ if (typeof txType !== 'string' || !TX_TYPES.has(txType)) {
260
+ throw new exceptions_1.OracleError('INVALID_INPUT', 'txType is not supported');
261
+ }
262
+ return txType;
263
+ }
193
264
  result(input) {
194
265
  this.logger.debug('Withdrawal fee calculated', {
195
266
  priceSource: input.priceSource,
@@ -199,10 +270,12 @@ class MixerXFeeOracle {
199
270
  gasCost: input.gasCost,
200
271
  relayerFee: input.relayerFee,
201
272
  refundAmount: input.refundAmount,
273
+ refundWei: input.refundWei,
202
274
  totalFee: input.totalFee,
203
275
  currency: input.currency,
204
276
  gasLimit: input.gasLimit,
205
277
  gasPriceWei: input.gasPriceWei,
278
+ gasSource: input.gasParams.gasSource,
206
279
  baseFeePerGas: input.gasParams.baseFeePerGas,
207
280
  maxFeePerGas: input.gasParams.maxFeePerGas,
208
281
  maxPriorityFeePerGas: input.gasParams.maxPriorityFeePerGas,
@@ -217,4 +290,3 @@ class MixerXFeeOracle {
217
290
  }
218
291
  }
219
292
  exports.MixerXFeeOracle = MixerXFeeOracle;
220
- //# sourceMappingURL=MixerXFeeOracle.js.map
@@ -7,10 +7,14 @@ export interface CallResult {
7
7
  success: boolean;
8
8
  returnData: string;
9
9
  }
10
+ export interface MulticallResult {
11
+ blockNumber: number;
12
+ results: CallResult[];
13
+ }
10
14
  export declare class MulticallProvider {
11
15
  private readonly provider;
12
16
  private readonly multicall;
13
17
  private readonly chainId;
14
18
  constructor(rpcUrl: string, multicallAddress: string | undefined, chainId: ChainId | number, rpcTimeoutMs?: number);
15
- aggregate(calls: readonly Call[]): Promise<CallResult[]>;
19
+ aggregate(calls: readonly Call[]): Promise<MulticallResult>;
16
20
  }
@@ -15,7 +15,7 @@ class MulticallProvider {
15
15
  multicall;
16
16
  chainId;
17
17
  constructor(rpcUrl, multicallAddress = MULTICALL3_ADDRESS, chainId, rpcTimeoutMs) {
18
- (0, gasOracleConfigs_1.assertSepoliaChainId)(chainId);
18
+ (0, gasOracleConfigs_1.assertSupportedChainId)(chainId);
19
19
  if (!ethers_1.ethers.isAddress(multicallAddress)) {
20
20
  throw new exceptions_1.OracleError('INVALID_CONFIG', 'multicallAddress must be a valid EVM address');
21
21
  }
@@ -28,7 +28,7 @@ class MulticallProvider {
28
28
  throw new exceptions_1.OracleError('INVALID_INPUT', `Multicall supports at most ${String(MAX_CALLS)} calls`);
29
29
  }
30
30
  if (calls.length === 0) {
31
- return [];
31
+ return { blockNumber: await this.provider.getBlockNumber(), results: [] };
32
32
  }
33
33
  for (const call of calls) {
34
34
  if (!ethers_1.ethers.isAddress(call.target) || !ethers_1.ethers.isHexString(call.callData)) {
@@ -37,14 +37,18 @@ class MulticallProvider {
37
37
  }
38
38
  try {
39
39
  await (0, staticProvider_1.assertProviderNetwork)(this.provider, this.chainId);
40
- const rawResults = (await this.multicall.aggregate3.staticCall(calls.map((call) => ({ ...call, allowFailure: true }))));
40
+ const blockNumber = await this.provider.getBlockNumber();
41
+ const rawResults = (await this.multicall.aggregate3.staticCall(calls.map((call) => ({ ...call, allowFailure: true })), { blockTag: blockNumber }));
41
42
  if (rawResults.length !== calls.length) {
42
43
  throw new exceptions_1.OracleError('RPC_REQUEST_FAILED', 'Multicall returned an unexpected number of results');
43
44
  }
44
- return rawResults.map((result) => ({
45
- success: result.success,
46
- returnData: result.returnData,
47
- }));
45
+ return {
46
+ blockNumber,
47
+ results: rawResults.map((result) => ({
48
+ success: result.success,
49
+ returnData: result.returnData,
50
+ })),
51
+ };
48
52
  }
49
53
  catch (error) {
50
54
  if (error instanceof exceptions_1.OracleError) {
@@ -55,4 +59,3 @@ class MulticallProvider {
55
59
  }
56
60
  }
57
61
  exports.MulticallProvider = MulticallProvider;
58
- //# sourceMappingURL=MulticallProvider.js.map
@@ -1,15 +1,13 @@
1
1
  import type { ILogger } from '../../application/ports/ILogger';
2
- import type { ChainId, Token, TokenPrices } from '../../types';
2
+ import type { ChainId, Token, TokenPriceSnapshot } from '../../types';
3
3
  export declare class TokenPriceOracle {
4
4
  private readonly multicall;
5
5
  private readonly offchainOracleAddress;
6
6
  private readonly interface;
7
7
  private readonly logger;
8
- private defaultTokens;
8
+ private readonly defaultTokens;
9
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>;
10
+ fetchPrices(tokens?: readonly Token[]): Promise<TokenPriceSnapshot>;
12
11
  getSupportedTokens(): Token[];
13
- addTokens(tokens: readonly Token[]): void;
14
12
  private normalizeTokens;
15
13
  }
@@ -31,7 +31,7 @@ class TokenPriceOracle {
31
31
  target: this.offchainOracleAddress,
32
32
  callData: this.interface.encodeFunctionData('getRateToEth', [token.address, true]),
33
33
  }));
34
- const results = await this.multicall.aggregate(calls);
34
+ const { blockNumber, results } = await this.multicall.aggregate(calls);
35
35
  const prices = Object.create(null);
36
36
  const failedSymbols = [];
37
37
  for (const [index, result] of results.entries()) {
@@ -57,18 +57,15 @@ class TokenPriceOracle {
57
57
  this.logger.warn('Token price batch rejected', { failedSymbols: [...failedSymbols] });
58
58
  throw new exceptions_1.TokenPriceError('One or more on-chain token prices are unavailable', failedSymbols);
59
59
  }
60
- return { ...prices };
61
- }
62
- async fetchPrice(token) {
63
- const prices = await this.fetchPrices([token]);
64
- return prices[token.symbol.trim().toLowerCase()];
60
+ return Object.freeze({
61
+ prices: Object.freeze({ ...prices }),
62
+ observedAtMs: Date.now(),
63
+ blockNumber,
64
+ });
65
65
  }
66
66
  getSupportedTokens() {
67
67
  return this.defaultTokens.map((token) => ({ ...token }));
68
68
  }
69
- addTokens(tokens) {
70
- this.defaultTokens = this.normalizeTokens([...this.defaultTokens, ...tokens]);
71
- }
72
69
  normalizeTokens(tokens) {
73
70
  if (tokens.length === 0) {
74
71
  throw new exceptions_1.OracleError('INVALID_CONFIG', `Token list must contain between 1 and ${String(MAX_TOKENS)} items`);
@@ -87,6 +84,11 @@ class TokenPriceOracle {
87
84
  if (symbols.has(symbol) && byAddress.get(address)?.symbol !== symbol) {
88
85
  throw new exceptions_1.OracleError('INVALID_CONFIG', `Duplicate token symbol: ${symbol}`);
89
86
  }
87
+ const existingToken = byAddress.get(address);
88
+ if (existingToken &&
89
+ (existingToken.symbol !== symbol || existingToken.decimals !== token.decimals)) {
90
+ throw new exceptions_1.OracleError('INVALID_CONFIG', `Conflicting metadata for token address: ${address}`);
91
+ }
90
92
  symbols.add(symbol);
91
93
  byAddress.set(address, { address, symbol, decimals: token.decimals });
92
94
  }
@@ -97,4 +99,3 @@ class TokenPriceOracle {
97
99
  }
98
100
  }
99
101
  exports.TokenPriceOracle = TokenPriceOracle;
100
- //# sourceMappingURL=TokenPriceOracle.js.map
package/dist/types.d.ts CHANGED
@@ -5,7 +5,12 @@ export interface Token {
5
5
  decimals: number;
6
6
  }
7
7
  export interface TokenPrices {
8
- [symbol: string]: string;
8
+ readonly [symbol: string]: string;
9
+ }
10
+ export interface TokenPriceSnapshot {
11
+ readonly prices: TokenPrices;
12
+ readonly observedAtMs: number;
13
+ readonly blockNumber: number;
9
14
  }
10
15
  export interface TokenPriceQuote {
11
16
  priceInEthWei: bigint;
package/dist/types.js CHANGED
@@ -1,3 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@mixerx/oracles",
3
- "version": "2.0.0",
4
- "description": "Fail-closed Sepolia gas, token-price and withdrawal-fee policies for MixerX",
3
+ "version": "3.1.0",
4
+ "description": "Fail-closed Ethereum gas, token-price and withdrawal-fee policies for MixerX",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
7
13
  "packageManager": "yarn@4.12.0",
8
14
  "files": [
9
15
  "dist/**/*"
@@ -39,7 +45,7 @@
39
45
  "access": "public"
40
46
  },
41
47
  "dependencies": {
42
- "@mixerx/config": "0.5.4",
48
+ "@mixerx/config": "1.1.0",
43
49
  "ethers": "6.17.0"
44
50
  },
45
51
  "devDependencies": {
@@ -51,6 +57,6 @@
51
57
  "typescript-eslint": "8.48.1"
52
58
  },
53
59
  "engines": {
54
- "node": ">=22.12.0"
60
+ "node": ">=24.12.0"
55
61
  }
56
- }
62
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"ILogger.js","sourceRoot":"","sources":["../../../src/application/ports/ILogger.ts"],"names":[],"mappings":";;;AAMA,MAAa,UAAU;IACrB,KAAK,CAAC,QAAgB,EAAE,KAAe,IAAS,CAAC;IACjD,IAAI,CAAC,QAAgB,EAAE,KAAe,IAAS,CAAC;IAChD,KAAK,CAAC,QAAgB,EAAE,KAAe,IAAS,CAAC;CAClD;AAJD,gCAIC;AAED,MAAa,aAAa;IACK;IAA7B,YAA6B,QAAQ,gBAAgB;QAAxB,UAAK,GAAL,KAAK,CAAmB;IAAG,CAAC;IAEzD,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAAc;QAClC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF;AAdD,sCAcC","sourcesContent":["export interface ILogger {\r\n debug(message: string, meta?: unknown): void;\r\n warn(message: string, meta?: unknown): void;\r\n error(message: string, meta?: unknown): void;\r\n}\r\n\r\nexport class NullLogger implements ILogger {\r\n debug(_message: string, _meta?: unknown): void {}\r\n warn(_message: string, _meta?: unknown): void {}\r\n error(_message: string, _meta?: unknown): void {}\r\n}\r\n\r\nexport class ConsoleLogger implements ILogger {\r\n constructor(private readonly scope = 'mixerx-oracles') {}\r\n\r\n debug(message: string, meta?: unknown): void {\r\n console.debug(`[${this.scope}] ${message}`, meta ?? '');\r\n }\r\n\r\n warn(message: string, meta?: unknown): void {\r\n console.warn(`[${this.scope}] ${message}`, meta ?? '');\r\n }\r\n\r\n error(message: string, meta?: unknown): void {\r\n console.error(`[${this.scope}] ${message}`, meta ?? '');\r\n }\r\n}\r\n"]}
@@ -1,5 +0,0 @@
1
- import type { Token, TokenPrices } from '../../types';
2
- export interface ITokenPriceFetcher {
3
- fetchPrices(tokens?: Token[]): Promise<TokenPrices>;
4
- }
5
- export * from './ILogger';
@@ -1,18 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./ILogger"), exports);
18
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/application/ports/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAMA,4CAA0B","sourcesContent":["import type { Token, TokenPrices } from '../../types';\r\n\r\nexport interface ITokenPriceFetcher {\r\n fetchPrices(tokens?: Token[]): Promise<TokenPrices>;\r\n}\r\n\r\nexport * from './ILogger';\r\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"TokenDefaults.js","sourceRoot":"","sources":["../../../src/domain/config/TokenDefaults.ts"],"names":[],"mappings":";;AAMA,wCAGC;AATD,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IAC5D,CAAC,oBAAoB,EAAE,EAAE,CAAC;IAC1B,CAAC,iBAAiB,EAAE,EAAE,CAAC;IACvB,CAAC,6BAA6B,EAAE,CAAC,CAAC;CACnC,CAAC,CAAC;AAEH,SAAgB,cAAc,CAAC,MAAc;IAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;AACzC,CAAC","sourcesContent":["const BUMP_PERCENTAGES: ReadonlyMap<string, number> = new Map([\r\n ['relayer_withdrawal', 10],\r\n ['user_withdrawal', 30],\r\n ['relayer_withdrawal_check_v4', 0],\r\n]);\r\n\r\nexport function getBumpPercent(txType: string): number {\r\n const key = txType.trim().toLowerCase();\r\n return BUMP_PERCENTAGES.get(key) ?? 10;\r\n}\r\n"]}
@@ -1 +0,0 @@
1
- export { getBumpPercent, } from './TokenDefaults';
@@ -1,8 +0,0 @@
1
- "use strict";
2
- // Domain configuration exports
3
- // Centralized defaults to eliminate duplication across the codebase
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.getBumpPercent = void 0;
6
- var TokenDefaults_1 = require("./TokenDefaults");
7
- Object.defineProperty(exports, "getBumpPercent", { enumerable: true, get: function () { return TokenDefaults_1.getBumpPercent; } });
8
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/domain/config/index.ts"],"names":[],"mappings":";AAAA,+BAA+B;AAC/B,oEAAoE;;;AAEpE,iDAEyB;AADvB,+GAAA,cAAc,OAAA","sourcesContent":["// Domain configuration exports\r\n// Centralized defaults to eliminate duplication across the codebase\r\n\r\nexport {\r\n getBumpPercent,\r\n} from './TokenDefaults';\r\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/domain/exceptions.ts"],"names":[],"mappings":";;;AASA,MAAa,WAAY,SAAQ,KAAK;IAElB;IADlB,YACkB,IAAqB,EACrC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAiB;QAIrC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AARD,kCAQC;AAED,MAAa,eAAgB,SAAQ,WAAW;IACD;IAA7C,YAAY,OAAe,EAAkB,UAA6B,EAAE;QAC1E,KAAK,CAAC,yBAAyB,EAAE,OAAO,CAAC,CAAC;QADC,YAAO,GAAP,OAAO,CAAwB;QAE1E,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AALD,0CAKC","sourcesContent":["export type OracleErrorCode =\r\n | 'INVALID_CONFIG'\r\n | 'INVALID_INPUT'\r\n | 'NETWORK_MISMATCH'\r\n | 'RPC_REQUEST_FAILED'\r\n | 'TOKEN_PRICE_UNAVAILABLE'\r\n | 'TOKEN_PRICE_STALE'\r\n | 'ECONOMIC_LIMIT_EXCEEDED';\r\n\r\nexport class OracleError extends Error {\r\n constructor(\r\n public readonly code: OracleErrorCode,\r\n message: string,\r\n ) {\r\n super(message);\r\n this.name = 'OracleError';\r\n }\r\n}\r\n\r\nexport class TokenPriceError extends OracleError {\r\n constructor(message: string, public readonly symbols: readonly string[] = []) {\r\n super('TOKEN_PRICE_UNAVAILABLE', message);\r\n this.name = 'TokenPriceError';\r\n }\r\n}\r\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"repositories.js","sourceRoot":"","sources":["../../src/domain/repositories.ts"],"names":[],"mappings":";;AAOA,oCAaC;AAbD,SAAgB,YAAY,CAC1B,KAAgC,EAChC,YAAuC;IAEvC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,YAAsB,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AAC9F,CAAC","sourcesContent":["import type { TokenPriceQuote } from '../types';\r\n\r\nexport interface ITokenPriceRepository {\r\n getPrice(symbol: string): Promise<bigint | null | undefined>;\r\n getLastUpdateTimestamp(symbol: string): Promise<number | null | undefined>;\r\n}\r\n\r\nexport function toPriceQuote(\r\n price: bigint | null | undefined,\r\n observedAtMs: number | null | undefined,\r\n): TokenPriceQuote | null {\r\n if (price === null || price === undefined || price <= 0n) {\r\n return null;\r\n }\r\n\r\n if (!Number.isSafeInteger(observedAtMs) || (observedAtMs ?? -1) < 0) {\r\n return null;\r\n }\r\n\r\n return { priceInEthWei: price, observedAtMs: observedAtMs as number, source: 'repository' };\r\n}\r\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":";;AAmCA,8DAyCC;AA3ED,yDAAyD;AAEzD,wEAAqE;AACrE,6EAA0E;AAC1E,oFAAiF;AA8BjF,SAAgB,yBAAyB,CAAC,MAA2B;IACnE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,oBAAU,EAAE,CAAC;IACjD,MAAM,cAAc,GAAG,IAAI,+BAAc,CAAC;QACxC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;QACN,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,eAAe,EAAE,MAAM,CAAC,eAAe;KACxC,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,IAAI,mCAAgB,CAC3C,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,qBAAqB,EAC5B,MAAM,CAAC,gBAAgB,EACvB,SAAS,EACT,MAAM,EACN,MAAM,CAAC,YAAY,CACpB,CAAC;IACF,MAAM,eAAe,GAAG,IAAI,iCAAe,CAAC;QAC1C,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,cAAc;QACd,gBAAgB;QAChB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,MAAM;QACN,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,YAAY,EAAE,MAAM,CAAC,YAAY;KAClC,CAAC,CAAC;IAEH,OAAO;QACL,cAAc;QACd,gBAAgB;QAChB,eAAe;QACf,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,WAAW,EAAE;QACjD,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,WAAW,EAAE;KACjD,CAAC;AACJ,CAAC","sourcesContent":["import type { ILogger } from './application/ports/ILogger';\r\nimport { NullLogger } from './application/ports/ILogger';\r\nimport type { ITokenPriceRepository } from './domain/repositories';\r\nimport { GasPriceOracle } from './infrastructure/gas/GasPriceOracle';\r\nimport { MixerXFeeOracle } from './infrastructure/mixerx/MixerXFeeOracle';\r\nimport { TokenPriceOracle } from './infrastructure/token-price/TokenPriceOracle';\r\nimport type { ChainId, TokenPrices } from './types';\r\n\r\nexport interface MixerxOraclesConfig {\r\n chainId: ChainId;\r\n rpcUrl: string;\r\n relayerFeePercent: number;\r\n priceRepository?: ITokenPriceRepository;\r\n multicallAddress?: string;\r\n offchainOracleAddress?: string;\r\n logger?: ILogger;\r\n rpcTimeoutMs?: number;\r\n maxGasPriceGwei?: number;\r\n maxPriceAgeMs?: number;\r\n maxRefundWei?: bigint;\r\n fallbackGasPrices?: {\r\n legacy?: { instant: number; fast: number; standard: number; low: number };\r\n eip1559?: { baseFee: number; maxFeePerGas: number; maxPriorityFeePerGas: number };\r\n };\r\n}\r\n\r\nexport interface IMixerxOraclesModule {\r\n gasPriceOracle: GasPriceOracle;\r\n tokenPriceOracle: TokenPriceOracle;\r\n mixerXFeeOracle: MixerXFeeOracle;\r\n fetchPrices(): Promise<TokenPrices>;\r\n assertReady(): Promise<void>;\r\n chainId: ChainId;\r\n}\r\n\r\nexport function createMixerxOraclesModule(config: MixerxOraclesConfig): IMixerxOraclesModule {\r\n const logger = config.logger ?? new NullLogger();\r\n const gasPriceOracle = new GasPriceOracle({\r\n chainId: config.chainId,\r\n rpcUrl: config.rpcUrl,\r\n logger,\r\n fallbackGasPrices: config.fallbackGasPrices,\r\n rpcTimeoutMs: config.rpcTimeoutMs,\r\n maxGasPriceGwei: config.maxGasPriceGwei,\r\n });\r\n const tokenPriceOracle = new TokenPriceOracle(\r\n config.rpcUrl,\r\n config.chainId,\r\n config.offchainOracleAddress,\r\n config.multicallAddress,\r\n undefined,\r\n logger,\r\n config.rpcTimeoutMs,\r\n );\r\n const mixerXFeeOracle = new MixerXFeeOracle({\r\n chainId: config.chainId,\r\n rpcUrl: config.rpcUrl,\r\n relayerFeePercent: config.relayerFeePercent,\r\n gasPriceOracle,\r\n tokenPriceOracle,\r\n priceRepository: config.priceRepository,\r\n logger,\r\n rpcTimeoutMs: config.rpcTimeoutMs,\r\n maxGasPriceGwei: config.maxGasPriceGwei,\r\n maxPriceAgeMs: config.maxPriceAgeMs,\r\n maxRefundWei: config.maxRefundWei,\r\n });\r\n\r\n return {\r\n gasPriceOracle,\r\n tokenPriceOracle,\r\n mixerXFeeOracle,\r\n chainId: config.chainId,\r\n fetchPrices: () => tokenPriceOracle.fetchPrices(),\r\n assertReady: () => mixerXFeeOracle.assertReady(),\r\n };\r\n}\r\n"]}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,kDAAmE;AAA1D,yGAAA,WAAW,OAAA;AAAE,6GAAA,eAAe,OAAA","sourcesContent":["export * from './factory';\r\nexport { OracleError, TokenPriceError } from './domain/exceptions';\r\nexport type { OracleErrorCode } from './domain/exceptions';\r\nexport type { ILogger } from './application/ports/ILogger';\r\nexport type { ITokenPriceRepository } from './domain/repositories';\r\nexport type {\r\n FeeCalculationResult,\r\n MixerXGasPurpose,\r\n MixerXFeeCalculationResult,\r\n MixerXGasParametersForTxResult,\r\n MixerXTxType,\r\n WithdrawalFeeInput,\r\n} from './infrastructure/mixerx/MixerXFeeOracle';\r\n"]}