@@ -6,14 +6,22 @@ import {
66 finalityCheckpoints ,
77 checkSourceValidators ,
88 checkTargetValidators ,
9+ callReadMethod ,
10+ callWriteMethodWithReceipt ,
11+ logInfo ,
912} from 'utils' ;
1013import { consolidation } from './main.js' ;
11- import { Address , Hex , zeroAddress } from 'viem' ;
14+ import { Address , Hex , hexToBigInt , zeroAddress } from 'viem' ;
1215import { checkPubkeys } from 'utils/pubkeys-checks.js' ;
1316import {
1417 revokeDelegate ,
1518 requestConsolidation ,
1619} from 'features/consolidation.js' ;
20+ import { getDashboardContract , getVaultHubContract } from 'contracts' ;
21+ import { getAccount , getPublicClient , getWalletWithAccount } from 'providers' ;
22+
23+ const CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS =
24+ '0x0000BBdDc7CE488642fb579F8B00f3a590007251' ;
1725
1826const consolidationWrite = consolidation
1927 . command ( 'write' )
@@ -87,3 +95,120 @@ consolidationWrite
8795 . command ( 'eoa-revoke-delegate' )
8896 . description ( 'Revoke delegate for the EOA using EIP-7702' )
8997 . action ( async ( ) => revokeDelegate ( ) ) ;
98+
99+ consolidationWrite
100+ . command ( 'eoa-transactions' )
101+ . description (
102+ 'Make separate consolidation requests for each source pubkey, increase rewards adjustment' ,
103+ )
104+ . argument (
105+ '<source_pubkeys>' ,
106+ '2D array of source validator pubkeys: each inner list will be consolidated into a single target validator' ,
107+ stringTo2dArray ,
108+ )
109+ . argument (
110+ '<target_pubkeys>' ,
111+ 'List of target validator public keys to consolidate into. One target pubkey per group of source pubkeys' ,
112+ stringToHexArray ,
113+ )
114+ . argument ( '<staking_vault>' , 'staking vault address' , stringToAddress )
115+ . action (
116+ async (
117+ sourcePubkeys : Hex [ ] [ ] ,
118+ targetPubkeys : Hex [ ] ,
119+ stakingVault : Address ,
120+ ) => {
121+ const publicClient = getPublicClient ( ) ;
122+ const walletClient = getWalletWithAccount ( ) ;
123+ const account = getAccount ( ) ;
124+
125+ // 0. Input validation
126+ const sourcePubkeysFlat = sourcePubkeys . flat ( ) ;
127+ checkPubkeys ( sourcePubkeysFlat ) ;
128+ checkPubkeys ( targetPubkeys ) ;
129+ if ( sourcePubkeys . length !== targetPubkeys . length ) {
130+ throw new Error (
131+ 'sourcePubkeys and targetPubkeys must have the same length' ,
132+ ) ;
133+ }
134+ if ( stakingVault === zeroAddress ) {
135+ throw new Error ( 'stakingVault must be non-zero address' ) ;
136+ }
137+
138+ // 1. Check source validators
139+ const finalityCheckpointsInfo = await finalityCheckpoints ( ) ;
140+ const finalizedEpoch = Number (
141+ finalityCheckpointsInfo . data . finalized . epoch ,
142+ ) ;
143+ const sourceValidatorsInfo = await fetchValidatorsInfo ( sourcePubkeysFlat ) ;
144+ await checkSourceValidators ( sourceValidatorsInfo , finalizedEpoch ) ;
145+
146+ // 2. Check target validators
147+ const targetValidatorsInfo = await fetchValidatorsInfo ( targetPubkeys ) ;
148+ await checkTargetValidators ( targetValidatorsInfo ) ;
149+
150+ // 3. Onchain checks
151+ const vaultHub = await getVaultHubContract ( ) ;
152+ const vaultConnection = await callReadMethod (
153+ vaultHub ,
154+ 'vaultConnection' ,
155+ [ stakingVault ] ,
156+ ) ;
157+ if (
158+ vaultConnection . vaultIndex === 0n ||
159+ vaultConnection . pendingDisconnect
160+ ) {
161+ throw new Error ( 'Vault is not connected or is pending disconnect' ) ;
162+ }
163+
164+ // 4. Get fee per request
165+ const { data } = await publicClient . call ( {
166+ to : CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS ,
167+ data : '0x' ,
168+ blockTag : 'latest' ,
169+ } ) ;
170+ if ( ! data ) throw new Error ( 'Fee read returned empty data' ) ;
171+ const feePerRequest = hexToBigInt ( data ) ;
172+
173+ // 5. Request consolidation
174+ for ( const [ pubkeyIndex , targetPubkey ] of targetPubkeys . entries ( ) ) {
175+ const sourcePubkeysGroup = sourcePubkeys [ pubkeyIndex ] ?? [ ] ;
176+ for ( const sourcePubkey of sourcePubkeysGroup ) {
177+ if ( sourcePubkey == null || targetPubkey == null ) {
178+ throw new Error ( 'sourcePubkey and targetPubkey must be defined' ) ;
179+ }
180+
181+ const calldata =
182+ `0x${ sourcePubkey . slice ( 2 ) } ${ targetPubkey . slice ( 2 ) } ` as Hex ;
183+ const txHash = await walletClient . sendTransaction ( {
184+ to : CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS ,
185+ data : calldata ,
186+ value : feePerRequest ,
187+ account : account ,
188+ chain : walletClient . chain ,
189+ } ) ;
190+
191+ logInfo ( 'consolidation request tx hash:' , txHash ) ;
192+
193+ const txReceipt = await publicClient . waitForTransactionReceipt ( {
194+ hash : txHash ,
195+ } ) ;
196+ logInfo ( 'consolidation request tx receipt:' , txReceipt ) ;
197+ }
198+ }
199+
200+ // 6. Increase rewards adjustment
201+ const dashboardContract = getDashboardContract ( vaultConnection . owner ) ;
202+ const totalBalance = sourceValidatorsInfo . data . reduce (
203+ ( sum , validator ) => sum + Number ( validator . balance ) ,
204+ 0 ,
205+ ) ;
206+ if ( totalBalance > 0 ) {
207+ await callWriteMethodWithReceipt ( {
208+ contract : dashboardContract ,
209+ methodName : 'increaseRewardsAdjustment' ,
210+ payload : [ BigInt ( totalBalance ) ] ,
211+ } ) ;
212+ }
213+ } ,
214+ ) ;
0 commit comments