Skip to content

Commit 5ba1b85

Browse files
committed
feat: modular rebalancer registry with shared threshold/callback engines and e2e harness
1 parent a4bfa48 commit 5ba1b85

30 files changed

Lines changed: 2576 additions & 2156 deletions

package.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"aws:setup": "npx ts-node scripts/aws-shard-setup.ts --manifest",
2727
"build": "yarn workspaces foreach -Av --parallel --topological-dev run build",
2828
"clean": "yarn workspaces foreach -A --parallel run clean",
29+
"e2e:rebalance": "yarn workspace @mark/poller e2e",
2930
"gcp:setup": "npx ts-node scripts/gcp-shard-setup.ts --manifest",
3031
"lint": "yarn lint:package && yarn workspaces foreach -A --parallel run lint",
3132
"lint:fix": "yarn workspaces foreach -A --parallel run lint:fix",
@@ -42,14 +43,6 @@
4243
"dependencies": {
4344
"@types/node": "20.17.12"
4445
},
45-
"dependenciesMeta": {
46-
"@eth-optimism/contracts-bedrock": {
47-
"built": false
48-
},
49-
"@eth-optimism/core-utils": {
50-
"built": false
51-
}
52-
},
5346
"devDependencies": {
5447
"@commitlint/cli": "19.6.1",
5548
"@commitlint/config-conventional": "19.6.0",
@@ -74,5 +67,13 @@
7467
"tsc-alias": "1.8.10",
7568
"typescript": "5.7.2"
7669
},
70+
"dependenciesMeta": {
71+
"@eth-optimism/contracts-bedrock": {
72+
"built": false
73+
},
74+
"@eth-optimism/core-utils": {
75+
"built": false
76+
}
77+
},
7778
"packageManager": "yarn@3.3.1"
7879
}

