How to earn yield on Bitcoin in 2026: Staking, lending and DeFi
Lido has opened a permissionless route to Ethereum validators that can hold much larger effective balances, but the first allocation is deliberately limited. Its October 23 launch of 0x02 Community Staking Module separates gaining access from receiving the full amount of stake a validator could eventually support.
TL;DRThe launch announcement sets the bond at 32 ETH for the first key and 30 ETH for each additional key. Lido supplies the allocated stake. New keys first receive a 32 ETH seed deposit; further deposits follow after activation through a separate top-up queue.
The module starts with a 0.01% share limit of Lido Core’s total value locked and 32 top-up seats. Further capacity increases depend on a staged governance process and sufficient protocol inflows. A maximum balance is therefore a technical ceiling, not an immediate allocation promise to every operator.
The Ethereum change underneath this is EIP-7251, which allows a validator’s effective balance to rise to 2,048 ETH while retaining a lower activation requirement. More stake can consequently be represented by fewer validator keys.
For an operator, fewer keys can simplify parts of administration. It also makes the amount controlled by each operational setup more consequential. Key count alone becomes a weaker way to judge how broadly stake is distributed.
That distinction fits the concentration questions examined in NewsBTC’s network-resilience coverage. Independent operators, infrastructure diversity and correlated failures all matter alongside the number of validators.
An operator’s experience depends on how long a key waits for activation, how quickly additional stake arrives and how reliably the validator performs. A favorable projection at full allocation cannot describe the whole period between posting a bond and reaching that allocation.
The comparison with solo staking also requires care. A bonded operator receiving protocol stake and a solo staker providing all the capital are taking different roles. The same percentage headline can conceal different denominators, duties and sources of reward.
Large corporate ETH treasuries illustrate how staking decisions can involve substantial balances. The new CSM route instead concerns how independent operators can service stake allocated through Lido.
It is separate, too, from Ethereum’s execution-capacity tests. Supporting larger validators does not by itself raise the volume of application transactions a block can carry.
The staged launch gives the market concrete milestones to observe: activated keys, completed top-ups and approved capacity increases. Those measurements will show whether the permissionless route is expanding in practice, beyond the largest balance allowed by its design.
Written by the News Desk and edited by Samuel Rae.

TL;DR: Bitmine says its Ethereum holdings reached approximately 6.04 million ETH after it added 12,535 tokens over the past week.
Bitmine Immersion Technologies has pushed its Ethereum treasury past the six-million-token mark, reporting holdings of 6,041,481 ETH as of October 18. The company said it acquired another 12,535 ETH during the preceding week.
The size is striking even by corporate crypto-treasury standards. Bitmine describes its position as roughly 4.9% of Ethereum’s circulating supply and says more than five million of its ETH are staked.
That staking component matters. Unlike a Bitcoin treasury that generally depends on price movements, a large Ethereum holding can also earn network rewards by participating in validation. Rewards are not risk-free, however, and the economics depend on staking performance, technical operations and protocol conditions.
Bitmine valued its wider portfolio of crypto assets, cash, marketable securities and other investments at about $15.1 billion. Its holdings therefore represent a substantial bet on Ethereum as both a long-term asset and a productive network.
The company’s disclosure offers investors a clearer look at the scale of that wager, though headline token counts are not the same as shareholder returns. Equity valuation, financing arrangements and the market price of ETH all affect what ownership in the company is ultimately worth.
As more listed firms build dedicated crypto treasuries, those distinctions are likely to become increasingly important.
Written by the News Desk and edited by Samuel Rae.

