Boar Finance lets users lock BTC into veBTC positions on Mezo and delegate voting power to managed position. Boar Finance votes and compounds rewards on behalf of all delegators, earning yield each epoch (7 days).
By integrating Boar Finance, you give your users access to BTC yield on Mezo. In return, locks created through your integration are attributed to you via on-chain referrer tracking, enabling revenue-share based on the TVL you bring.
Note
Reference implementation: See example/ for a working React app that implements the full flow below.
The integration requires two user interactions:
- Sign permit + Lock - User signs a gasless permit, then locks BTC through LockGateway with your referrer address
- Delegate - Delegate the resulting veBTC NFT to Boar's managed position
import { parseUnits, parseSignature, decodeEventLog, toEventSelector } from "viem"
const YOUR_REFERRER_ADDRESS = "0xYourAddress" // Replace with your address for revenue attribution
const LOCK_GATEWAY = "0xb4C0eA5E674Cd32D93EbE94F9d2a31CeB0490132" // mainnet
const POOLS_VOTER = "0x48233cCC97B87Ba93bCA212cbEe48e3210211f03" // mainnet
const BTC_TOKEN = "0x7b7C000000000000000000000000000000000000"
const BOAR_TOKEN_ID = 1226n // mainnet
const amount = parseUnits("0.1", 18) // 0.1 BTC
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour
// Step 1: Sign EIP-2612 permit (off-chain, no gas)
const [nonce, name] = await Promise.all([
publicClient.readContract({
address: BTC_TOKEN,
abi: btcAbi,
functionName: "nonce",
args: [userAddress],
}),
publicClient.readContract({
address: BTC_TOKEN,
abi: btcAbi,
functionName: "name",
}),
])
const signature = await walletClient.signTypedData({
domain: {
name,
version: "1",
chainId: 31612,
verifyingContract: BTC_TOKEN,
},
types: {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
primaryType: "Permit",
message: {
owner: userAddress,
spender: LOCK_GATEWAY,
value: amount,
nonce,
deadline,
},
})
const { v, r, s } = parseSignature(signature)
// Step 2: Lock with permit + referrer (single transaction)
const txHash = await walletClient.writeContract({
address: LOCK_GATEWAY,
abi: lockGatewayAbi,
functionName: "lock",
args: [amount, deadline, Number(v), r, s, YOUR_REFERRER_ADDRESS],
})
// Read tokenId from the Locked event in the receipt
const LOCKED_TOPIC = toEventSelector("Locked(address,uint256,uint256,address)")
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })
let tokenId: bigint | undefined
for (const log of receipt.logs) {
if (log.address.toLowerCase() !== LOCK_GATEWAY.toLowerCase()) continue
if (log.topics[0] !== LOCKED_TOPIC) continue
const decoded = decodeEventLog({ abi: lockGatewayAbi, data: log.data, topics: log.topics })
if (decoded.eventName === "Locked" && "tokenId" in decoded.args) {
tokenId = decoded.args.tokenId as bigint
break
}
}
// Step 3: Delegate to Boar
await walletClient.writeContract({
address: POOLS_VOTER,
abi: poolsVoterAbi,
functionName: "depositManaged",
args: [tokenId, BOAR_TOKEN_ID],
})Important
Delegation will revert during the distribution window (first hour of each epoch, Thu 00:00-01:00 UTC). See Constraints for details.
Note
Mezo is an EVM-compatible L2 that uses BTC as its native gas token. Users need a small amount of native BTC for gas in addition to the BTC ERC-20 token used for locking.
Pass your address as the referrer parameter when calling lock. Every lock emits a Locked event that permanently associates the referrer with the minted tokenId:
event Locked(
address indexed user,
uint256 indexed tokenId,
uint256 amount,
address referrer
);Boar uses these events to track which locks were created through your integration and calculates revenue-share per epoch based on the delegated TVL attributed to you.
LockGateway provides two ways to lock BTC. Both support optional referrer tracking.
The user pre-approves BTC spending, then locks in a second transaction. Works with all wallet types. Referrer is required - without referrer tracking there is no reason to use the gateway (users can lock directly via veBTC.createLock).
LockGateway.lock(amount, referrer) → tokenIdThe user signs an EIP-2612 permit off-chain, then locks in a single transaction (no separate approve needed). Only works with wallets that support eth_signTypedData.
// With referrer
LockGateway.lock(amount, permitDeadline, v, r, s, referrer) → tokenId
// Without referrer
LockGateway.lock(amount, permitDeadline, v, r, s) → tokenId| Parameter | Type | Description |
|---|---|---|
amount |
uint256 |
BTC to lock (18 decimals) |
referrer |
address |
Your address for rev-share attribution |
permitDeadline |
uint256 |
Unix timestamp, permit expiry (permit flow only) |
v, r, s |
uint8, bytes32, bytes32 |
EIP-2612 permit signature (permit flow only) |
Lock duration is 28 days (4 epochs) in the contract and remains as such after undelegation.
Both methods return tokenId (uint256) - the ID of the newly minted veBTC NFT, needed for the delegation step.
After locking, delegate the veBTC NFT to Boar's managed position:
PoolsVoter.depositManaged(tokenId, boarTokenId)After delegation, Boar votes and compounds rewards on behalf of the user. The lock's voting power is consolidated under Boar's managed position.
Important
- Only
NORMALtype locks can be delegated (freshly created locks are always NORMAL) - Cannot delegate during the distribution window (first hour of each epoch)
- Epochs flip every 7 days, every Thursday at 00:00:00 UTC. Check the current epoch boundary via
PoolsVoter.epochNext(block.timestamp)or the Boar Finance dashboard. - If the lock has active votes from a previous delegation, call
PoolsVoter.reset(tokenId)first
uint256 count = VeBTC.balanceOf(userAddress);
for (uint256 i = 0; i < count; i++) {
uint256 tokenId = VeBTC.ownerToNFTokenIdList(userAddress, i);
// Lock details: amount (int128), end (uint256), isPermanent (bool), boost (uint256)
VeBTC.locked(tokenId);
// Voting power
VeBTC.votingPowerOfNFT(tokenId);
// Lock type:
// 0 = NORMAL - freshly created, not yet delegated
// 1 = LOCKED - delegated to a managed position (via depositManaged)
// 2 = MANAGED - Boar's own managed position
VeBTC.escrowType(tokenId);
}// Returns the managed token ID this lock is delegated to (0 if not delegated)
PoolsVoter.idToManaged(tokenId);Yield auto-compounds each epoch (7 days). Delegators do not need to claim or take any action - Boar handles voting and compounding automatically. The compounded yield increases the voting power and underlying BTC amount of the managed position.
To check current APY, visit the Boar Finance dashboard. APY varies per epoch based on the total BTC delegated and the rewards distributed.
PoolsVoter.withdrawManaged(tokenId);After undelegation, the lock's end time extends to 28 days from the current epoch.
VeBTC.withdraw(tokenId);Only possible after the lock's end timestamp has passed.
| Contract | Address |
|---|---|
| BTC (ERC-20) | 0x7b7C000000000000000000000000000000000000 |
| LockGateway | 0xb4C0eA5E674Cd32D93EbE94F9d2a31CeB0490132 |
| veBTC (VotingEscrow) | 0x3D4b1b884A7a1E59fE8589a3296EC8f8cBB6f279 |
| Boar Managed Token | 1226 (pass as _mTokenId to depositManaged) |
| Contract | Address |
|---|---|
| BTC (ERC-20) | 0x7b7C000000000000000000000000000000000000 |
| LockGateway | 0x8B2de1591843D1B167035c654CC476A28548470C |
| veBTC (VotingEscrow) | 0x38E35d92E6Bfc6787272A62345856B13eA12130a |
| Boar Managed Token | 503 (pass as _mTokenId to depositManaged) |
| Mainnet | Testnet | |
|---|---|---|
| Chain ID | 31612 |
31611 |
| RPC | See chainlist | https://rpc.test.mezo.org |
| Explorer | https://explorer.mezo.org |
https://explorer.test.mezo.org |
Important
For production use, contact Boar RPC team for a dedicated endpoint or pick a public RPC from chainlist.
- Epoch duration: 7 days (604,800 seconds). Epochs flip every Thursday at 00:00:00 UTC.
- Distribution window: First hour of each epoch (Thu 00:00-01:00 UTC). No delegation or withdrawal during this time.
- Lock duration: 28 days (4 epochs)