Skip to main content

Delegation & Staking

Manage delegation contracts, validator nodes, and staking operations on MultiversX.

Overview

The DelegationController provides an API for:

  • Validator Operations - Create delegation contracts, manage validator nodes
  • Delegator Operations - Stake, unstake, claim rewards
  • Contract Management - Configure fees, caps, and metadata

DelegationController

import 'package:abidock_mvx/abidock_mvx.dart';

final controller = DelegationController(
chainId: const ChainId.mainnet(),
gasLimitEstimator: GasEstimator(networkProvider: provider),
);

Validator Operations

Create Delegation Contract

Create a new staking provider contract:

import 'dart:io';

// Account.fromPem takes the PEM *content*, not a file path.
final pemContent = await File('validator.pem').readAsString();
final account = await Account.fromPem(pemContent);
final accountInfo = await provider.getAccount(account.address);

final input = NewDelegationContractInput(
totalDelegationCap: BigInt.from(10000000) * BigInt.from(10).pow(18), // 10M EGLD cap
serviceFee: BigInt.from(1000), // 10% fee (10000 = 100%)
amount: Balance.fromEgld(1250), // 1250 EGLD initial stake
);

final tx = await controller.createTransactionForNewDelegationContract(
account,
accountInfo.nonce,
input,
);

final hash = await provider.sendTransaction(tx);
print('Delegation contract created: $hash');
Minimum Requirements
  • Minimum deposit of 1250 EGLD to create a delegation contract
  • A validator node requires 2500 EGLD of base stake before it can be staked
  • Service fee is in basis points (1000 = 10%, 10000 = 100%)

Add Validator Nodes

Adding a node proves ownership of the validator key: for each BLS public key you supply a BLS signature over the delegation contract address bytes.

BLS signing needs a backend

Validator identities are BLS12-381 keypairs (32-byte secret, 96-byte public key, 96-byte signature). No pure-Dart BLS12-381 implementation ships with this package, so ValidatorSecretKey.sign throws UnimplementedError. Wire up a native BLS plugin, an FFI binding, or a remote signing service and hand it to ValidatorSigner.custom.

import 'dart:typed_data';

// Your BLS backend, exposed as Uint8List Function(Uint8List).
final signer = ValidatorSigner.custom(
(Uint8List bytes) => myBlsBackend.sign(bytes),
);

// The proof is a signature over the delegation contract address bytes.
final Uint8List proofPayload = Uint8List.fromList(delegationContract.bytes);
final Uint8List signedMessage = signer.sign(proofPayload);

// BLS public keys are 96-byte values, built from hex or raw bytes.
final validatorPublicKey = ValidatorPublicKey.fromHex('00e9...');

final input = AddNodesInput(
delegationContract: delegationContract,
publicKeys: [validatorPublicKey],
signedMessages: [signedMessage],
);

final tx = await controller.createTransactionForAddingNodes(
account,
accountInfo.nonce,
input,
);

publicKeys and signedMessages must be the same length -- the factory throws ArgumentError otherwise, since each key is paired positionally with its proof.

Reading validator keys from PEM

ValidatorSecretKey.fromPem parses PEM content, not a path, and the PEM header carries the matching public key hex:

final pemText = await File('validator_key.pem').readAsString();
final secretKey = ValidatorSecretKey.fromPem(pemText);

// Multiple entries in one file:
final allKeys = parseValidatorKeys(pemText);

The secret key is still useful for storage and transport (toPem); only the signing step needs the external BLS backend.

Stake Nodes

Activate validator nodes for consensus:

final input = ManageNodesInput(
delegationContract: delegationContract,
publicKeys: [validatorPublicKey1, validatorPublicKey2],
);

final tx = await controller.createTransactionForStakingNodes(
account,
accountInfo.nonce,
input,
);

Unstake Nodes

Remove nodes from active validation:

final input = ManageNodesInput(
delegationContract: delegationContract,
publicKeys: [validatorPublicKey],
);

final tx = await controller.createTransactionForUnstakingNodes(
account,
accountInfo.nonce,
input,
);

Unbond Nodes

Complete unbonding after unstaking period:

final input = ManageNodesInput(
delegationContract: delegationContract,
publicKeys: [validatorPublicKey],
);

final tx = await controller.createTransactionForUnbondingNodes(
account,
accountInfo.nonce,
input,
);

Remove Nodes

Remove validator keys from contract:

final input = ManageNodesInput(
delegationContract: delegationContract,
publicKeys: [validatorPublicKey],
);

final tx = await controller.createTransactionForRemovingNodes(
account,
accountInfo.nonce,
input,
);

Unjail Nodes

Unjail slashed validator nodes:

final input = UnjailingNodesInput(
delegationContract: delegationContract,
publicKeys: [jailedValidatorKey],
// The fine is 2.5 EGLD per jailed node -- multiply by the number of keys
// you are unjailing. Any other amount is rejected by the chain.
amount: Balance.fromEgld(2.5),
);

final tx = await controller.createTransactionForUnjailingNodes(
account,
accountInfo.nonce,
input,
);

Contract Configuration

Change Service Fee

final input = ChangeServiceFeeInput(
delegationContract: delegationContract,
serviceFee: BigInt.from(1500), // Change to 15%
);

