Overview
The stake action stakes NEAR tokens with a validator to earn rewards and participate in network consensus. Staking helps secure the NEAR network while earning yields.
Signature
stake(args: {
amount: NearTokenArgs,
validatorPublicKey: string
}): StakeAction
Parameters
The amount of NEAR tokens to stake. Can be specified as:
{ near: string | number } - Amount in NEAR
{ yoctoNear: string | bigint } - Amount in yoctoNEAR
The public key of the validator to stake with. Format: ed25519: followed by the base58-encoded key.
Returns
A StakeAction object:
{
actionType: 'Stake',
amount: NearTokenArgs,
validatorPublicKey: string
}
Basic Example
import { stake, near } from '@near-api-ts/universal';
const action = stake({
amount: near('1000'),
validatorPublicKey: 'ed25519:ValidatorPublicKeyHere...'
});
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action
}
});
console.log('Tokens staked successfully');
Staking is done from and to the same account. The receiverAccountId is the account that will stake the tokens.
Stake with Known Validator
import { stake, near } from '@near-api-ts/universal';
// Stake with a specific validator
const validatorKey = 'ed25519:6DSjZ8mvsRZDvFqFxo8tCKePG96omXW7eVYVSySmDk8e';
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('100'),
validatorPublicKey: validatorKey
})
}
});
Find Validators
import { stake, near } from '@near-api-ts/universal';
// Get list of validators from RPC
const validators = await client.getValidators();
// Choose validator with good performance
const validator = validators.currentValidators.find(v =>
v.is_slashed === false &&
v.num_expected_blocks > 0
);
if (validator) {
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('500'),
validatorPublicKey: validator.public_key
})
}
});
}
Unstake Tokens
To unstake, stake 0 tokens:
import { stake, near } from '@near-api-ts/universal';
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('0'),
validatorPublicKey: 'ed25519:ValidatorPublicKeyHere...'
})
}
});
console.log('Unstaking initiated');
Unstaking has a delay (typically 4 epochs, ~2 days on mainnet). Your tokens will be locked during this period.
Update Stake Amount
import { stake, near } from '@near-api-ts/universal';
const validatorKey = 'ed25519:ValidatorPublicKeyHere...';
// Increase stake
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('2000'), // New total amount
validatorPublicKey: validatorKey
})
}
});
The stake amount is the new total, not an additional amount. If you have 1000 NEAR staked and stake 2000 NEAR, you’ll have 2000 NEAR staked total (not 3000).
Check Staking Status
import { stake, near } from '@near-api-ts/universal';
// Get account staking info
const accountInfo = await client.getAccountInfo({
accountId: 'alice.near'
});
console.log('Staked balance:', accountInfo.staked);
console.log('Available balance:', accountInfo.amount);
if (BigInt(accountInfo.staked) > 0n) {
console.log('Account is currently staking');
}
Stake Minimum Amount
import { stake, near } from '@near-api-ts/universal';
// Check validator minimum stake
const MINIMUM_STAKE = near('1'); // Varies by validator
try {
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: MINIMUM_STAKE,
validatorPublicKey: 'ed25519:ValidatorPublicKeyHere...'
})
}
});
} catch (error) {
if (isNatError(error) &&
error.kind === 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.BelowThreshold') {
console.error('Stake amount below minimum threshold');
}
}
Change Validators
import { stake, near } from '@near-api-ts/universal';
const oldValidatorKey = 'ed25519:OldValidatorKey...';
const newValidatorKey = 'ed25519:NewValidatorKey...';
// Unstake from old validator
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('0'),
validatorPublicKey: oldValidatorKey
})
}
});
console.log('Wait for unstaking period (4 epochs)...');
// After unstaking period, stake with new validator
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('1000'),
validatorPublicKey: newValidatorKey
})
}
});
Error Handling
import { stake, near, isNatError } from '@near-api-ts/universal';
try {
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near('1000'),
validatorPublicKey: 'ed25519:ValidatorKey...'
})
}
});
} catch (error) {
if (isNatError(error)) {
switch (error.kind) {
case 'CreateAction.Stake.Args.InvalidSchema':
console.error('Invalid stake parameters');
break;
case 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.BelowThreshold':
console.error('Stake amount below minimum');
break;
case 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.Balance.TooLow':
console.error('Insufficient balance for staking');
break;
case 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.NotFound':
console.error('Validator not found');
break;
default:
console.error('Staking failed:', error.kind);
}
}
}
Safe Variant
import { safeStake, near } from '@near-api-ts/universal';
const actionResult = safeStake({
amount: near('1000'),
validatorPublicKey: 'ed25519:ValidatorKey...'
});
if (!actionResult.ok) {
console.error('Invalid stake action:', actionResult.error.kind);
return;
}
const result = await signer.safeExecuteTransaction({
intent: {
receiverAccountId: 'alice.near',
action: actionResult.value
}
});
if (result.ok) {
console.log('Staked successfully:', result.value.transactionHash);
} else {
console.error('Staking failed:', result.error.kind);
}
Calculate Expected Rewards
import { stake, near } from '@near-api-ts/universal';
const stakeAmount = 1000; // NEAR
const annualAPY = 0.10; // 10% APY (example)
const dailyRewards = (stakeAmount * annualAPY) / 365;
const monthlyRewards = (stakeAmount * annualAPY) / 12;
const yearlyRewards = stakeAmount * annualAPY;
console.log(`Staking ${stakeAmount} NEAR at ${annualAPY * 100}% APY:`);
console.log(`Daily rewards: ~${dailyRewards.toFixed(4)} NEAR`);
console.log(`Monthly rewards: ~${monthlyRewards.toFixed(2)} NEAR`);
console.log(`Yearly rewards: ~${yearlyRewards.toFixed(2)} NEAR`);
await signer.executeTransaction({
intent: {
receiverAccountId: 'alice.near',
action: stake({
amount: near(stakeAmount.toString()),
validatorPublicKey: 'ed25519:ValidatorKey...'
})
}
});
Common Errors
CreateAction.Stake.Args.InvalidSchema - Invalid parameters
CreateAction.Stake.Internal - Internal error
MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.BelowThreshold - Amount below minimum
MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.Balance.TooLow - Insufficient account balance
MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.NotFound - Validator not found or not active
Important Notes
Staking Risks:
- Tokens are locked during unstaking period (~2 days)
- Validators can be slashed for misbehavior
- Your stake could be affected if validator is slashed
- Choose reputable validators with good track records
Staking Mechanics:
- Rewards are automatically compounded
- Unstaking takes 4 epochs (approximately 48 hours)
- Minimum stake varies by validator
- You can only stake with one validator at a time per account
Validator Selection:
Consider these factors when choosing a validator:
- Uptime and reliability
- Commission rate
- Total stake (not too large or too small)
- Reputation in the community
- Geographic diversity
Best Practices
DO:
- Research validators before staking
- Start with a small amount to test
- Monitor validator performance regularly
- Understand the unstaking period
- Keep some NEAR unstaked for transactions
- Diversify across multiple accounts if staking large amounts
DON’T:
- Don’t stake all your NEAR (keep some for gas)
- Don’t stake with unknown validators
- Don’t expect instant unstaking
- Don’t ignore validator performance changes
- Don’t forget about the lock-up period
Staking Checklist
See Also