1+ import React , { useState , useEffect } from 'react' ;
2+ import { useWalletStore } from '@/components/toolbox/stores/walletStore' ;
3+ import { useViemChainStore } from '@/components/toolbox/stores/toolboxStore' ;
4+ import { Button } from '@/components/toolbox/components/Button' ;
5+ import { Input } from '@/components/toolbox/components/Input' ;
6+ import { Success } from '@/components/toolbox/components/Success' ;
7+ import { Alert } from '@/components/toolbox/components/Alert' ;
8+ import { bytesToHex , hexToBytes } from 'viem' ;
9+ import nativeTokenStakingManagerAbi from '@/contracts/icm-contracts/compiled/NativeTokenStakingManager.json' ;
10+ import { GetRegistrationJustification } from '@/components/toolbox/console/permissioned-l1s/ValidatorManager/justification' ;
11+ import { packL1ValidatorWeightMessage } from '@/components/toolbox/coreViem/utils/convertWarp' ;
12+ import { packWarpIntoAccessList } from '@/components/toolbox/console/permissioned-l1s/ValidatorManager/packWarp' ;
13+ import { useAvalancheSDKChainkit } from '@/components/toolbox/stores/useAvalancheSDKChainkit' ;
14+ import useConsoleNotifications from '@/hooks/useConsoleNotifications' ;
15+
16+ interface CompleteDelegatorRegistrationProps {
17+ subnetIdL1 : string ;
18+ delegationID : string ;
19+ pChainTxId : string ;
20+ stakingManagerAddress : string ;
21+ signingSubnetId : string ;
22+ onSuccess : ( message : string ) => void ;
23+ onError : ( message : string ) => void ;
24+ }
25+
26+ const CompleteDelegatorRegistration : React . FC < CompleteDelegatorRegistrationProps > = ( {
27+ subnetIdL1,
28+ delegationID,
29+ pChainTxId,
30+ stakingManagerAddress,
31+ signingSubnetId,
32+ onSuccess,
33+ onError,
34+ } ) => {
35+ const { coreWalletClient, publicClient, avalancheNetworkID, walletEVMAddress } = useWalletStore ( ) ;
36+ const { aggregateSignature } = useAvalancheSDKChainkit ( ) ;
37+ const { notify } = useConsoleNotifications ( ) ;
38+ const viemChain = useViemChainStore ( ) ;
39+
40+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
41+ const [ error , setErrorState ] = useState < string | null > ( null ) ;
42+ const [ successMessage , setSuccessMessage ] = useState < string | null > ( null ) ;
43+ const [ transactionHash , setTransactionHash ] = useState < string | null > ( null ) ;
44+
45+ const handleCompleteDelegation = async ( ) => {
46+ setErrorState ( null ) ;
47+ setSuccessMessage ( null ) ;
48+
49+ if ( ! pChainTxId . trim ( ) ) {
50+ setErrorState ( "P-Chain transaction ID is required." ) ;
51+ onError ( "P-Chain transaction ID is required." ) ;
52+ return ;
53+ }
54+
55+ if ( ! delegationID || delegationID === '0x0000000000000000000000000000000000000000000000000000000000000000' ) {
56+ setErrorState ( "Valid delegation ID is required." ) ;
57+ onError ( "Valid delegation ID is required." ) ;
58+ return ;
59+ }
60+
61+ if ( ! subnetIdL1 ) {
62+ setErrorState ( "L1 Subnet ID is required. Please select a subnet first." ) ;
63+ onError ( "L1 Subnet ID is required. Please select a subnet first." ) ;
64+ return ;
65+ }
66+
67+ if ( ! stakingManagerAddress ) {
68+ setErrorState ( "Staking Manager address is not set. Check L1 Subnet selection." ) ;
69+ onError ( "Staking Manager address is not set. Check L1 Subnet selection." ) ;
70+ return ;
71+ }
72+
73+ if ( ! coreWalletClient || ! publicClient || ! viemChain ) {
74+ setErrorState ( "Wallet or chain configuration is not properly initialized." ) ;
75+ onError ( "Wallet or chain configuration is not properly initialized." ) ;
76+ return ;
77+ }
78+
79+ setIsProcessing ( true ) ;
80+ try {
81+ // Step 1: Extract L1ValidatorWeightMessage from P-Chain transaction
82+ const weightMessageData = await coreWalletClient . extractL1ValidatorWeightMessage ( {
83+ txId : pChainTxId
84+ } ) ;
85+
86+ // Step 2: Get justification for the validation (using the extracted validation ID)
87+ const justification = await GetRegistrationJustification (
88+ weightMessageData . validationID ,
89+ subnetIdL1 ,
90+ publicClient
91+ ) ;
92+
93+ if ( ! justification ) {
94+ throw new Error ( "No justification logs found for this validation ID" ) ;
95+ }
96+
97+ // Step 3: Create P-Chain warp signature using the extracted weight message data
98+ const warpValidationID = hexToBytes ( weightMessageData . validationID as `0x${string } `) ;
99+ const warpNonce = weightMessageData . nonce ;
100+ const warpWeight = weightMessageData . weight ;
101+
102+ const weightMessage = packL1ValidatorWeightMessage (
103+ {
104+ validationID : warpValidationID ,
105+ nonce : warpNonce ,
106+ weight : warpWeight ,
107+ } ,
108+ avalancheNetworkID ,
109+ "11111111111111111111111111111111LpoYY" // always use P-Chain ID
110+ ) ;
111+
112+ const aggregateSignaturePromise = aggregateSignature ( {
113+ message : bytesToHex ( weightMessage ) ,
114+ justification : bytesToHex ( justification ) ,
115+ signingSubnetId : signingSubnetId || subnetIdL1 ,
116+ quorumPercentage : 67 ,
117+ } ) ;
118+ notify ( {
119+ type : 'local' ,
120+ name : 'Aggregate Signatures'
121+ } , aggregateSignaturePromise ) ;
122+ const signature = await aggregateSignaturePromise ;
123+
124+ // Step 4: Complete the delegator registration on EVM
125+ const signedPChainWarpMsgBytes = hexToBytes ( `0x${ signature . signedMessage } ` ) ;
126+ const accessList = packWarpIntoAccessList ( signedPChainWarpMsgBytes ) ;
127+
128+ const writePromise = coreWalletClient . writeContract ( {
129+ address : stakingManagerAddress as `0x${string } `,
130+ abi : nativeTokenStakingManagerAbi . abi ,
131+ functionName : "completeDelegatorRegistration" ,
132+ args : [ delegationID as `0x${string } `, 0 ] , // delegationID and messageIndex (0)
133+ accessList,
134+ account : walletEVMAddress as `0x${string } `,
135+ chain : viemChain ,
136+ } ) ;
137+
138+ notify ( {
139+ type : 'call' ,
140+ name : 'Complete Delegator Registration'
141+ } , writePromise , viemChain ?? undefined ) ;
142+
143+ const hash = await writePromise ;
144+ const finalReceipt = await publicClient . waitForTransactionReceipt ( { hash } ) ;
145+ if ( finalReceipt . status !== 'success' ) {
146+ throw new Error ( `Transaction failed with status: ${ finalReceipt . status } ` ) ;
147+ }
148+
149+ setTransactionHash ( hash ) ;
150+ const successMsg = `Delegator registration completed successfully.` ;
151+ setSuccessMessage ( successMsg ) ;
152+ onSuccess ( successMsg ) ;
153+ } catch ( err : any ) {
154+ const message = err instanceof Error ? err . message : String ( err ) ;
155+ setErrorState ( `Failed to complete delegator registration: ${ message } ` ) ;
156+ onError ( `Failed to complete delegator registration: ${ message } ` ) ;
157+ } finally {
158+ setIsProcessing ( false ) ;
159+ }
160+ } ;
161+
162+ // Don't render if no subnet is selected
163+ if ( ! subnetIdL1 ) {
164+ return (
165+ < div className = "text-sm text-zinc-500 dark:text-zinc-400" >
166+ Please select an L1 subnet first.
167+ </ div >
168+ ) ;
169+ }
170+
171+ return (
172+ < div className = "space-y-4" >
173+ { error && (
174+ < Alert variant = "error" > { error } </ Alert >
175+ ) }
176+
177+ < div className = "text-sm text-zinc-600 dark:text-zinc-400" >
178+ < p > < strong > Delegation ID:</ strong > { delegationID } </ p >
179+ < p > < strong > P-Chain Tx ID:</ strong > { pChainTxId } </ p >
180+ </ div >
181+
182+ < Button
183+ onClick = { handleCompleteDelegation }
184+ disabled = { isProcessing || ! ! successMessage || ! pChainTxId || ! delegationID }
185+ >
186+ { isProcessing ? 'Processing...' : 'Sign & Complete Delegator Registration' }
187+ </ Button >
188+
189+ { transactionHash && (
190+ < Success
191+ label = "Transaction Hash"
192+ value = { transactionHash }
193+ />
194+ ) }
195+ </ div >
196+ ) ;
197+ } ;
198+
199+ export default CompleteDelegatorRegistration ;
0 commit comments