@mixerx/oracles 3.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.
- package/README.md +96 -90
- package/dist/infrastructure/blockchain/staticProvider.js +1 -1
- package/dist/infrastructure/config/ProtocolTokenConfig.js +1 -1
- package/dist/infrastructure/config/gasOracleConfigs.d.ts +1 -2
- package/dist/infrastructure/config/gasOracleConfigs.js +5 -6
- package/dist/infrastructure/mixerx/MixerXFeeOracle.js +1 -1
- package/dist/infrastructure/multicall/MulticallProvider.js +1 -1
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -1,59 +1,65 @@
|
|
|
1
|
-
# MixerX Oracles
|
|
2
|
-
|
|
3
|
-
Fail-closed
|
|
1
|
+
# MixerX Oracles
|
|
2
|
+
|
|
3
|
+
Fail-closed Ethereum pricing and gas policy for MixerX. Version 3.1.0 rejects a
|
|
4
4
|
withdrawal quote when its gas or token price is missing, stale or economically
|
|
5
5
|
unsafe.
|
|
6
|
-
|
|
7
|
-
## Security contract
|
|
8
|
-
|
|
9
|
-
- Sepolia (`11155111`)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
|
|
7
|
+
## Security contract
|
|
8
|
+
|
|
9
|
+
- Ethereum mainnet (`1`) and Sepolia (`11155111`) are accepted, as defined by
|
|
10
|
+
`@mixerx/config`. A fork must report a supported chain ID.
|
|
11
|
+
- RPC endpoints must use HTTPS, are time-bounded and are checked against the
|
|
12
|
+
real `eth_chainId` response.
|
|
13
|
+
- Token calls use Multicall3 atomically at one block. Any failed token rejects
|
|
14
|
+
the batch; no historical constant is substituted.
|
|
15
|
+
- Non-native withdrawal fees require a repository price plus its observation
|
|
16
|
+
timestamp. The default maximum age is two minutes.
|
|
16
17
|
- Gas data, service fee, refund, token decimals and total fee are bounded before
|
|
17
18
|
a result is returned. The default total-fee ceiling matches the contracts at
|
|
18
19
|
10% of the withdrawal amount.
|
|
19
20
|
- Configurable token-price sanity bands can reject corrupted but fresh cache
|
|
20
21
|
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
|
|
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.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
- The exact withdrawal gas limit is mandatory input and must come from
|
|
23
|
+
simulation or `eth_estimateGas`; the package contains no token-specific
|
|
24
|
+
limits inherited from another protocol.
|
|
25
|
+
- Logging is disabled by default. Inject an `ILogger` that follows the host
|
|
26
|
+
service's privacy policy if operational events are required.
|
|
27
|
+
|
|
28
|
+
The selected network's OffchainOracle deployment must contain valid rates and must be
|
|
29
|
+
protected by the protocol's TWAP/deviation policy. The library cannot create or
|
|
30
|
+
initialize that on-chain state. Call `assertReady()` during application startup;
|
|
31
|
+
do not accept traffic if it fails.
|
|
32
|
+
|
|
33
|
+
Mainnet addresses in Config 1.1.0 are provisional copies of Sepolia. Accepting
|
|
34
|
+
chain ID `1` only enables backend configuration; it does not establish that
|
|
35
|
+
contracts or oracle rates exist. Readiness and pricing still fail when required
|
|
36
|
+
on-chain data is absent or the RPC reports another chain.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
yarn add @mixerx/oracles@3.1.0
|
|
42
|
+
```
|
|
43
|
+
|
|
38
44
|
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';
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import {
|
|
50
|
+
createMixerxOraclesModule,
|
|
51
|
+
type ITokenPriceRepository,
|
|
52
|
+
} from '@mixerx/oracles';
|
|
53
|
+
|
|
48
54
|
const priceRepository: ITokenPriceRepository = {
|
|
49
55
|
getQuote: async (symbol) => readCachedQuote(symbol),
|
|
50
56
|
};
|
|
51
|
-
|
|
52
|
-
const oracles = createMixerxOraclesModule({
|
|
53
|
-
chainId: 11155111,
|
|
54
|
-
rpcUrl: process.env.SEPOLIA_RPC_URL!,
|
|
55
|
-
relayerFeePercent: 0.004,
|
|
56
|
-
priceRepository,
|
|
57
|
+
|
|
58
|
+
const oracles = createMixerxOraclesModule({
|
|
59
|
+
chainId: 11155111,
|
|
60
|
+
rpcUrl: process.env.SEPOLIA_RPC_URL!,
|
|
61
|
+
relayerFeePercent: 0.004,
|
|
62
|
+
priceRepository,
|
|
57
63
|
maxGasPriceGwei: 100,
|
|
58
64
|
maxPriceAgeMs: 120_000,
|
|
59
65
|
maxTotalFeeBps: 1_000,
|
|
@@ -63,56 +69,56 @@ const oracles = createMixerxOraclesModule({
|
|
|
63
69
|
maximumWeiPerToken: maximumAcceptedFdsPrice,
|
|
64
70
|
},
|
|
65
71
|
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await oracles.assertReady();
|
|
69
|
-
|
|
70
|
-
const fee = await oracles.mixerXFeeOracle.calculateWithdrawalFee({
|
|
71
|
-
currency: 'fds',
|
|
72
|
-
amount: 1_000_000_000_000_000_000n,
|
|
73
|
-
decimals: 18,
|
|
74
|
-
gasLimit: estimatedWithdrawalGas,
|
|
75
|
-
refund: '0',
|
|
76
|
-
purpose: 'fee',
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
console.log({
|
|
80
|
-
totalFee: fee.totalFee,
|
|
81
|
-
tokenPriceInEth: fee.tokenPriceInEth,
|
|
82
|
-
priceSource: fee.priceSource,
|
|
83
|
-
priceObservedAtMs: fee.priceObservedAtMs,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await oracles.assertReady();
|
|
75
|
+
|
|
76
|
+
const fee = await oracles.mixerXFeeOracle.calculateWithdrawalFee({
|
|
77
|
+
currency: 'fds',
|
|
78
|
+
amount: 1_000_000_000_000_000_000n,
|
|
79
|
+
decimals: 18,
|
|
80
|
+
gasLimit: estimatedWithdrawalGas,
|
|
81
|
+
refund: '0',
|
|
82
|
+
purpose: 'fee',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log({
|
|
86
|
+
totalFee: fee.totalFee,
|
|
87
|
+
tokenPriceInEth: fee.tokenPriceInEth,
|
|
88
|
+
priceSource: fee.priceSource,
|
|
89
|
+
priceObservedAtMs: fee.priceObservedAtMs,
|
|
84
90
|
baseFeePerGas: fee.baseFeePerGas,
|
|
85
91
|
effectiveGasPriceWei: fee.gasPriceWei,
|
|
86
92
|
gasSource: fee.gasSource,
|
|
87
93
|
});
|
|
88
|
-
```
|
|
89
|
-
|
|
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.
|
|
92
|
-
|
|
93
|
-
## Price-feed operation
|
|
94
|
-
|
|
95
|
-
`fetchPrices()` reads all configured
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`fee_quote` and `fee_validation` remain accepted as input aliases for migration,
|
|
97
|
+
but both normalize to the single `fee` policy in returned data.
|
|
98
|
+
|
|
99
|
+
## Price-feed operation
|
|
100
|
+
|
|
101
|
+
`fetchPrices()` reads all tokens configured for the selected network through Multicall3 at one
|
|
96
102
|
explicit block. It returns the complete price map together with `blockNumber`
|
|
97
103
|
and `observedAtMs`, and only succeeds when every token succeeds. The host price
|
|
98
104
|
feed must persist each price and its observation metadata atomically. Fee
|
|
99
105
|
calculation never falls back from that repository to a spot value.
|
|
100
|
-
|
|
101
|
-
The default Multicall3 address is
|
|
102
|
-
`0xcA11bde05977b3631167028862bE2a173976CA11`; an explicit verified address can
|
|
103
|
-
be supplied with `multicallAddress`.
|
|
104
|
-
|
|
105
|
-
## Development
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
yarn install --immutable
|
|
109
|
-
yarn validate
|
|
110
|
-
yarn npm audit --all --recursive --severity high
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
CI executes those gates for every pull request. Published packages pin the
|
|
114
|
-
protocol configuration dependency exactly.
|
|
115
|
-
|
|
116
|
-
## License
|
|
117
|
-
|
|
118
|
-
GPL-3.0-only. See `LICENSE`.
|
|
106
|
+
|
|
107
|
+
The default Multicall3 address is
|
|
108
|
+
`0xcA11bde05977b3631167028862bE2a173976CA11`; an explicit verified address can
|
|
109
|
+
be supplied with `multicallAddress`.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
yarn install --immutable
|
|
115
|
+
yarn validate
|
|
116
|
+
yarn npm audit --all --recursive --severity high
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
CI executes those gates for every pull request. Published packages pin the
|
|
120
|
+
protocol configuration dependency exactly.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
GPL-3.0-only. See `LICENSE`.
|
|
@@ -7,7 +7,7 @@ const exceptions_1 = require("../../domain/exceptions");
|
|
|
7
7
|
const gasOracleConfigs_1 = require("../config/gasOracleConfigs");
|
|
8
8
|
const DEFAULT_RPC_TIMEOUT_MS = 10_000;
|
|
9
9
|
function createVerifiedJsonRpcProvider(rpcUrl, chainId, options, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) {
|
|
10
|
-
(0, gasOracleConfigs_1.
|
|
10
|
+
(0, gasOracleConfigs_1.assertSupportedChainId)(chainId);
|
|
11
11
|
validateRpcUrl(rpcUrl);
|
|
12
12
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 1_000 || timeoutMs > 60_000) {
|
|
13
13
|
throw new exceptions_1.OracleError('INVALID_CONFIG', 'rpcTimeoutMs must be an integer between 1000 and 60000');
|
|
@@ -7,7 +7,7 @@ const exceptions_1 = require("../../domain/exceptions");
|
|
|
7
7
|
const gasOracleConfigs_1 = require("./gasOracleConfigs");
|
|
8
8
|
function toSupportedChainId(chainId) {
|
|
9
9
|
const numericChainId = Number(chainId);
|
|
10
|
-
(0, gasOracleConfigs_1.
|
|
10
|
+
(0, gasOracleConfigs_1.assertSupportedChainId)(numericChainId);
|
|
11
11
|
if (!(0, config_1.isSupportedNetId)(numericChainId)) {
|
|
12
12
|
throw new exceptions_1.OracleError('INVALID_CONFIG', `Unsupported chainId ${chainId} for @mixerx/config`);
|
|
13
13
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
3
|
+
exports.assertSupportedChainId = assertSupportedChainId;
|
|
4
|
+
const config_1 = require("@mixerx/config");
|
|
5
5
|
const exceptions_1 = require("../../domain/exceptions");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
throw new exceptions_1.OracleError('INVALID_CONFIG', `Unsupported chainId ${String(chainId)}; @mixerx/oracles is Sepolia-only`);
|
|
6
|
+
function assertSupportedChainId(chainId) {
|
|
7
|
+
if (!(0, config_1.isSupportedNetId)(chainId)) {
|
|
8
|
+
throw new exceptions_1.OracleError('INVALID_CONFIG', `Unsupported chainId ${String(chainId)}; supported networks: ${config_1.SUPPORTED_NET_IDS.join(', ')}`);
|
|
10
9
|
}
|
|
11
10
|
}
|
|
@@ -38,7 +38,7 @@ class MixerXFeeOracle {
|
|
|
38
38
|
currencyDecimals;
|
|
39
39
|
priceBounds;
|
|
40
40
|
constructor(options) {
|
|
41
|
-
(0, gasOracleConfigs_1.
|
|
41
|
+
(0, gasOracleConfigs_1.assertSupportedChainId)(options.chainId);
|
|
42
42
|
if (!Number.isFinite(options.relayerFeePercent) || options.relayerFeePercent < 0 || options.relayerFeePercent > 0.1) {
|
|
43
43
|
throw new exceptions_1.OracleError('INVALID_CONFIG', 'relayerFeePercent must be between 0 and 0.1');
|
|
44
44
|
}
|
|
@@ -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.
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mixerx/oracles",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Fail-closed
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"default": "./dist/index.js"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"packageManager": "yarn@4.12.0",
|
|
14
|
-
"files": [
|
|
15
|
-
"dist/**/*"
|
|
16
|
-
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "yarn clean && tsc",
|
|
19
|
-
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
20
|
-
"typecheck": "tsc --noEmit",
|
|
21
|
-
"lint": "eslint src tests",
|
|
22
|
-
"test": "yarn build && node --test \"tests/**/*.test.cjs\"",
|
|
23
|
-
"validate": "yarn typecheck && yarn lint && yarn test",
|
|
24
|
-
"prepack": "yarn build",
|
|
25
|
-
"prepublish": "yarn validate"
|
|
26
|
-
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"mixerx",
|
|
29
|
-
"ethereum",
|
|
30
|
-
"gas",
|
|
31
|
-
"oracle",
|
|
32
|
-
"fees"
|
|
33
|
-
],
|
|
34
|
-
"author": "MixerX Contributors",
|
|
35
|
-
"license": "GPL-3.0-only",
|
|
36
|
-
"repository": {
|
|
37
|
-
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/zkusd-mixerx/mixerx-oracles.git"
|
|
39
|
-
},
|
|
40
|
-
"bugs": {
|
|
41
|
-
"url": "https://github.com/zkusd-mixerx/mixerx-oracles/issues"
|
|
42
|
-
},
|
|
43
|
-
"homepage": "https://github.com/zkusd-mixerx/mixerx-oracles#readme",
|
|
44
|
-
"publishConfig": {
|
|
45
|
-
"access": "public"
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@mixerx/config": "1.
|
|
49
|
-
"ethers": "6.17.0"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@eslint/js": "9.39.1",
|
|
53
|
-
"@types/node": "24.10.1",
|
|
54
|
-
"eslint": "9.39.1",
|
|
55
|
-
"globals": "16.5.0",
|
|
56
|
-
"typescript": "5.9.3",
|
|
57
|
-
"typescript-eslint": "8.48.1"
|
|
58
|
-
},
|
|
59
|
-
"engines": {
|
|
60
|
-
"node": ">=24.12.0"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@mixerx/oracles",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Fail-closed Ethereum gas, token-price and withdrawal-fee policies for MixerX",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"packageManager": "yarn@4.12.0",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**/*"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "yarn clean && tsc",
|
|
19
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "eslint src tests",
|
|
22
|
+
"test": "yarn build && node --test \"tests/**/*.test.cjs\"",
|
|
23
|
+
"validate": "yarn typecheck && yarn lint && yarn test",
|
|
24
|
+
"prepack": "yarn build",
|
|
25
|
+
"prepublish": "yarn validate"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mixerx",
|
|
29
|
+
"ethereum",
|
|
30
|
+
"gas",
|
|
31
|
+
"oracle",
|
|
32
|
+
"fees"
|
|
33
|
+
],
|
|
34
|
+
"author": "MixerX Contributors",
|
|
35
|
+
"license": "GPL-3.0-only",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/zkusd-mixerx/mixerx-oracles.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/zkusd-mixerx/mixerx-oracles/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/zkusd-mixerx/mixerx-oracles#readme",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@mixerx/config": "1.1.0",
|
|
49
|
+
"ethers": "6.17.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "9.39.1",
|
|
53
|
+
"@types/node": "24.10.1",
|
|
54
|
+
"eslint": "9.39.1",
|
|
55
|
+
"globals": "16.5.0",
|
|
56
|
+
"typescript": "5.9.3",
|
|
57
|
+
"typescript-eslint": "8.48.1"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=24.12.0"
|
|
61
|
+
}
|
|
62
|
+
}
|