Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,12 +437,8 @@ const isValidator = await client.isValidator("0x...");
// Get validator info
const validatorInfo = await client.getValidatorInfo("0x...");

// Join as validator (requires an owner account with funds and the operator key)
const registration = await createOperatorRegistration({
privateKey: operatorPrivateKey,
...(await client.getValidatorRegistrationContext()),
});
const result = await client.validatorJoin({ amount: "42000gen", registration });
// Join as validator (requires account with funds)
const result = await client.validatorJoin({ amount: "42000gen" });

// Join as delegator
const delegateResult = await client.delegatorJoin({
Expand Down
8 changes: 2 additions & 6 deletions docs/api-references/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,8 @@ const isValidator = await client.isValidator("0x...");
// Get validator info
const validatorInfo = await client.getValidatorInfo("0x...");

// Join as validator (requires an owner account with funds and the operator key)
const registration = await createOperatorRegistration({
privateKey: operatorPrivateKey,
...(await client.getValidatorRegistrationContext()),
});
const result = await client.validatorJoin({ amount: "42000gen", registration });
// Join as validator (requires account with funds)
const result = await client.validatorJoin({ amount: "42000gen" });

// Join as delegator
const delegateResult = await client.delegatorJoin({
Expand Down
2 changes: 1 addition & 1 deletion docs/api-references/staking.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Joins as a validator with the specified stake amount.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| amount | `bigint \| string` | yes | |
| registration | `OperatorRegistrationProof` | yes | |
| operator | `Address` | no | |

**Returns:** `ValidatorJoinResult`

Expand Down
13 changes: 5 additions & 8 deletions docs/api-references/types.Interface.ValidatorJoinOptions.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
# Interface: ValidatorJoinOptions

Defined in: `src/types/staking.ts`
Defined in: [types/staking.ts:152](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L152)

## Properties

### amount

> **amount**: `string` \| `bigint`

Defined in: `src/types/staking.ts`
Defined in: [types/staking.ts:153](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L153)

***

### registration
### operator?

> **registration**: `OperatorRegistrationProof`
> `optional` **operator?**: `` `0x${string}` ``

Proof-of-possession package bound to this chain, the validator wallet factory,
and the joining owner address.

Defined in: `src/types/staking.ts`
Defined in: [types/staking.ts:154](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L154)
12 changes: 8 additions & 4 deletions src/abi/staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,10 +1249,14 @@ export const STAKING_ABI = [
name: "validatorJoin",
type: "function",
stateMutability: "payable",
inputs: [
{name: "_operatorPubKey", type: "uint256[2]"},
{name: "_possessionProof", type: "bytes"},
],
inputs: [{name: "_operator", type: "address"}],
outputs: [{name: "", type: "address"}],
},
{
name: "validatorJoin",
type: "function",
stateMutability: "payable",
inputs: [],
outputs: [{name: "", type: "address"}],
},
{
Expand Down
12 changes: 1 addition & 11 deletions src/abi/vesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,7 @@ export const VESTING_ABI = [
{name: "vestingDelegatorJoin", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}, {name: "amount", type: "uint256"}], outputs: []},
{name: "vestingDelegatorExit", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}, {name: "shares", type: "uint256"}], outputs: []},
{name: "vestingDelegatorClaim", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}], outputs: []},
{
name: "vestingValidatorJoin",
type: "function",
stateMutability: "nonpayable",
inputs: [
{name: "operatorPubKey", type: "uint256[2]"},
{name: "possessionProof", type: "bytes"},
{name: "amount", type: "uint256"},
],
outputs: [],
},
{name: "vestingValidatorJoin", type: "function", stateMutability: "nonpayable", inputs: [{name: "operator", type: "address"}, {name: "amount", type: "uint256"}], outputs: []},
{name: "vestingValidatorDeposit", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}, {name: "amount", type: "uint256"}], outputs: []},
{name: "vestingValidatorExit", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}, {name: "shares", type: "uint256"}], outputs: []},
{name: "vestingValidatorClaim", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}], outputs: []},
Expand Down
15 changes: 1 addition & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,5 @@ export * as abi from "./abi";
export * from "./transactions/fees";
export {isSuccessful} from "./transactions/actions";
export {parseStakingAmount, formatStakingAmount} from "./staking";
export {
OPERATOR_REGISTRATION_DOMAIN,
createOperatorRegistration,
operatorAddressFromPublicKey,
operatorPossessionMessage,
verifyOperatorRegistration,
vestingActions,
} from "./vesting";
export type {
CreateOperatorRegistrationOptions,
OperatorPublicKey,
OperatorRegistrationContext,
OperatorRegistrationProof,
} from "./vesting";
export {vestingActions} from "./vesting";
export {buildGenVmPositionalArgs} from "./contracts/schema";
68 changes: 13 additions & 55 deletions src/staking/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError, zeroAddress} from "viem";
import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError} from "viem";
import {GenLayerClient, GenLayerChain, Address} from "@/types";
import {STAKING_ABI, VALIDATOR_WALLET_ABI} from "@/abi/staking";
import {ADDRESS_MANAGER_ABI, CONSENSUS_ADDRESS_MANAGER_ABI} from "@/abi/vesting";
import {parseStakingAmount, formatStakingAmount} from "./utils";
import {operatorAddressFromPublicKey, verifyOperatorRegistration} from "@/vesting/operatorRegistration";
import {
ValidatorInfo,
ValidatorIdentity,
Expand Down Expand Up @@ -34,7 +32,6 @@ type WalletClientWithAccount = Client<Transport, Chain, Account>;

const FALLBACK_GAS = 1000000n;
const GAS_BUFFER_MULTIPLIER = 2n;
const VALIDATOR_WALLET_FACTORY_KEY = "ValidatorWalletFactory";

// Combined ABI for error decoding (both staking and validator wallet errors)
const COMBINED_ERROR_ABI = [...STAKING_ABI, ...VALIDATOR_WALLET_ABI];
Expand Down Expand Up @@ -239,59 +236,22 @@ export const stakingActions = (
});
};