TL;DR: Bitmine says its Ethereum holdings reached approximately 6.04 million ETH after it added 12,535 tokens over the past week.
Bitmine Immersion Technologies has pushed its Ethereum treasury past the six-million-token mark, reporting holdings of 6,041,481 ETH as of October 18. The company said it acquired another 12,535 ETH during the preceding week.
The size is striking even by corporate crypto-treasury standards. Bitmine describes its position as roughly 4.9% of Ethereum’s circulating supply and says more than five million of its ETH are staked.
That staking component matters. Unlike a Bitcoin treasury that generally depends on price movements, a large Ethereum holding can also earn network rewards by participating in validation. Rewards are not risk-free, however, and the economics depend on staking performance, technical operations and protocol conditions.
Bitmine valued its wider portfolio of crypto assets, cash, marketable securities and other investments at about $15.1 billion. Its holdings therefore represent a substantial bet on Ethereum as both a long-term asset and a productive network.
The company’s disclosure offers investors a clearer look at the scale of that wager, though headline token counts are not the same as shareholder returns. Equity valuation, financing arrangements and the market price of ETH all affect what ownership in the company is ultimately worth.
As more listed firms build dedicated crypto treasuries, those distinctions are likely to become increasingly important.
Written by the News Desk and edited by Samuel Rae.

TL;DR: BitGo and HashKey Cloud have expanded an earlier staking relationship into a broader Asia-Pacific partnership covering staking, trading, custody and real-world asset tokenization. HashKey will support validator services for eligible BitGo clients, initially across Ethereum and Solana.
BitGo and HashKey are turning an existing staking relationship into a wider institutional crypto infrastructure partnership across Asia.
The companies signed an expanded agreement in Singapore covering four areas: staking, trading, custody and real-world asset tokenization.
HashKey Cloud will become a validator partner on BitGo’s platform, initially supporting Ethereum and Solana staking for eligible institutional customers.
The two firms also plan to cooperate on institutional trading flows.
On the custody side, BitGo will make its infrastructure available to HashKey Capital and associated funds, subject to onboarding and individual agreements.
BitGo is also set to become a custody partner for HashKey’s real-world asset tokenization initiatives.
Taken together, those pieces form something closer to a full institutional asset stack than a single product partnership.
A fund entering crypto increasingly needs more than somewhere to store tokens.
It may need staking infrastructure for proof-of-stake assets, trading liquidity, regulated custody and systems capable of handling tokenized traditional assets.
Providing those services through connected partners can reduce some of the fragmentation that institutions face when entering digital markets.
The geographic focus matters too.
HashKey has built a substantial presence in Hong Kong and the broader Asia-Pacific crypto market, while BitGo has spent years developing regulated custody and institutional infrastructure.
BitGo’s Abel Seow said institutions in Asia increasingly want infrastructure covering the full lifecycle of an asset rather than isolated services.
The agreement attempts to provide exactly that.
The two companies had already been working together around staking before broadening the relationship.
The latest deal therefore looks less like a speculative memorandum and more like an expansion of an existing integration.
Asia has become one of the most competitive regions for institutional digital-asset infrastructure.
Hong Kong, Singapore and other financial centers have been building regulatory frameworks intended to let established financial firms engage with crypto without relying on the looser models that defined the industry’s early years.
That creates demand for providers able to combine regulated custody with blockchain-native services.
BitGo and HashKey are betting that the winning institutional platforms will need both.