packages/adapters/rebalance/src/adapters/linea/linea.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,10 @@ export class LineaNativeBridgeAdapter implements BridgeAdapter {
432432
// The Linea SDK issues wide-range eth_getLogs on both L1 and L2.
433433
// Commercial free-tier providers (Alchemy, DRPC) reject block ranges >10k.
434434
// Try configured providers first, then fall back to public RPCs for both chains.
435-
const l1Candidates = [...(this.chains[ETHEREUM_CHAIN_ID.toString()]?.providers ?? []), ...LINEA_SDK_FALLBACK_L1_RPCS];
435+
const l1Candidates = [
436+
...(this.chains[ETHEREUM_CHAIN_ID.toString()]?.providers ?? []),
437+
...LINEA_SDK_FALLBACK_L1_RPCS,
438+
];
436439
const l2Candidates = [...(this.chains[LINEA_CHAIN_ID.toString()]?.providers ?? []), ...LINEA_SDK_FALLBACK_L2_RPCS];
437440

438441
if (l2Candidates.length === 0) {

packages/adapters/rebalance/src/adapters/pendle/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ export const PENDLE_SUPPORTED_CHAINS = {
2424
export const USDC_PTUSDE_PAIRS: Record<number, { usdc: string; ptUSDe: string }> = {
2525
1: {
2626
usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
27-
ptUSDe: '0xE8483517077afa11A9B07f849cee2552f040d7b2',
27+
ptUSDe: '0x3de0ff76e8b528c092d47b9dac775931cef80f49',
2828
},
2929
};

packages/adapters/rebalance/src/adapters/stargate/stargate.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ export class StargateBridgeAdapter implements BridgeAdapter {
6363
*/
6464
private resolveDstToken(route: RebalanceRoute): string | null {
6565
if (route.destination === 30826) return USDT_TON_STARGATE;
66-
return (
67-
getDestinationAssetAddress(route.asset, route.origin, route.destination, this.chains, this.logger) ?? null
68-
);
66+
return getDestinationAssetAddress(route.asset, route.origin, route.destination, this.chains, this.logger) ?? null;
6967
}
7068

7169
/**

packages/adapters/rebalance/src/adapters/zircuit/zircuit.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,7 @@ export class ZircuitNativeBridgeAdapter implements BridgeAdapter {
396396
data: encodeFunctionData({
397397
abi: zircuitOptimismPortalAbi,
398398
functionName: 'proveWithdrawalTransaction',
399-
args: [
400-
withdrawalTx,
401-
proofResult.l2OutputIndex,
402-
proofResult.outputRootProof,
403-
proofResult.withdrawalProof,
404-
],
399+
args: [withdrawalTx, proofResult.l2OutputIndex, proofResult.outputRootProof, proofResult.withdrawalProof],
405400
}),
406401
value: BigInt(0),
407402
},

packages/adapters/rebalance/test/adapters/pendle/pendle.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ describe('PendleBridgeAdapter', () => {
284284
it('has USDC/ptUSDe pair for mainnet', () => {
285285
expect(USDC_PTUSDE_PAIRS[1]).toBeDefined();
286286
expect(USDC_PTUSDE_PAIRS[1].usdc).toBe('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48');
287-
expect(USDC_PTUSDE_PAIRS[1].ptUSDe).toBe('0xE8483517077afa11A9B07f849cee2552f040d7b2');
287+
expect(USDC_PTUSDE_PAIRS[1].ptUSDe).toBe('0x3de0ff76e8b528c092d47b9dac775931cef80f49');
288288
});
289289

290290
it('has mainnet in supported chains', () => {

packages/agent/src/validation.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { Logger } from '@mark/logger';
2-
import { MarkConfiguration, TokenRebalanceConfig } from '@mark/core';
2+
import {
3+
MarkConfiguration,
4+
TokenRebalanceConfig,
5+
TOKEN_REBALANCER_KEYS,
6+
TokenRebalancerKey,
7+
SolanaRebalanceConfig,
8+
} from '@mark/core';
39

410
/**
511
* Validates a single token rebalance configuration.
612
* Helper function used by validateTokenRebalanceConfig.
713
*/
814
function validateSingleTokenRebalanceConfig(
915
tokenConfig: TokenRebalanceConfig | undefined,
10-
configName: 'tacRebalance' | 'methRebalance' | 'aManUsdeRebalance' | 'aMansyrupUsdtRebalance',
16+
configName: TokenRebalancerKey,
1117
config: MarkConfiguration,
1218
logger: Logger,
1319
): void {
@@ -99,13 +105,56 @@ function validateSingleTokenRebalanceConfig(
99105
});
100106
}
101107

108+
/**
109+
* Validates Solana ptUSDe rebalance configuration.
110+
* Solana uses a different config shape (SolanaRebalanceConfig) than the EVM token rebalancers.
111+
*/
112+
function validateSolanaRebalanceConfig(
113+
solanaConfig: SolanaRebalanceConfig | undefined,
114+
config: MarkConfiguration,
115+
logger: Logger,
116+
): void {
117+
if (!solanaConfig?.enabled) {
118+
logger.debug('solanaPtusdeRebalance disabled, skipping config validation');
119+
return;
120+
}
121+
122+
const errors: string[] = [];
123+
124+
if (!solanaConfig.ptUsdeThreshold) {
125+
errors.push('solanaPtusdeRebalance.ptUsdeThreshold is required when enabled');
126+
}
127+
if (!solanaConfig.ptUsdeTarget) {
128+
errors.push('solanaPtusdeRebalance.ptUsdeTarget is required when enabled');
129+
}
130+
if (!solanaConfig.bridge?.minRebalanceAmount) {
131+
errors.push('solanaPtusdeRebalance.bridge.minRebalanceAmount is required');
132+
}
133+
if (!config.solana?.privateKey) {
134+
errors.push('solana.privateKey (SOLANA_PRIVATE_KEY) is required for Solana rebalancing');
135+
}
136+
137+
if (errors.length > 0) {
138+
const errorMessage = `solanaPtusdeRebalance config validation failed:\n - ${errors.join('\n - ')}`;
139+
logger.error('solanaPtusdeRebalance config validation failed', { errors });
140+
throw new Error(errorMessage);
141+
}
142+
143+
logger.info('solanaPtusdeRebalance config validated successfully', {
144+
ptUsdeThreshold: solanaConfig.ptUsdeThreshold,
145+
ptUsdeTarget: solanaConfig.ptUsdeTarget,
146+
minRebalanceAmount: solanaConfig.bridge.minRebalanceAmount,
147+
maxRebalanceAmount: solanaConfig.bridge.maxRebalanceAmount,
148+
});
149+
}
150+
102151
/**
103152
* Validates token rebalance configuration for production readiness.
104153
* Throws if required fields are missing when token rebalancing is enabled.
105154
*/
106155
export function validateTokenRebalanceConfig(config: MarkConfiguration, logger: Logger): void {
107-
validateSingleTokenRebalanceConfig(config.tacRebalance, 'tacRebalance', config, logger);
108-
validateSingleTokenRebalanceConfig(config.methRebalance, 'methRebalance', config, logger);
109-
validateSingleTokenRebalanceConfig(config.aManUsdeRebalance, 'aManUsdeRebalance', config, logger);
110-
validateSingleTokenRebalanceConfig(config.aMansyrupUsdtRebalance, 'aMansyrupUsdtRebalance', config, logger);
156+
for (const key of TOKEN_REBALANCER_KEYS) {
157+
validateSingleTokenRebalanceConfig(config[key], key, config, logger);
158+
}
159+
validateSolanaRebalanceConfig(config.solanaPtusdeRebalance, config, logger);
111160
}

0 commit comments

Comments
 (0)