@mixerx/oracles 1.0.0 → 3.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 +90 -111
- package/dist/application/ports/ILogger.d.ts +0 -1
- package/dist/application/ports/ILogger.js +0 -1
- package/dist/domain/config/TokenDefaults.d.ts +0 -32
- package/dist/domain/config/TokenDefaults.js +7 -73
- package/dist/domain/exceptions.d.ts +8 -4
- package/dist/domain/exceptions.js +12 -4
- package/dist/domain/repositories.d.ts +3 -2
- package/dist/domain/repositories.js +10 -1
- package/dist/factory.d.ts +15 -46
- package/dist/factory.js +18 -28
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -1
- package/dist/infrastructure/blockchain/staticProvider.d.ts +3 -0
- package/dist/infrastructure/blockchain/staticProvider.js +36 -0
- package/dist/infrastructure/config/ProtocolTokenConfig.d.ts +0 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.js +7 -5
- package/dist/infrastructure/config/gasOracleConfigs.d.ts +2 -13
- package/dist/infrastructure/config/gasOracleConfigs.js +7 -34
- package/dist/infrastructure/gas/GasPriceOracle.d.ts +14 -17
- package/dist/infrastructure/gas/GasPriceOracle.js +89 -44
- package/dist/infrastructure/mixerx/MixerXFeeOracle.d.ts +99 -0
- package/dist/infrastructure/mixerx/MixerXFeeOracle.js +292 -0
- package/dist/infrastructure/multicall/MulticallProvider.d.ts +9 -3
- package/dist/infrastructure/multicall/MulticallProvider.js +48 -10
- package/dist/infrastructure/token-price/TokenPriceOracle.d.ts +10 -47
- package/dist/infrastructure/token-price/TokenPriceOracle.js +75 -107
- package/dist/types.d.ts +11 -2
- package/dist/types.js +0 -1
- package/package.json +37 -15
- package/dist/application/ports/ILogger.d.ts.map +0 -1
- package/dist/application/ports/ILogger.js.map +0 -1
- package/dist/application/ports/index.d.ts +0 -6
- package/dist/application/ports/index.d.ts.map +0 -1
- package/dist/application/ports/index.js +0 -18
- package/dist/application/ports/index.js.map +0 -1
- package/dist/application/services/TokenPriceService.d.ts +0 -61
- 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/TokenDefaults.js.map +0 -1
- package/dist/domain/config/index.d.ts +0 -2
- package/dist/domain/config/index.d.ts.map +0 -1
- package/dist/domain/config/index.js +0 -20
- package/dist/domain/config/index.js.map +0 -1
- package/dist/domain/exceptions.d.ts.map +0 -1
- package/dist/domain/exceptions.js.map +0 -1
- package/dist/domain/repositories.d.ts.map +0 -1
- package/dist/domain/repositories.js.map +0 -1
- package/dist/factory.d.ts.map +0 -1
- package/dist/factory.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.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/config/ProtocolTokenConfig.d.ts.map +0 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.js.map +0 -1
- package/dist/infrastructure/config/gasOracleConfigs.d.ts.map +0 -1
- package/dist/infrastructure/config/gasOracleConfigs.js.map +0 -1
- package/dist/infrastructure/config/index.d.ts +0 -5
- package/dist/infrastructure/config/index.d.ts.map +0 -1
- package/dist/infrastructure/config/index.js +0 -11
- package/dist/infrastructure/config/index.js.map +0 -1
- package/dist/infrastructure/gas/GasPriceOracle.d.ts.map +0 -1
- package/dist/infrastructure/gas/GasPriceOracle.js.map +0 -1
- package/dist/infrastructure/multicall/MulticallProvider.d.ts.map +0 -1
- package/dist/infrastructure/multicall/MulticallProvider.js.map +0 -1
- package/dist/infrastructure/token-price/TokenPriceOracle.d.ts.map +0 -1
- package/dist/infrastructure/token-price/TokenPriceOracle.js.map +0 -1
- package/dist/infrastructure/token-price/index.d.ts +0 -3
- package/dist/infrastructure/token-price/index.d.ts.map +0 -1
- package/dist/infrastructure/token-price/index.js +0 -6
- package/dist/infrastructure/token-price/index.js.map +0 -1
- package/dist/infrastructure/tornado/TornadoFeeOracle.d.ts +0 -90
- package/dist/infrastructure/tornado/TornadoFeeOracle.d.ts.map +0 -1
- package/dist/infrastructure/tornado/TornadoFeeOracle.js +0 -230
- package/dist/infrastructure/tornado/TornadoFeeOracle.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,139 +1,118 @@
|
|
|
1
1
|
# MixerX Oracles
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
Fail-closed Sepolia pricing and gas policy for MixerX. Version 3.0.0 rejects a
|
|
4
|
+
withdrawal quote when its gas or token price is missing, stale or economically
|
|
5
|
+
unsafe.
|
|
6
|
+
|
|
7
|
+
## Security contract
|
|
8
|
+
|
|
9
|
+
- Sepolia (`11155111`) is the only accepted chain.
|
|
10
|
+
- RPC endpoints must use HTTPS, are time-bounded and are checked against the
|
|
11
|
+
real `eth_chainId` response.
|
|
12
|
+
- Token calls use Multicall3 atomically at one block. Any failed token rejects
|
|
13
|
+
the batch; no historical constant is substituted.
|
|
14
|
+
- Non-native withdrawal fees require a repository price plus its observation
|
|
15
|
+
timestamp. The default maximum age is two minutes.
|
|
16
|
+
- Gas data, service fee, refund, token decimals and total fee are bounded before
|
|
17
|
+
a result is returned. The default total-fee ceiling matches the contracts at
|
|
18
|
+
10% of the withdrawal amount.
|
|
19
|
+
- Configurable token-price sanity bands can reject corrupted but fresh cache
|
|
20
|
+
values.
|
|
21
|
+
- The exact withdrawal gas limit is mandatory input and must come from
|
|
22
|
+
simulation or `eth_estimateGas`; the package contains no token-specific
|
|
23
|
+
limits inherited from another protocol.
|
|
24
|
+
- Logging is disabled by default. Inject an `ILogger` that follows the host
|
|
25
|
+
service's privacy policy if operational events are required.
|
|
26
|
+
|
|
27
|
+
The Sepolia OffchainOracle deployment must contain valid rates and must be
|
|
28
|
+
protected by the protocol's TWAP/deviation policy. The library cannot create or
|
|
29
|
+
initialize that on-chain state. Call `assertReady()` during application startup;
|
|
30
|
+
do not accept traffic if it fails.
|
|
15
31
|
|
|
16
32
|
## Installation
|
|
17
33
|
|
|
18
34
|
```bash
|
|
19
|
-
yarn add @mixerx/oracles
|
|
35
|
+
yarn add @mixerx/oracles@3.0.0
|
|
20
36
|
```
|
|
21
37
|
|
|
22
|
-
|
|
38
|
+
Node.js 24.12.0 or newer is required.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import {
|
|
44
|
+
createMixerxOraclesModule,
|
|
45
|
+
type ITokenPriceRepository,
|
|
46
|
+
} from '@mixerx/oracles';
|
|
23
47
|
|
|
24
|
-
|
|
25
|
-
|
|
48
|
+
const priceRepository: ITokenPriceRepository = {
|
|
49
|
+
getQuote: async (symbol) => readCachedQuote(symbol),
|
|
50
|
+
};
|
|
26
51
|
|
|
27
|
-
// Create module
|
|
28
52
|
const oracles = createMixerxOraclesModule({
|
|
29
|
-
chainId:
|
|
30
|
-
rpcUrl:
|
|
31
|
-
relayerFeePercent: 0.004,
|
|
32
|
-
priceRepository
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
chainId: 11155111,
|
|
54
|
+
rpcUrl: process.env.SEPOLIA_RPC_URL!,
|
|
55
|
+
relayerFeePercent: 0.004,
|
|
56
|
+
priceRepository,
|
|
57
|
+
maxGasPriceGwei: 100,
|
|
58
|
+
maxPriceAgeMs: 120_000,
|
|
59
|
+
maxTotalFeeBps: 1_000,
|
|
60
|
+
priceBounds: {
|
|
61
|
+
fds: {
|
|
62
|
+
minimumWeiPerToken: minimumAcceptedFdsPrice,
|
|
63
|
+
maximumWeiPerToken: maximumAcceptedFdsPrice,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
40
66
|
});
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
const prices = await oracles.fetchPrices();
|
|
44
|
-
console.log(prices); // { dai: '603108348359886', usdc: '601311723569085', ... }
|
|
68
|
+
await oracles.assertReady();
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
amount: BigInt('100000000000000000000'), // 100 DAI
|
|
70
|
+
const fee = await oracles.mixerXFeeOracle.calculateWithdrawalFee({
|
|
71
|
+
currency: 'fds',
|
|
72
|
+
amount: 1_000_000_000_000_000_000n,
|
|
50
73
|
decimals: 18,
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
gasLimit: estimatedWithdrawalGas,
|
|
75
|
+
refund: '0',
|
|
76
|
+
purpose: 'fee',
|
|
53
77
|
});
|
|
54
78
|
|
|
55
|
-
console.log(
|
|
79
|
+
console.log({
|
|
80
|
+
totalFee: fee.totalFee,
|
|
81
|
+
tokenPriceInEth: fee.tokenPriceInEth,
|
|
82
|
+
priceSource: fee.priceSource,
|
|
83
|
+
priceObservedAtMs: fee.priceObservedAtMs,
|
|
84
|
+
baseFeePerGas: fee.baseFeePerGas,
|
|
85
|
+
effectiveGasPriceWei: fee.gasPriceWei,
|
|
86
|
+
gasSource: fee.gasSource,
|
|
87
|
+
});
|
|
56
88
|
```
|
|
57
89
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
This library follows Domain-Driven Design (DDD) principles:
|
|
61
|
-
|
|
62
|
-
- **Domain**: Value objects and entities for fees, gas, and tokens
|
|
63
|
-
- **Application**: Services and use cases for fee calculation and price fetching
|
|
64
|
-
- **Infrastructure**: Adapters for blockchain interaction (ethers)
|
|
65
|
-
|
|
66
|
-
## Features
|
|
67
|
-
|
|
68
|
-
### Fee Calculation (V5)
|
|
69
|
-
|
|
70
|
-
- Smart gas limit bumping (10% for relayer, 30% for user)
|
|
71
|
-
- On-chain gas estimation with fallback to defaults
|
|
72
|
-
- Support for ETH and ERC-20 tokens
|
|
73
|
-
- Automatic conversion between ETH and token units
|
|
74
|
-
- EIP-1559 and Legacy gas price support
|
|
75
|
-
|
|
76
|
-
### Token Price Fetching
|
|
90
|
+
`fee_quote` and `fee_validation` remain accepted as input aliases for migration,
|
|
91
|
+
but both normalize to the single `fee` policy in returned data.
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
- Uses Multicall for efficient batch requests
|
|
80
|
-
- Fallback to default prices if fetching fails
|
|
81
|
-
- Token list is resolved from `@mixerx/config` by `chainId`
|
|
93
|
+
## Price-feed operation
|
|
82
94
|
|
|
83
|
-
|
|
95
|
+
`fetchPrices()` reads all configured Sepolia tokens through Multicall3 at one
|
|
96
|
+
explicit block. It returns the complete price map together with `blockNumber`
|
|
97
|
+
and `observedAtMs`, and only succeeds when every token succeeds. The host price
|
|
98
|
+
feed must persist each price and its observation metadata atomically. Fee
|
|
99
|
+
calculation never falls back from that repository to a spot value.
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
The default Multicall3 address is
|
|
102
|
+
`0xcA11bde05977b3631167028862bE2a173976CA11`; an explicit verified address can
|
|
103
|
+
be supplied with `multicallAddress`.
|
|
88
104
|
|
|
89
|
-
##
|
|
105
|
+
## Development
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- `chainId`: Chain ID (1 for Mainnet)
|
|
97
|
-
- `rpcUrl`: RPC endpoint URL
|
|
98
|
-
- `relayerFeePercent`: Fee percentage (e.g., 0.004 for 0.4%)
|
|
99
|
-
- `priceRepository`: Implementation of `ITokenPriceRepository`
|
|
100
|
-
- `multicallAddress?`: Multicall contract address
|
|
101
|
-
- `offchainOracleAddress?`: OffchainOracle contract address (fallback to `@mixerx/config` by `chainId`)
|
|
102
|
-
- `fallbackGasPrices?`: Optional fallback gas prices
|
|
103
|
-
|
|
104
|
-
### Use Cases
|
|
105
|
-
|
|
106
|
-
#### `tornadoFeeOracle.calculateWithdrawalFee(input)`
|
|
107
|
-
|
|
108
|
-
Calculate and validate fees for Tornado Cash withdrawals.
|
|
109
|
-
|
|
110
|
-
**Input:**
|
|
111
|
-
- `currency`: Token symbol (eth, dai, usdc, etc.)
|
|
112
|
-
- `amount`: Withdrawal amount in base units (`bigint`)
|
|
113
|
-
- `decimals`: Token decimals
|
|
114
|
-
- `refund?`: Optional refund amount (hex string)
|
|
115
|
-
- `txType?`: `'relayer_withdrawal' | 'user_withdrawal' | 'relayer_withdrawal_check_v4'`
|
|
116
|
-
|
|
117
|
-
**Output:**
|
|
118
|
-
- `gasCost`: bigint
|
|
119
|
-
- `relayerFee`: bigint
|
|
120
|
-
- `refundAmount`: bigint
|
|
121
|
-
- `totalFee`: bigint
|
|
122
|
-
- `currency`: string
|
|
123
|
-
|
|
124
|
-
#### `fetchTokenPrices.execute(input?)`
|
|
125
|
-
|
|
126
|
-
Fetch current token prices from on-chain oracle.
|
|
127
|
-
|
|
128
|
-
**Input (optional):**
|
|
129
|
-
- `tokens?`: Specific tokens to fetch
|
|
130
|
-
- `useDefaults?`: Use default prices as fallback
|
|
107
|
+
```bash
|
|
108
|
+
yarn install --immutable
|
|
109
|
+
yarn validate
|
|
110
|
+
yarn npm audit --all --recursive --severity high
|
|
111
|
+
```
|
|
131
112
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- `failedTokens`: Tokens that failed to fetch
|
|
135
|
-
- `usedDefaults`: Whether defaults were used
|
|
113
|
+
CI executes those gates for every pull request. Published packages pin the
|
|
114
|
+
protocol configuration dependency exactly.
|
|
136
115
|
|
|
137
116
|
## License
|
|
138
117
|
|
|
139
|
-
|
|
118
|
+
GPL-3.0-only. See `LICENSE`.
|
|
@@ -1,33 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token Defaults
|
|
3
|
-
* Centralized configuration for Tornado Cash tokens
|
|
4
|
-
*
|
|
5
|
-
* This eliminates duplication between:
|
|
6
|
-
* - TokenPriceOracle
|
|
7
|
-
* - TokenPriceService
|
|
8
|
-
* - FeeCalculator
|
|
9
|
-
*
|
|
10
|
-
* All token addresses, decimals, and default prices in one place
|
|
11
|
-
*/
|
|
12
|
-
export declare const DEFAULT_TOKEN_PRICES: Record<string, string>;
|
|
13
|
-
export declare const TORNADO_GAS_LIMITS: Record<string, number>;
|
|
14
|
-
export declare const MINING_GAS_LIMITS: Record<string, number>;
|
|
15
|
-
export declare const DEFAULT_RELAYER_FEE_PERCENT = 0.004;
|
|
16
|
-
export declare const BUMP_PERCENTAGES: Record<string, number>;
|
|
17
|
-
/**
|
|
18
|
-
* Get default price for token
|
|
19
|
-
*/
|
|
20
|
-
export declare function getDefaultTokenPrice(symbol: string): string | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* Get gas limit for Tornado operation
|
|
23
|
-
*/
|
|
24
|
-
export declare function getTornadoGasLimit(currency: string): number;
|
|
25
|
-
/**
|
|
26
|
-
* Get mining gas limit
|
|
27
|
-
*/
|
|
28
|
-
export declare function getMiningGasLimit(operation: 'reward' | 'withdraw'): number;
|
|
29
|
-
/**
|
|
30
|
-
* Get bump percent for transaction type
|
|
31
|
-
*/
|
|
32
1
|
export declare function getBumpPercent(txType: string): number;
|
|
33
|
-
//# sourceMappingURL=TokenDefaults.d.ts.map
|
|
@@ -1,78 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Token Defaults
|
|
4
|
-
* Centralized configuration for Tornado Cash tokens
|
|
5
|
-
*
|
|
6
|
-
* This eliminates duplication between:
|
|
7
|
-
* - TokenPriceOracle
|
|
8
|
-
* - TokenPriceService
|
|
9
|
-
* - FeeCalculator
|
|
10
|
-
*
|
|
11
|
-
* All token addresses, decimals, and default prices in one place
|
|
12
|
-
*/
|
|
13
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.BUMP_PERCENTAGES = exports.DEFAULT_RELAYER_FEE_PERCENT = exports.MINING_GAS_LIMITS = exports.TORNADO_GAS_LIMITS = exports.DEFAULT_TOKEN_PRICES = void 0;
|
|
15
|
-
exports.getDefaultTokenPrice = getDefaultTokenPrice;
|
|
16
|
-
exports.getTornadoGasLimit = getTornadoGasLimit;
|
|
17
|
-
exports.getMiningGasLimit = getMiningGasLimit;
|
|
18
3
|
exports.getBumpPercent = getBumpPercent;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
torn: '1689423546359032',
|
|
25
|
-
dai: '598416104472725',
|
|
26
|
-
cdai: '13384388487019',
|
|
27
|
-
usdc: '599013776676721',
|
|
28
|
-
usdt: '599323410893614',
|
|
29
|
-
wbtc: '15659889148334216720',
|
|
30
|
-
};
|
|
31
|
-
// Gas limits for Tornado operations by token (in gas units)
|
|
32
|
-
exports.TORNADO_GAS_LIMITS = {
|
|
33
|
-
eth: 390000,
|
|
34
|
-
dai: 550000,
|
|
35
|
-
cdai: 425000,
|
|
36
|
-
usdc: 80000,
|
|
37
|
-
usdt: 100000,
|
|
38
|
-
wbtc: 85000,
|
|
39
|
-
};
|
|
40
|
-
// Mining operation gas limits
|
|
41
|
-
exports.MINING_GAS_LIMITS = {
|
|
42
|
-
reward: 455000,
|
|
43
|
-
withdraw: 400000,
|
|
44
|
-
};
|
|
45
|
-
// Relayer fee percentage (0.4% default)
|
|
46
|
-
exports.DEFAULT_RELAYER_FEE_PERCENT = 0.004;
|
|
47
|
-
// Bump percentages by transaction type
|
|
48
|
-
exports.BUMP_PERCENTAGES = {
|
|
49
|
-
relayer_withdrawal: 10,
|
|
50
|
-
user_withdrawal: 30,
|
|
51
|
-
relayer_withdrawal_check_v4: 0,
|
|
52
|
-
default: 10,
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Get default price for token
|
|
56
|
-
*/
|
|
57
|
-
function getDefaultTokenPrice(symbol) {
|
|
58
|
-
return exports.DEFAULT_TOKEN_PRICES[symbol.toLowerCase()];
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get gas limit for Tornado operation
|
|
62
|
-
*/
|
|
63
|
-
function getTornadoGasLimit(currency) {
|
|
64
|
-
return exports.TORNADO_GAS_LIMITS[currency.toLowerCase()] ?? exports.TORNADO_GAS_LIMITS.eth;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Get mining gas limit
|
|
68
|
-
*/
|
|
69
|
-
function getMiningGasLimit(operation) {
|
|
70
|
-
return exports.MINING_GAS_LIMITS[operation] ?? 400000;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Get bump percent for transaction type
|
|
74
|
-
*/
|
|
4
|
+
const BUMP_PERCENTAGES = new Map([
|
|
5
|
+
['relayer_withdrawal', 10],
|
|
6
|
+
['user_withdrawal', 30],
|
|
7
|
+
['relayer_withdrawal_check_v4', 0],
|
|
8
|
+
]);
|
|
75
9
|
function getBumpPercent(txType) {
|
|
76
|
-
|
|
10
|
+
const key = txType.trim().toLowerCase();
|
|
11
|
+
return BUMP_PERCENTAGES.get(key) ?? 10;
|
|
77
12
|
}
|
|
78
|
-
//# sourceMappingURL=TokenDefaults.js.map
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export type OracleErrorCode = 'INVALID_CONFIG' | 'INVALID_INPUT' | 'NETWORK_MISMATCH' | 'RPC_REQUEST_FAILED' | 'TOKEN_PRICE_UNAVAILABLE' | 'TOKEN_PRICE_STALE' | 'TOKEN_PRICE_OUT_OF_RANGE' | 'ECONOMIC_LIMIT_EXCEEDED';
|
|
2
|
+
export declare class OracleError extends Error {
|
|
3
|
+
readonly code: OracleErrorCode;
|
|
4
|
+
constructor(code: OracleErrorCode, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class TokenPriceError extends OracleError {
|
|
7
|
+
readonly symbols: readonly string[];
|
|
8
|
+
constructor(message: string, symbols?: readonly string[]);
|
|
4
9
|
}
|
|
5
|
-
//# sourceMappingURL=exceptions.d.ts.map
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TokenPriceError = void 0;
|
|
4
|
-
class
|
|
3
|
+
exports.TokenPriceError = exports.OracleError = void 0;
|
|
4
|
+
class OracleError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = 'OracleError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.OracleError = OracleError;
|
|
13
|
+
class TokenPriceError extends OracleError {
|
|
5
14
|
symbols;
|
|
6
15
|
constructor(message, symbols = []) {
|
|
7
|
-
super(message);
|
|
16
|
+
super('TOKEN_PRICE_UNAVAILABLE', message);
|
|
8
17
|
this.symbols = symbols;
|
|
9
18
|
this.name = 'TokenPriceError';
|
|
10
19
|
}
|
|
11
20
|
}
|
|
12
21
|
exports.TokenPriceError = TokenPriceError;
|
|
13
|
-
//# sourceMappingURL=exceptions.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { TokenPriceQuote } from '../types';
|
|
1
2
|
export interface ITokenPriceRepository {
|
|
2
|
-
|
|
3
|
+
getQuote(symbol: string): Promise<TokenPriceQuote | null | undefined>;
|
|
3
4
|
}
|
|
4
|
-
|
|
5
|
+
export declare function toPriceQuote(price: bigint | null | undefined, observedAtMs: number | null | undefined): TokenPriceQuote | null;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
exports.toPriceQuote = toPriceQuote;
|
|
4
|
+
function toPriceQuote(price, observedAtMs) {
|
|
5
|
+
if (price === null || price === undefined || price <= 0n) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (!Number.isSafeInteger(observedAtMs) || (observedAtMs ?? -1) < 0) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return { priceInEthWei: price, observedAtMs: observedAtMs, source: 'repository' };
|
|
12
|
+
}
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,62 +1,31 @@
|
|
|
1
|
+
import type { ILogger } from './application/ports/ILogger';
|
|
2
|
+
import type { ITokenPriceRepository } from './domain/repositories';
|
|
1
3
|
import { GasPriceOracle } from './infrastructure/gas/GasPriceOracle';
|
|
4
|
+
import { MixerXFeeOracle } from './infrastructure/mixerx/MixerXFeeOracle';
|
|
5
|
+
import type { TokenPriceBounds } from './infrastructure/mixerx/MixerXFeeOracle';
|
|
2
6
|
import { TokenPriceOracle } from './infrastructure/token-price/TokenPriceOracle';
|
|
3
|
-
import {
|
|
4
|
-
import { ITokenPriceRepository } from './domain/repositories';
|
|
5
|
-
import { ChainId } from './infrastructure/config/gasOracleConfigs';
|
|
6
|
-
import { TokenPrices } from './types';
|
|
7
|
-
/**
|
|
8
|
-
* Simplified Configuration for mixerx-oracles module
|
|
9
|
-
*/
|
|
7
|
+
import type { ChainId, TokenPriceSnapshot } from './types';
|
|
10
8
|
export interface MixerxOraclesConfig {
|
|
11
|
-
/** Chain ID */
|
|
12
9
|
chainId: ChainId;
|
|
13
|
-
/** RPC URL for blockchain connection */
|
|
14
10
|
rpcUrl: string;
|
|
15
|
-
/** Relayer fee percentage (e.g., 0.004 for 0.4%) */
|
|
16
11
|
relayerFeePercent: number;
|
|
17
|
-
/** Token price repository (for reading prices from Redis/cache) - optional */
|
|
18
12
|
priceRepository?: ITokenPriceRepository;
|
|
19
|
-
/** Multicall contract address (optional, uses default) */
|
|
20
13
|
multicallAddress?: string;
|
|
21
|
-
/** Offchain oracle contract address (optional, falls back to @mixerx/config by chain) */
|
|
22
14
|
offchainOracleAddress?: string;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
eip1559?: {
|
|
32
|
-
baseFee: number;
|
|
33
|
-
maxFeePerGas: number;
|
|
34
|
-
maxPriorityFeePerGas: number;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
15
|
+
logger?: ILogger;
|
|
16
|
+
rpcTimeoutMs?: number;
|
|
17
|
+
maxGasPriceGwei?: number;
|
|
18
|
+
maxPriceAgeMs?: number;
|
|
19
|
+
maxRefundWei?: bigint;
|
|
20
|
+
maxTotalFeeBps?: number;
|
|
21
|
+
priceBounds?: Readonly<Record<string, TokenPriceBounds>>;
|
|
37
22
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Mixerx Oracles Module Interface
|
|
40
|
-
*/
|
|
41
23
|
export interface IMixerxOraclesModule {
|
|
42
|
-
/** Gas price oracle (EIP-1559 + Legacy) */
|
|
43
24
|
gasPriceOracle: GasPriceOracle;
|
|
44
|
-
/** Token price oracle (Multicall + OffchainOracle) */
|
|
45
25
|
tokenPriceOracle: TokenPriceOracle;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fetchPrices(): Promise<TokenPrices>;
|
|
50
|
-
/** Chain ID */
|
|
26
|
+
mixerXFeeOracle: MixerXFeeOracle;
|
|
27
|
+
fetchPrices(): Promise<TokenPriceSnapshot>;
|
|
28
|
+
assertReady(): Promise<void>;
|
|
51
29
|
chainId: ChainId;
|
|
52
30
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Factory function to create mixerx-oracles module
|
|
55
|
-
*
|
|
56
|
-
* This creates a complete oracle module with:
|
|
57
|
-
* - GasPriceOracle: Fetches EIP-1559 and Legacy gas prices from multiple sources
|
|
58
|
-
* - TokenPriceOracle: Fetches token prices using Multicall to OffchainOracle
|
|
59
|
-
* - TornadoFeeOracle: Calculates Tornado Cash withdrawal fees
|
|
60
|
-
*/
|
|
61
31
|
export declare function createMixerxOraclesModule(config: MixerxOraclesConfig): IMixerxOraclesModule;
|
|
62
|
-
//# sourceMappingURL=factory.d.ts.map
|
package/dist/factory.js
CHANGED
|
@@ -1,51 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createMixerxOraclesModule = createMixerxOraclesModule;
|
|
4
|
+
const ILogger_1 = require("./application/ports/ILogger");
|
|
4
5
|
const GasPriceOracle_1 = require("./infrastructure/gas/GasPriceOracle");
|
|
6
|
+
const MixerXFeeOracle_1 = require("./infrastructure/mixerx/MixerXFeeOracle");
|
|
5
7
|
const TokenPriceOracle_1 = require("./infrastructure/token-price/TokenPriceOracle");
|
|
6
|
-
const TornadoFeeOracle_1 = require("./infrastructure/tornado/TornadoFeeOracle");
|
|
7
|
-
const ProtocolTokenConfig_1 = require("./infrastructure/config/ProtocolTokenConfig");
|
|
8
|
-
/**
|
|
9
|
-
* Factory function to create mixerx-oracles module
|
|
10
|
-
*
|
|
11
|
-
* This creates a complete oracle module with:
|
|
12
|
-
* - GasPriceOracle: Fetches EIP-1559 and Legacy gas prices from multiple sources
|
|
13
|
-
* - TokenPriceOracle: Fetches token prices using Multicall to OffchainOracle
|
|
14
|
-
* - TornadoFeeOracle: Calculates Tornado Cash withdrawal fees
|
|
15
|
-
*/
|
|
16
8
|
function createMixerxOraclesModule(config) {
|
|
17
|
-
|
|
18
|
-
const multicallAddress = config.multicallAddress || '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441';
|
|
19
|
-
const offchainOracleAddress = config.offchainOracleAddress || (0, ProtocolTokenConfig_1.resolveOffchainOracleAddress)(config.chainId);
|
|
20
|
-
const defaultTokens = (0, ProtocolTokenConfig_1.resolveDefaultTokens)(config.chainId);
|
|
21
|
-
// Create gas price oracle
|
|
9
|
+
const logger = config.logger ?? new ILogger_1.NullLogger();
|
|
22
10
|
const gasPriceOracle = new GasPriceOracle_1.GasPriceOracle({
|
|
23
11
|
chainId: config.chainId,
|
|
24
12
|
rpcUrl: config.rpcUrl,
|
|
25
|
-
|
|
13
|
+
logger,
|
|
14
|
+
rpcTimeoutMs: config.rpcTimeoutMs,
|
|
15
|
+
maxGasPriceGwei: config.maxGasPriceGwei,
|
|
26
16
|
});
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
// Create tornado fee oracle
|
|
30
|
-
const tornadoFeeOracle = new TornadoFeeOracle_1.TornadoFeeOracle({
|
|
17
|
+
const tokenPriceOracle = new TokenPriceOracle_1.TokenPriceOracle(config.rpcUrl, config.chainId, config.offchainOracleAddress, config.multicallAddress, undefined, logger, config.rpcTimeoutMs);
|
|
18
|
+
const mixerXFeeOracle = new MixerXFeeOracle_1.MixerXFeeOracle({
|
|
31
19
|
chainId: config.chainId,
|
|
32
20
|
rpcUrl: config.rpcUrl,
|
|
33
21
|
relayerFeePercent: config.relayerFeePercent,
|
|
34
22
|
gasPriceOracle,
|
|
35
23
|
tokenPriceOracle,
|
|
36
24
|
priceRepository: config.priceRepository,
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
logger,
|
|
26
|
+
rpcTimeoutMs: config.rpcTimeoutMs,
|
|
27
|
+
maxGasPriceGwei: config.maxGasPriceGwei,
|
|
28
|
+
maxPriceAgeMs: config.maxPriceAgeMs,
|
|
29
|
+
maxRefundWei: config.maxRefundWei,
|
|
30
|
+
maxTotalFeeBps: config.maxTotalFeeBps,
|
|
31
|
+
priceBounds: config.priceBounds,
|
|
39
32
|
});
|
|
40
33
|
return {
|
|
41
34
|
gasPriceOracle,
|
|
42
35
|
tokenPriceOracle,
|
|
43
|
-
|
|
36
|
+
mixerXFeeOracle,
|
|
44
37
|
chainId: config.chainId,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return tokenPriceOracle.fetchPrices();
|
|
48
|
-
},
|
|
38
|
+
fetchPrices: () => tokenPriceOracle.fetchPrices(),
|
|
39
|
+
assertReady: () => mixerXFeeOracle.assertReady(),
|
|
49
40
|
};
|
|
50
41
|
}
|
|
51
|
-
//# sourceMappingURL=factory.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export * from './factory';
|
|
2
|
-
|
|
2
|
+
export { OracleError, TokenPriceError } from './domain/exceptions';
|
|
3
|
+
export type { OracleErrorCode } from './domain/exceptions';
|
|
4
|
+
export type { ILogger } from './application/ports/ILogger';
|
|
5
|
+
export type { ITokenPriceRepository } from './domain/repositories';
|
|
6
|
+
export type { TokenPriceQuote, TokenPriceSnapshot } from './types';
|
|
7
|
+
export type { FeeCalculationResult, MixerXGasPurpose, MixerXFeeCalculationResult, MixerXGasParametersForTxResult, MixerXTxType, WithdrawalFeeInput, } from './infrastructure/mixerx/MixerXFeeOracle';
|
package/dist/index.js
CHANGED
|
@@ -14,5 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.TokenPriceError = exports.OracleError = void 0;
|
|
17
18
|
__exportStar(require("./factory"), exports);
|
|
18
|
-
|
|
19
|
+
var exceptions_1 = require("./domain/exceptions");
|
|
20
|
+
Object.defineProperty(exports, "OracleError", { enumerable: true, get: function () { return exceptions_1.OracleError; } });
|
|
21
|
+
Object.defineProperty(exports, "TokenPriceError", { enumerable: true, get: function () { return exceptions_1.TokenPriceError; } });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ethers, type JsonRpcApiProviderOptions } from 'ethers';
|
|
2
|
+
export declare function createVerifiedJsonRpcProvider(rpcUrl: string, chainId: number, options?: JsonRpcApiProviderOptions, timeoutMs?: number): ethers.JsonRpcProvider;
|
|
3
|
+
export declare function assertProviderNetwork(provider: ethers.JsonRpcProvider, expectedChainId: number): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createVerifiedJsonRpcProvider = createVerifiedJsonRpcProvider;
|
|
4
|
+
exports.assertProviderNetwork = assertProviderNetwork;
|
|
5
|
+
const ethers_1 = require("ethers");
|
|
6
|
+
const exceptions_1 = require("../../domain/exceptions");
|
|
7
|
+
const gasOracleConfigs_1 = require("../config/gasOracleConfigs");
|
|
8
|
+
const DEFAULT_RPC_TIMEOUT_MS = 10_000;
|
|
9
|
+
function createVerifiedJsonRpcProvider(rpcUrl, chainId, options, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) {
|
|
10
|
+
(0, gasOracleConfigs_1.assertSepoliaChainId)(chainId);
|
|
11
|
+
validateRpcUrl(rpcUrl);
|
|
12
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1_000 || timeoutMs > 60_000) {
|
|
13
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'rpcTimeoutMs must be an integer between 1000 and 60000');
|
|
14
|
+
}
|
|
15
|
+
const request = new ethers_1.FetchRequest(rpcUrl);
|
|
16
|
+
request.timeout = timeoutMs;
|
|
17
|
+
return new ethers_1.ethers.JsonRpcProvider(request, undefined, options);
|
|
18
|
+
}
|
|
19
|
+
async function assertProviderNetwork(provider, expectedChainId) {
|
|
20
|
+
const network = await provider.getNetwork();
|
|
21
|
+
if (network.chainId !== BigInt(expectedChainId)) {
|
|
22
|
+
throw new exceptions_1.OracleError('NETWORK_MISMATCH', `RPC network mismatch: expected ${String(expectedChainId)}, received ${network.chainId.toString()}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function validateRpcUrl(rawUrl) {
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(rawUrl);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'rpcUrl must be a valid HTTPS URL');
|
|
32
|
+
}
|
|
33
|
+
if (url.protocol !== 'https:' || url.username || url.password) {
|
|
34
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', 'rpcUrl must use HTTPS and must not contain user-info credentials');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { ChainId, Token } from '../../types';
|
|
2
2
|
export declare function resolveOffchainOracleAddress(chainId: ChainId | number): string;
|
|
3
3
|
export declare function resolveDefaultTokens(chainId: ChainId | number): Token[];
|
|
4
|
-
//# sourceMappingURL=ProtocolTokenConfig.d.ts.map
|