const getValidatorRegistrationContext = async () => {
if (!client.account) {
throw new Error("Account is required to resolve validator registration context.");
}

const consensusMain = client.chain.consensusMainContract;
if (!consensusMain?.address || consensusMain.address === zeroAddress) {
throw new Error("Cannot resolve ValidatorWalletFactory without a consensus main contract.");
}

const [addressManager, chainId] = await Promise.all([
publicClient.readContract({
address: consensusMain.address as ViemAddress,
abi: CONSENSUS_ADDRESS_MANAGER_ABI,
functionName: "getAddressManager",
}) as Promise<Address>,
publicClient.getChainId(),
]);
const registrar = await publicClient.readContract({
address: addressManager as ViemAddress,
abi: ADDRESS_MANAGER_ABI,
functionName: "getAddress",
args: [VALIDATOR_WALLET_FACTORY_KEY],
}) as Address;

if (!registrar || registrar === zeroAddress) {
throw new Error(
`ValidatorWalletFactory is not registered in AddressManager under key ${VALIDATOR_WALLET_FACTORY_KEY}.`,
);
}

return {
registrar,
owner: client.account.address as Address,
chainId: BigInt(chainId),
};
};

return {
/** Joins as a validator with the specified stake amount. */
validatorJoin: async (options: ValidatorJoinOptions): Promise<ValidatorJoinResult> => {
const amount = parseStakingAmount(options.amount);
const stakingAddress = getStakingAddress();
const context = await getValidatorRegistrationContext();
if (!await verifyOperatorRegistration(options.registration, context)) {
throw new Error("Operator registration proof does not match the owner, registrar, chain, or public key.");
}
const operator = operatorAddressFromPublicKey(options.registration.operatorPubKey);
const data = encodeFunctionData({
abi: STAKING_ABI,
functionName: "validatorJoin",
args: [options.registration.operatorPubKey, options.registration.possessionProof],
});

const data = options.operator
? encodeFunctionData({
abi: STAKING_ABI,
functionName: "validatorJoin",
args: [options.operator as ViemAddress],
})
: encodeFunctionData({
abi: STAKING_ABI,
functionName: "validatorJoin",
});

const result = await executeWrite({to: stakingAddress, data, value: amount});
const receipt = await publicClient.getTransactionReceipt({hash: result.transactionHash});
Expand Down Expand Up @@ -323,13 +283,11 @@ export const stakingActions = (
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed,
validatorWallet: validatorWallet!,
operator,
operator: options.operator || (client.account!.address as Address),
amount: formatStakingAmount(amount),
amountRaw: amount,
};
},
/** Resolves the registrar, owner, and chain binding required to create an operator proof. */
getValidatorRegistrationContext,

/**
* Adds additional self-stake to an active validator position. The
Expand Down
4 changes: 1 addition & 3 deletions src/types/staking.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Address} from "./accounts";
import {GetContractReturnType, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress} from "viem";
import {STAKING_ABI} from "@/abi/staking";
import type {OperatorRegistrationContext, OperatorRegistrationProof} from "./vesting";

type WalletClientWithAccount = Client<Transport, Chain, Account>;

Expand Down Expand Up @@ -156,7 +155,7 @@ export interface DelegatorJoinResult extends StakingTransactionResult {

export interface ValidatorJoinOptions {
amount: bigint | string;
registration: OperatorRegistrationProof;
operator?: Address;
}

export interface ValidatorDepositOptions {
Expand Down Expand Up @@ -212,7 +211,6 @@ export interface DelegatorClaimOptions {

export interface StakingActions {
validatorJoin: (options: ValidatorJoinOptions) => Promise<ValidatorJoinResult>;
getValidatorRegistrationContext: () => Promise<OperatorRegistrationContext>;
validatorDeposit: (options: ValidatorDepositOptions) => Promise<StakingTransactionResult>;
validatorExit: (options: ValidatorExitOptions) => Promise<StakingTransactionResult>;
validatorClaim: (options?: ValidatorClaimOptions) => Promise<StakingTransactionResult & {claimedAmount: bigint}>;
Expand Down
23 changes: 2 additions & 21 deletions src/types/vesting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Account, Address as ViemAddress, Chain, Client, GetContractReturnType, Hex, PublicClient, Transport} from "viem";
import {Account, Address as ViemAddress, Chain, Client, GetContractReturnType, PublicClient, Transport} from "viem";
import {Address} from "./accounts";
import {VESTING_ABI, VESTING_FACTORY_ABI} from "@/abi/vesting";

Expand All @@ -14,24 +14,6 @@ export type VestingFactoryContract = GetContractReturnType<typeof VESTING_FACTOR

export type VestingCategory = 0 | 1 | 2 | 3 | 4 | 5 | 6;

export type OperatorPublicKey = readonly [bigint, bigint];

export interface OperatorRegistrationContext {
registrar: Address;
owner: Address;
chainId: bigint;
}

export interface OperatorRegistrationProof {
operator: Address;
operatorPubKey: OperatorPublicKey;
possessionProof: Hex;
}

export interface CreateOperatorRegistrationOptions extends OperatorRegistrationContext {
privateKey: Hex;
}

export interface VestingTransactionResult {
transactionHash: `0x${string}`;
blockNumber: bigint;
Expand Down Expand Up @@ -65,7 +47,7 @@ export interface VestingDelegatorClaimOptions {

export interface VestingValidatorJoinOptions {
vesting: Address;
registration: OperatorRegistrationProof;
operator: Address;
amount: bigint | string;
}

Expand Down Expand Up @@ -220,7 +202,6 @@ export interface VestingActions {
vestingRevoker: (vesting: Address) => Promise<Address>;
vestingFactory: (vesting: Address) => Promise<Address>;
vestingAddressManager: (vesting: Address) => Promise<Address>;
getVestingValidatorRegistrationContext: (vesting: Address) => Promise<OperatorRegistrationContext>;
vestingTotalAmount: (vesting: Address) => Promise<bigint>;
vestingStartDate: (vesting: Address) => Promise<bigint>;
vestingCliffDuration: (vesting: Address) => Promise<bigint>;
Expand Down
40 changes: 2 additions & 38 deletions src/vesting/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,12 @@ import {
VestingWithdrawResult,
} from "@/types/vesting";
import {formatStakingAmount, parseStakingAmount} from "@/staking/utils";
import {
operatorAddressFromPublicKey,
verifyOperatorRegistration,
} from "./operatorRegistration";

type WalletClientWithAccount = Client<Transport, Chain, Account>;

const FALLBACK_GAS = 1000000n;
const GAS_BUFFER_MULTIPLIER = 2n;
const VESTING_FACTORY_KEY = "VestingFactory";
const VALIDATOR_WALLET_FACTORY_KEY = "ValidatorWalletFactory";
const COMBINED_ERROR_ABI = [...VESTING_ABI, ...VESTING_FACTORY_ABI, ...ADDRESS_MANAGER_ABI, ...STAKING_ABI] as const;

function extractRevertReason(err: unknown): string {
Expand Down Expand Up @@ -273,31 +268,6 @@ export const vestingActions = (
return factory;
};

const getVestingValidatorRegistrationContext = async (vesting: Address) => {
const [addressManager, chainId] = await Promise.all([
readVesting<Address>(vesting, "addressManager"),
publicClient.getChainId(),
]);
const registrar = await publicClient.readContract({
address: addressManager as ViemAddress,
abi: ADDRESS_MANAGER_ABI,
functionName: "getAddress",
args: [VALIDATOR_WALLET_FACTORY_KEY],
}) as Address;

if (!registrar || registrar === zeroAddress) {
throw new Error(
`ValidatorWalletFactory is not registered in AddressManager under key ${VALIDATOR_WALLET_FACTORY_KEY}.`,
);
}

return {
registrar,
owner: vesting,
chainId: BigInt(chainId),
};
};

const getVestingContract = (vesting: Address): VestingContract => {
return getContract({
address: vesting as ViemAddress,
Expand Down Expand Up @@ -359,22 +329,17 @@ export const vestingActions = (
/** Creates a validator wallet and self-stakes vesting-held tokens. Must be called by the vesting beneficiary. */
vestingValidatorJoin: async (options: VestingValidatorJoinOptions): Promise<VestingValidatorJoinResult> => {
const amount = parseStakingAmount(options.amount);
const context = await getVestingValidatorRegistrationContext(options.vesting);
if (!await verifyOperatorRegistration(options.registration, context)) {
throw new Error("Operator registration proof does not match the vesting, registrar, chain, or public key.");
}
const operator = operatorAddressFromPublicKey(options.registration.operatorPubKey);
const data = encodeFunctionData({
abi: VESTING_ABI,
functionName: "vestingValidatorJoin",
args: [options.registration.operatorPubKey, options.registration.possessionProof, amount],
args: [options.operator as ViemAddress, amount],
});
const result = await executeWrite({to: options.vesting as ViemAddress, data});

return {
...result,
vesting: options.vesting,
operator,
operator: options.operator,
beneficiary: client.account!.address as Address,
amount: formatStakingAmount(amount),
amountRaw: amount,
Expand Down Expand Up @@ -645,7 +610,6 @@ export const vestingActions = (
vestingRevoker: (vesting: Address): Promise<Address> => readVesting<Address>(vesting, "revoker"),
vestingFactory: (vesting: Address): Promise<Address> => readVesting<Address>(vesting, "factory"),
vestingAddressManager: (vesting: Address): Promise<Address> => readVesting<Address>(vesting, "addressManager"),
getVestingValidatorRegistrationContext,
vestingTotalAmount: (vesting: Address): Promise<bigint> => readVesting<bigint>(vesting, "totalAmount"),
vestingStartDate: (vesting: Address): Promise<bigint> => readVesting<bigint>(vesting, "startDate"),
vestingCliffDuration: (vesting: Address): Promise<bigint> => readVesting<bigint>(vesting, "cliffDuration"),
Expand Down
Loading
Loading