This is the third blog in our series Quill Findings, where we share vulnerabilities our auditors found during client audits, for auditor and developer education. If you missed the earlier ones, you can read the first one here and the second one here.
Today we have a staking protocol, where users lock their tokens for 60 days and earn 8% APY on the staked amount. The reward math missed calculated rewards, and turned an 8% yearly yield into roughly 49%.
APY is a yearly rate and a 60 days lock period should only give rewards generated by the 60days part of that 8% yearly calculation, not all of it. In code below you can see that how 60 days rewards calculation should work:
// 8% per year, in basis points
uint256 constant APY_BPS = 800;
uint256 constant LOCK_PERIOD = 60 days;
// reward = amount x yearly rate x (lock time / 1 year)
//
// 10,000 tokens x 8% x (60 / 365)
// = 10,000 x 0.08 x 0.164
// = ~131 tokens
Here a user is staking 10,000 tokens for one lock period(60 days) and should get back about 10,131 tokens.
The contract calculated the rewards like this:
function calculateReward(uint256 amount) public pure returns (uint256) {
// bug: pays the full yearly 8% on every 60-day lock,
// the lock duration never enters the math
//
// 10,000 tokens -> 800 tokens reward
// expected -> ~131 tokens
// overpaid -> ~6x on every lock
return amount * APY_BPS / 10_000;
}The yearly rate is treated as a 60 days rate and every lock pays the full 8%, whether the tokens were locked for 60 days or a whole year.
A year has over six 60days periods cycles, so a user who keeps locking again and again earns far more than 8%:
// intended: 8% a year
// actual, re-locking(original deposit only): 8% x 6.08 periods = ~49% a year
// actual, restaking(compunding: deposit + previous reward): (1.08)^6.08 - 1 = ~60% a year
No special access or attacker contract was required. Anyone who stakes normally gets overpaid. This was a business logic implementation bug.
The rewards come from a pool that team funds. That pool is create for 8% a year, but it pays out as if the rate were about six times higher and tt drains much faster than planned, and as TVL grows.
This is a minimal example to show the bug from the code side. Its a replica of vulnerable code
src/MockToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockToken is ERC20 {
constructor() ERC20("Mock Token", "MOCK") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
src/StakingVulnerable.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract StakingVulnerable {
IERC20 public immutable token;
uint256 public constant APY_BPS = 800; // 8% a year
uint256 public constant LOCK_PERIOD = 60 days;
struct Stake {
uint256 amount;
uint256 unlockAt;
}
mapping(address => Stake) public stakes;
constructor(IERC20 _token) {
token = _token;
}
function stake(uint256 amount) external {
require(stakes[msg.sender].amount == 0, "already staked");
token.transferFrom(msg.sender, address(this), amount);
stakes[msg.sender] = Stake(amount, block.timestamp + LOCK_PERIOD);
}
// bug: the full yearly rate is paid for a 60-day lock
function calculateReward(uint256 amount) public pure returns (uint256) {
return amount * APY_BPS / 10_000;
}
function withdraw() external {
Stake memory s = stakes[msg.sender];
require(s.amount > 0, "no stake");
require(block.timestamp >= s.unlockAt, "still locked");
delete stakes[msg.sender];
token.transfer(msg.sender, s.amount + calculateReward(s.amount));
}
}
Create the fixed version. It’s the same contract with only calculateReward changed:
sed -e 's/contract StakingVulnerable/contract StakingFixed/' \
-e 's|return amount \* APY_BPS / 10_000;|return amount * APY_BPS * LOCK_PERIOD / (10_000 * 365 days);|' \
src/StakingVulnerable.sol > src/StakingFixed.sol
test/APYOverpay.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {MockToken} from "../src/MockToken.sol";
import {StakingVulnerable} from "../src/StakingVulnerable.sol";
import {StakingFixed} from "../src/StakingFixed.sol";
contract APYOverpayTest is Test {
MockToken token;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
uint256 constant STAKE = 10_000e18;
uint256 constant POOL = 800e18; // reward pool funded by the team
function setUp() public {
token = new MockToken();
token.mint(alice, STAKE);
token.mint(bob, STAKE);
}
function _stake(address user, address staking) internal {
vm.startPrank(user);
token.approve(staking, STAKE);
StakingVulnerable(staking).stake(STAKE);
vm.stopPrank();
}
function test_RewardIsFullYearlyRate() public {
StakingVulnerable staking = new StakingVulnerable(token);
uint256 paid = staking.calculateReward(STAKE);
uint256 expected = STAKE * 800 * 60 days / (10_000 * 365 days);
console2.log("reward paid for 60 days :", paid / 1e18);
console2.log("reward expected (8% APY):", expected / 1e18);
assertEq(paid, 800e18, "full 8% paid on a 60-day lock");
assertGt(paid, expected * 6, "more than 6x the intended reward");
}
function test_FirstStakerDrainsPool() public {
StakingVulnerable staking = new StakingVulnerable(token);
token.mint(address(staking), POOL);
_stake(alice, address(staking));
_stake(bob, address(staking));
vm.warp(block.timestamp + 60 days);
vm.prank(alice);
staking.withdraw();
console2.log("alice received :", token.balanceOf(alice) / 1e18);
// the pool is already gone, bob can't even get his principal back
vm.prank(bob);
vm.expectRevert();
staking.withdraw();
console2.log("bob withdraw : reverted");
}
function test_FixedPaysOnlySixtyDays() public {
StakingFixed staking = new StakingFixed(token);
token.mint(address(staking), POOL);
_stake(alice, address(staking));
_stake(bob, address(staking));
vm.warp(block.timestamp + 60 days);
vm.prank(alice);
staking.withdraw();
vm.prank(bob);
staking.withdraw();
console2.log("alice received :", token.balanceOf(alice) / 1e18);
console2.log("bob received :", token.balanceOf(bob) / 1e18);
console2.log("pool left :", token.balanceOf(address(staking)) / 1e18);
assertEq(token.balanceOf(alice), STAKE + staking.calculateReward(STAKE));
assertEq(token.balanceOf(bob), STAKE + staking.calculateReward(STAKE));
}
}
Run it:
forge test --match-contract APYOverpayTest -vv
Output:
Ran 3 tests for test/APYOverpay.t.sol:APYOverpayTest
[PASS] test_FirstStakerDrainsPool() (gas: 440263)
Logs:
alice received : 10800
bob withdraw : reverted
[PASS] test_FixedPaysOnlySixtyDays() (gas: 503675)
Logs:
alice received : 10131
bob received : 10131
pool left : 536
[PASS] test_RewardIsFullYearlyRate() (gas: 16956)
Logs:
reward paid for 60 days : 800
reward expected (8% APY): 131
Suite result: ok. 3 passed; 0 failed; 0 skipped
Here’s what the three tests show.
test_RewardIsFullYearlyRate: for 10,000 tokens, the contract pays 800 tokens for a 60-day lock, instead of 131.
test_FirstStakerDrainsPool: the team funds the pool with 800 tokens, which is more than enough for two users at the real reward distribution. Alice and Bob each stake 10,000 and after 60 days, Alice withdraws 10,800 and takes the whole pool. When Bob tries to withdraw, the contract owes him 10,800 but only holds 10,000, so his withdrawal reverts. Bob can't even get his principal back.
test_FixedPaysOnlySixtyDays: the same flow on the fixed contract. Both users get about 10,131 back, and 536 tokens are still left in the pool.
Scale the yearly rate down to the lock duration.
- return amount * APY_BPS / 10_000;
+ return amount * APY_BPS * LOCK_PERIOD / (10_000 * 365 days);
function calculateReward(uint256 amount) public pure returns (uint256) {
// fix: only pay the 60-day share of the yearly rate
// multiply first, divide once at the end,
// so integer division doesn't round rewards away
//
// 10,000 tokens -> ~131 tokens, matches 8% APY
return amount * APY_BPS * LOCK_PERIOD / (10_000 * 365 days);
}The order of operations matters here. If you divide first, for example LOCK_PERIOD / 365 days, Solidity's integer division rounds it down to 0, and every user gets nothing. Multiplying everything first and dividing once at the end keeps the result accurate.
If the protocol ever supports more than one lock period, pass the actual lock duration into the function instead of relying on a single constant. With this, the same formula stays correct for every option.
Nothing in this bug reverts, and no test fails unless it checks the reward amount itself. The contract runs smoothly while paying far more than intended. When a rate is yearly, every payout needs to know how long the money was locked, and here, one missing time factor turned 8% into 49%.
Originally published at https://www.quillaudits.com.
Quill Findings: APY Miscalculation in Staking Rewards was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.