final tx = await controller.createTransactionForChangingServiceFee(
account,
accountInfo.nonce,
input,
);

Modify Delegation Cap

final input = ModifyDelegationCapInput(
delegationContract: delegationContract,
delegationCap: BigInt.from(5000000) * BigInt.from(10).pow(18), // 5M EGLD
);

final tx = await controller.createTransactionForModifyingDelegationCap(
account,
accountInfo.nonce,
input,
);

Set Metadata

final input = SetMetadataInput(
delegationContract: delegationContract,
name: 'My Staking Provider',
website: 'https://mystaking.com',
identifier: 'mystaking',
);

final tx = await controller.createTransactionForSettingMetadata(
account,
accountInfo.nonce,
input,
);

Automatic Activation

Enable/disable automatic node activation:

final input = ManageDelegationContractInput(
delegationContract: delegationContract,
);

// Enable
final enableTx = await controller.createTransactionForSettingAutomaticActivation(
account,
accountInfo.nonce,
input,
);

// Disable
final disableTx = await controller.createTransactionForUnsettingAutomaticActivation(
account,
accountInfo.nonce,
input,
);

Cap Check on Redelegate

Control whether delegation cap applies to reward redelegation:

final input = ManageDelegationContractInput(
delegationContract: delegationContract,
);

// Enable cap check
final enableTx = await controller.createTransactionForSettingCapCheckOnRedelegateRewards(
account,
accountInfo.nonce,
input,
);

// Disable cap check
final disableTx = await controller.createTransactionForUnsettingCapCheckOnRedelegateRewards(
account,
accountInfo.nonce,
input,
);

Delegator Operations

Delegate EGLD

Stake EGLD with a staking provider:

final input = DelegateInput(
delegationContract: Address.fromBech32('erd1qqqqqqqqqqqqqpgq...'),
amount: Balance.fromEgld(100), // Delegate 100 EGLD
);

final tx = await controller.createTransactionForDelegating(
account,
accountInfo.nonce,
input,
);

final hash = await provider.sendTransaction(tx);
print('Delegated: $hash');

Undelegate EGLD

Initiate unstaking. Funds stay locked for the unbonding period -- 10 epochs, roughly 10 days at the current epoch length -- before withdraw releases them:

final input = UndelegateInput(
delegationContract: delegationContract,
amount: Balance.fromEgld(50), // Undelegate 50 EGLD
);

final tx = await controller.createTransactionForUndelegating(
account,
accountInfo.nonce,
input,
);

Withdraw Funds

Claim unbonded EGLD after unbonding period:

final input = WithdrawInput(
delegationContract: delegationContract,
);

final tx = await controller.createTransactionForWithdrawing(
account,
accountInfo.nonce,
input,
);

Claim Rewards

Claim accumulated staking rewards:

final input = WithdrawInput(
delegationContract: delegationContract,
);

final tx = await controller.createTransactionForClaimingRewards(
account,
accountInfo.nonce,
input,
);

Redelegate Rewards

Automatically restake rewards:

final input = WithdrawInput(
delegationContract: delegationContract,
);

final tx = await controller.createTransactionForRedelegatingRewards(
account,
accountInfo.nonce,
input,
);

Parsing Outcomes

Use DelegationOutcomeParser to extract results:

final parser = DelegationOutcomeParser();

// Parse delegation contract creation
final tx = await provider.getTransaction(hash);
final results = parser.parseCreateNewDelegationContract(tx);

for (final result in results) {
print('Contract address: ${result.contractAddress}');
}

Complete Example

import 'package:abidock_mvx/abidock_mvx.dart';

void main() async {
final provider = GatewayNetworkProvider.mainnet();
final account = await Account.fromMnemonic('your mnemonic...');

final controller = DelegationController(
chainId: const ChainId.mainnet(),
gasLimitEstimator: GasEstimator(networkProvider: provider),
);

// Get account info
final accountInfo = await provider.getAccount(account.address);

// Choose a staking provider
final stakingProvider = Address.fromBech32('erd1qqqqqqqqqqqqqpgq...');

// Delegate 100 EGLD
final delegateTx = await controller.createTransactionForDelegating(
account,
accountInfo.nonce,
DelegateInput(
delegationContract: stakingProvider,
amount: Balance.fromEgld(100),
),
);

final hash = await provider.sendTransaction(delegateTx);
print('Delegated: $hash');

// Wait for rewards to accumulate...

// Claim rewards
final claimTx = await controller.createTransactionForClaimingRewards(
account,
accountInfo.nonce.increment(),
WithdrawInput(delegationContract: stakingProvider),
);

await provider.sendTransaction(claimTx);
print('Rewards claimed!');
}

Service Fee Reference

Fee (basis points)Percentage
5005%
100010%
150015%
200020%
10000100%

Important Notes

  1. Unbonding Period - Undelegated funds are locked for 10 epochs (about 10 days) before they can be withdrawn
  2. Minimum Stake - Check provider's minimum delegation requirement
  3. Rewards - Rewards accumulate continuously and can be claimed anytime
  4. Validator Keys - Keep validator secret keys secure and backed up
  5. Slashing - Jailed nodes require unjail fee and may lose rewards