Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2521569
feat: staking manager rough draft
owenwahlgren Sep 12, 2025
f91c7b5
Merge branch 'master' into console/staking-manager-setup
owenwahlgren Sep 25, 2025
686712c
update sidebar to split native and erc20 staking manager
owenwahlgren Sep 25, 2025
bd7e835
Merge branch 'console/staking-manager-setup' of github.com:ava-labs/b…
owenwahlgren Sep 25, 2025
4cf5a4e
use new notify hook, fix bug in native init
owenwahlgren Sep 25, 2025
5931a23
fix history page, make enable native minter not optional
owenwahlgren Sep 25, 2025
8ecc664
Merge branch 'master' into console/staking-manager-setup
owenwahlgren Oct 22, 2025
684996f
update components to align with master
owenwahlgren Oct 22, 2025
cbe8372
fix precompile checker
owenwahlgren Oct 22, 2025
4bbf4c7
L1 native staking ROUGH ROUGH DRAFT (DRAFT)
owenwahlgren Oct 22, 2025
60a429a
cleanup native stake component
owenwahlgren Oct 22, 2025
a55089e
fix initialize check call on NativeStakingManager
owenwahlgren Oct 22, 2025
cc083f8
prefill StakingManager in enable native minter
owenwahlgren Oct 22, 2025
57a16ed
prefill stakingManager on tranfer ownership, remove old step flow for…
owenwahlgren Oct 22, 2025
09c3639
delegate native token
owenwahlgren Oct 29, 2025
62eac8a
owen commits
navillanueva Nov 18, 2025
f5d96fb
only keeping native token staking manager
navillanueva Nov 18, 2025
e12cd61
merging master
navillanueva Nov 18, 2025
6e38489
Remove auto-generated documentation and OpenAPI files
navillanueva Nov 18, 2025
aad432c
fixing git ignore
navillanueva Nov 18, 2025
e4a2c8c
fix build
navillanueva Nov 18, 2025
6328189
Merge remote-tracking branch 'origin/master' into owen/staking-manage…
navillanueva Nov 19, 2025
e8e0c2d
Merge branch 'master' into owen/staking-manager-deployment
navillanueva Nov 19, 2025
ecd6ca8
Merge remote-tracking branch 'origin/master' into owen/staking-manage…
navillanueva Nov 20, 2025
6ec98ff
fixed versions
navillanueva Nov 20, 2025
eea5bf7
fixed commit
navillanueva Nov 20, 2025
5a21b9c
Merge remote-tracking branch 'origin/master' into owen/staking-manage…
navillanueva Nov 20, 2025
d6a5ff6
fixed ilya comments
navillanueva Nov 20, 2025
f2a8a16
Merge branch 'master' into owen/staking-manager-deployment
containerman17 Nov 20, 2025
825701f
Merge branch 'master' into owen/staking-manager-deployment
containerman17 Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/console/history/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ export default function ConsoleHistoryPage() {
type: 'address'
});
}
if (toolboxStore.stakingManagerAddress && toolboxStore.stakingManagerAddress !== '') {
if (toolboxStore.nativeStakingManagerAddress && toolboxStore.nativeStakingManagerAddress !== '') {
items.push({
id: 'tb-staking-mgr',
title: 'Staking Manager',
id: 'tb-native-staking-mgr',
title: 'Native Token Staking Manager',
description: 'Deployed Contract',
address: toolboxStore.stakingManagerAddress,
address: toolboxStore.nativeStakingManagerAddress,
chainId,
type: 'address'
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import StepFlow from "@/components/console/step-flow";
import { steps } from "../steps";

export default function NativeStakingManagerSetupClientPage({ currentStepKey }: { currentStepKey: string }) {
const basePath = "/console/permissionless-l1s/native-staking-manager-setup";
return (
<StepFlow
steps={steps}
basePath={basePath}
currentStepKey={currentStepKey}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import NativeStakingManagerSetupClientPage from "./client-page";

export default async function Page({ params }: { params: Promise<{ step: string }> }) {
const { step } = await params;
return (
<NativeStakingManagerSetupClientPage currentStepKey={step} />
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { redirect } from "next/navigation";

export default function Page() {
redirect("/console/permissionless-l1s/native-staking-manager-setup/deploy-native-token-staking-manager");
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type StepDefinition } from "@/components/console/step-flow";
import ReadContract from "@/components/toolbox/console/permissioned-l1s/validator-manager-setup/ReadContract";
import DeployNativeTokenStakingManager from "@/components/toolbox/console/permissionless-l1s/setup/native/DeployNativeStakingManager";
import InitializeNativeTokenStakingManager from "@/components/toolbox/console/permissionless-l1s/setup/native/InitializeNativeStakingManager";
import DeployExampleRewardCalculator from "@/components/toolbox/console/permissionless-l1s/setup/DeployExampleRewardCalculator";
import TransferOwnershipToStakingManager from "@/components/toolbox/console/permissionless-l1s/setup/TransferOwnershipToStakingManager";
import EnableStakingManagerMinting from "@/components/toolbox/console/permissionless-l1s/setup/native/EnableStakingManagerMinting";

export const steps: StepDefinition[] = [
{
type: "single",
key: "deploy-native-token-staking-manager",
title: "Deploy Staking Manager",
component: DeployNativeTokenStakingManager,
},
{ type: "single", key: "deploy-example-reward-calculator", title: "Deploy Example Reward Calculator", component: DeployExampleRewardCalculator },
{
type: "single",
key: "initialize-native-staking-manager",
title: "Initialize Staking Manager",
component: InitializeNativeTokenStakingManager,
},
{ type: "single", key: "enable-staking-minting", title: "Enable StakingManager in Native Minter", component: EnableStakingManagerMinting },
{ type: "single", key: "transfer-ownership", title: "Transfer Ownership to Staking Manager", component: TransferOwnershipToStakingManager },
{ type: "single", key: "read-contract", title: "Read Contract", component: ReadContract },
];
11 changes: 2 additions & 9 deletions components/console/console-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,9 @@ const data = {
icon: Globe,
items: [
{
title: "Migrate from Permissioned L1",
url: "/console/permissionless-l1s/deploy-reward-manager",
title: "Native Staking Manager Setup",
url: "/console/permissionless-l1s/native-staking-manager-setup",
icon: GitMerge,
comingSoon: true,
},
{
title: "Stake & Unstake",
url: "/console/permissionless-l1s/manage-validators",
icon: Hexagon,
comingSoon: true,
},
],
},
Expand Down
7 changes: 6 additions & 1 deletion components/toolbox/components/AllowListComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ function SetEnabledComponent({
precompileType = "precompiled contract",
abi = allowListAbi.abi,
onSuccess,
defaultAddress,
}: {
precompileAddress: string;
precompileType?: string;
abi?: any;
onSuccess?: () => void;
defaultAddress?: string;
}) {
const { publicClient, walletEVMAddress, walletChainId } =
useWalletStore();
const { coreWalletClient } = useConnectedWallet();
const viemChain = useViemChainStore();
const [isProcessing, setIsProcessing] = useState(false);
const [enabledAddress, setEnabledAddress] = useState<string>("");
const [enabledAddress, setEnabledAddress] = useState<string>(defaultAddress || "");
const [txHash, setTxHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);

Expand Down Expand Up @@ -523,11 +525,13 @@ export function AllowlistComponent({
precompileType = "precompiled contract",
abi = allowListAbi.abi,
onSuccess,
defaultEnabledAddress,
}: {
precompileAddress: string;
precompileType?: string;
abi?: any;
onSuccess?: () => void;
defaultEnabledAddress?: string;
}) {
return (
<div className="space-y-6">
Expand All @@ -538,6 +542,7 @@ export function AllowlistComponent({
precompileType={precompileType}
abi={abi}
onSuccess={onSuccess}
defaultAddress={defaultEnabledAddress}
/>
<SetManagerComponent
precompileAddress={precompileAddress}
Expand Down
2 changes: 1 addition & 1 deletion components/toolbox/components/CheckPrecompile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ export const CheckPrecompile = ({
}

return <>{children}</>;
};
};
3 changes: 3 additions & 0 deletions components/toolbox/components/ValidatorListInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface ValidatorListInputProps {
maxValidators?: number;
selectedSubnetId?: string | null;
isTestnet?: boolean;
hideConsensusWeight?: boolean;
}

export function ValidatorListInput({
Expand All @@ -42,6 +43,7 @@ export function ValidatorListInput({
maxValidators,
selectedSubnetId = null,
isTestnet = false,
hideConsensusWeight = false,
}: ValidatorListInputProps) {

const [error, setError] = useState<string | null>(null)
Expand Down Expand Up @@ -85,6 +87,7 @@ export function ValidatorListInput({
onChange={onChange}
l1TotalInitializedWeight={l1TotalInitializedWeight}
userPChainBalanceNavax={userPChainBalanceNavax}
hideConsensusWeight={hideConsensusWeight}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Props {
onUpdate: (index: number, updated: Partial<ConvertToL1Validator>) => void
l1TotalInitializedWeight?: bigint | null
userPChainBalanceNavax?: bigint | null
hideConsensusWeight?: boolean
}

export function ValidatorItem({
Expand All @@ -25,6 +26,7 @@ export function ValidatorItem({
onUpdate,
l1TotalInitializedWeight = null,
userPChainBalanceNavax = null,
hideConsensusWeight = false,
}: Props) {

let insufficientBalanceError: string | null = null
Expand Down Expand Up @@ -89,29 +91,31 @@ export function ValidatorItem({
/>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="space-y-2">
<label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
Consensus Weight
</label>
<input
type="number"
value={validator.validatorWeight.toString()}
onChange={(e) => onUpdate(index, { validatorWeight: BigInt(e.target.value || 0) })}
className={cn(
"w-full rounded p-2",
"bg-zinc-50 dark:bg-zinc-900",
"border border-zinc-200 dark:border-zinc-700",
"text-zinc-900 dark:text-zinc-100",
"shadow-sm focus:ring focus:ring-primary/30 focus:ring-opacity-50",
<div className={cn("grid gap-3", hideConsensusWeight ? "grid-cols-1" : "grid-cols-1 md:grid-cols-2")}>
{!hideConsensusWeight && (
<div className="space-y-2">
<label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
Consensus Weight
</label>
<input
type="number"
value={validator.validatorWeight.toString()}
onChange={(e) => onUpdate(index, { validatorWeight: BigInt(e.target.value || 0) })}
className={cn(
"w-full rounded p-2",
"bg-zinc-50 dark:bg-zinc-900",
"border border-zinc-200 dark:border-zinc-700",
"text-zinc-900 dark:text-zinc-100",
"shadow-sm focus:ring focus:ring-primary/30 focus:ring-opacity-50",
)}
/>
{hasWeightError && (
<p className="text-xs mt-1 text-red-500 dark:text-red-400">
Warning: This validator's weight is 20% or more of the current L1 total stake ({ Number(validator.validatorWeight * 10000n / l1TotalInitializedWeight / 100n).toFixed(2) }%). Recommended to be less than 20%.
</p>
)}
/>
{hasWeightError && (
<p className="text-xs mt-1 text-red-500 dark:text-red-400">
Warning: This validator's weight is 20% or more of the current L1 total stake ({ Number(validator.validatorWeight * 10000n / l1TotalInitializedWeight / 100n).toFixed(2) }%). Recommended to be less than 20%.
</p>
)}
</div>
</div>
)}
<div className="space-y-2">
<label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
Validator Balance (P-Chain AVAX)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ interface Props {
onChange: (validators: ConvertToL1Validator[]) => void
l1TotalInitializedWeight?: bigint | null
userPChainBalanceNavax?: bigint | null
hideConsensusWeight?: boolean
}

export function ValidatorsList({ validators, onChange, l1TotalInitializedWeight = null, userPChainBalanceNavax = null }: Props) {
export function ValidatorsList({ validators, onChange, l1TotalInitializedWeight = null, userPChainBalanceNavax = null, hideConsensusWeight = false }: Props) {
const [expandedIndex, setExpandedIndex] = useState<number | null>(null)

const toggleExpand = (index: number) => {
Expand Down Expand Up @@ -45,6 +46,7 @@ export function ValidatorsList({ validators, onChange, l1TotalInitializedWeight
onUpdate={updateValidator}
l1TotalInitializedWeight={l1TotalInitializedWeight}
userPChainBalanceNavax={userPChainBalanceNavax}
hideConsensusWeight={hideConsensusWeight}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import {
ShieldCheck,
ShieldUser,
SquareTerminal,
Hexagon,
SlidersVertical,
SquareMinus,
SquarePlus,
BookKey,
Hexagon,
} from "lucide-react";

import {
Expand Down Expand Up @@ -178,16 +178,9 @@ const data = {
icon: Globe,
items: [
{
title: "Migrate from Permissioned L1",
url: "/console/permissionless-l1s/deploy-reward-manager",
title: "Native Staking Manager Setup",
url: "/console/permissionless-l1s/native-staking-manager-setup",
icon: GitMerge,
comingSoon: true,
},
{
title: "Stake & Unstake",
url: "/console/permissionless-l1s/manage-validators",
icon: Hexagon,
comingSoon: true,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { packWarpIntoAccessList } from '@/components/toolbox/console/permissione
import { hexToBytes, bytesToHex } from 'viem';
import validatorManagerAbi from '@/contracts/icm-contracts/compiled/ValidatorManager.json';
import poaManagerAbi from '@/contracts/icm-contracts/compiled/PoAManager.json';
import nativeTokenStakingManagerAbi from '@/contracts/icm-contracts/compiled/NativeTokenStakingManager.json';
import { packL1ValidatorRegistration } from '@/components/toolbox/coreViem/utils/convertWarp';
import { getValidationIdHex } from '@/components/toolbox/coreViem/hooks/getValidationID';
import { useAvalancheSDKChainkit } from '@/components/toolbox/stores/useAvalancheSDKChainkit';
Expand Down Expand Up @@ -61,8 +62,18 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps

// Determine target contract and ABI based on ownerType
const useMultisig = ownerType === 'PoAManager';
const targetContractAddress = useMultisig ? contractOwner : validatorManagerAddress;
const targetAbi = useMultisig ? poaManagerAbi.abi : validatorManagerAbi.abi;
const useStakingManager = ownerType === 'StakingManager';

// For both PoAManager and StakingManager, we use the contractOwner address
// For direct ValidatorManager (EOA owned), we use validatorManagerAddress
const targetContractAddress = (useMultisig || useStakingManager) ? contractOwner : validatorManagerAddress;

// Select the appropriate ABI based on owner type
const targetAbi = useMultisig
? poaManagerAbi.abi
: useStakingManager
? nativeTokenStakingManagerAbi.abi
: validatorManagerAbi.abi;

// Initialize state with prop value when it becomes available
useEffect(() => {
Expand Down Expand Up @@ -90,7 +101,7 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps
onError("Validator Manager address is not set. Check L1 Subnet selection.");
return;
}
if (ownershipState === 'differentEOA' && !useMultisig) {
if (ownershipState === 'differentEOA' && !useMultisig && !useStakingManager) {
setErrorState("You are not the contract owner. Please contact the contract owner.");
onError("You are not the contract owner. Please contact the contract owner.");
return;
Expand All @@ -100,6 +111,11 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps
onError("PoAManager address could not be fetched. Please ensure the ValidatorManager is owned by a PoAManager.");
return;
}
if (useStakingManager && !contractOwner?.trim()) {
setErrorState("StakingManager address could not be fetched. Please ensure the ValidatorManager is owned by a StakingManager.");
onError("StakingManager address could not be fetched. Please ensure the ValidatorManager is owned by a StakingManager.");
return;
}
if (!coreWalletClient || !publicClient || !viemChain || !coreWalletClient.account) {
setErrorState("Wallet or chain configuration is not properly initialized.");
onError("Wallet or chain configuration is not properly initialized.");
Expand All @@ -122,9 +138,31 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps
});

// Step 2: Get validation ID from contract using nodeID
// For StakingManager, we need to query the underlying ValidatorManager
let validationIdQueryAddress = validatorManagerAddress;

if (useStakingManager && contractOwner) {
// Get the underlying ValidatorManager address from StakingManager settings
try {
const settings = await publicClient.readContract({
address: contractOwner as `0x${string}`,
abi: nativeTokenStakingManagerAbi.abi,
functionName: 'getStakingManagerSettings',
}) as any;

validationIdQueryAddress = settings.manager; // This is the actual ValidatorManager
console.log('[StakingManager] Querying ValidatorManager for validation ID:', validationIdQueryAddress);
} catch (err) {
console.warn("Failed to get ValidatorManager from StakingManager settings, using default:", err);
}
}

console.log('[CompleteValidatorRegistration] Querying validation ID from:', validationIdQueryAddress);
console.log('[CompleteValidatorRegistration] NodeID:', registrationMessageData.nodeID);

const validationId = await getValidationIdHex(
publicClient,
validatorManagerAddress as `0x${string}`,
validationIdQueryAddress as `0x${string}`,
registrationMessageData.nodeID
);

Expand All @@ -149,6 +187,7 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps

// Step 4: Get justification for the validation
// For validator registration, we need to find the original registration message
// Note: Justification is always fetched from logs, which would be on the chain regardless of contract
const justification = await GetRegistrationJustification(
validationId, // Use the actual validation ID instead of converting nodeID
subnetIdL1,
Expand Down Expand Up @@ -254,7 +293,7 @@ const CompleteValidatorRegistration: React.FC<CompleteValidatorRegistrationProps

<Button
onClick={handleCompleteRegisterValidator}
disabled={isProcessing || !pChainTxIdState.trim() || !!successMessage || (ownershipState === 'differentEOA' && !useMultisig) || isLoadingOwnership}
disabled={isProcessing || !pChainTxIdState.trim() || !!successMessage || (ownershipState === 'differentEOA' && !useMultisig && !useStakingManager) || isLoadingOwnership}
>
{isLoadingOwnership ? 'Checking ownership...' : (isProcessing ? 'Processing...' : 'Sign & Complete Validator Registration')}
</Button>
Expand Down
Loading