@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 CHANGED
@@ -1,59 +1,65 @@
1
- # MixerX Oracles
2
-
3
- Fail-closed Sepolia pricing and gas policy for MixerX. Version 3.0.0 rejects a
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`) 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.
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 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.
31
-
32
- ## Installation
33
-
34
- ```bash
35
- yarn add @mixerx/oracles@3.0.0
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 Sepolia tokens through Multicall3 at one
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.assertSepoliaChainId)(chainId);
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.assertSepoliaChainId)(numericChainId);
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,3 +1,2 @@
1
1
  export type ChainId = number;
2
- export declare const SEPOLIA_CHAIN_ID = 11155111;
3
- export declare function assertSepoliaChainId(chainId: number): void;
2
+ export declare function assertSupportedChainId(chainId: number): void;
@@ -1,11 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SEPOLIA_CHAIN_ID = void 0;
4
- exports.assertSepoliaChainId = assertSepoliaChainId;
3
+ exports.assertSupportedChainId = assertSupportedChainId;
4
+ const config_1 = require("@mixerx/config");
5
5
  const exceptions_1 = require("../../domain/exceptions");
6
- exports.SEPOLIA_CHAIN_ID = 11_155_111;
7
- function assertSepoliaChainId(chainId) {
8
- if (chainId !== exports.SEPOLIA_CHAIN_ID) {
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.assertSepoliaChainId)(options.chainId);
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.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
  }
package/package.json CHANGED
@@ -1,62 +1,62 @@
1
- {
2
- "name": "@mixerx/oracles",
3
- "version": "3.0.0",
4
- "description": "Fail-closed Sepolia 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.0.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
- }
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
+ }