A Solana program built with Anchor that allows users to bond tokens for a configurable period of time. After the lock period expires, users can withdraw their bonded tokens.
The Bond program implements a simple token locking mechanism with the following features:
- Configurable parameters: Admin can set the allowed mint, bond duration, and required bond amount
- Single mint restriction: Only one token mint can be used for bonding (set by admin)
- Time-locked bonds: Tokens are locked for a configurable duration before withdrawal
- 2-step ownership transfer: Secure admin transfer requiring both parties to act
Global configuration account that stores program parameters.
| Field | Type | Description |
|---|---|---|
authority |
Pubkey |
Admin who can update config |
allowed_mint |
Pubkey |
The only mint allowed for bonding |
bond_duration_seconds |
i64 |
Lock period in seconds |
bond_amount |
u64 |
Required bond amount (base units) |
pending_authority |
Option<Pubkey> |
For 2-step ownership transfer |
bump |
u8 |
PDA bump seed |
Seeds: [b"config"]
Per-user bond record.
| Field | Type | Description |
|---|---|---|
signer |
Pubkey |
User who created the bond |
mint |
Pubkey |
Token mint address |
total_bonded |
u64 |
Amount of tokens locked |
start_time |
i64 |
Unix timestamp of bond creation |
bump |
u8 |
PDA bump seed |
token_account_bump |
u8 |
Token account PDA bump |
Seeds: [b"bond_account", signer, mint]
Token account that holds the bonded tokens, controlled by the program.
Seeds: [b"bond_token_account", signer, mint]
Initializes the global config. Can only be called once.
Parameters:
bond_duration_seconds: i64- Lock period (must be > 30 days)bond_amount: u64- Required bond amount in base unitsauthority: Option<Pubkey>- Admin address (defaults to signer)
Accounts:
signer- Payer and default authorityallowed_mint- The mint to allow for bondingconfig- Config PDA (created)system_program
Creates a new bond by transferring tokens to a program-controlled account.
Parameters:
amount: u64- Must equalconfig.bond_amount * 10^decimals
Accounts:
signer- User creating the bondmint- Must matchconfig.allowed_mintbond_account- User's bond record (created)signer_token_account- User's token account (source)bond_token_account- Program token account (created)config- Global configassociated_token_program,system_program,token_program
Withdraws bonded tokens after the lock period has elapsed.
Accounts:
signer- Must be the original bondermint- Token mintbond_account- User's bond recordsigner_token_account- User's token account (destination)bond_token_account- Program token account (source)config- Global configassociated_token_program,system_program,token_program
Updates configuration parameters. Admin only.
Parameters:
bond_duration_seconds: i64- New lock periodbond_amount: u64- New bond amount
Accounts:
signer- Must be current authoritynew_allowed_mint- Optional new mint to allowconfig- Global config
Transfers admin ownership using a 2-step process.
Parameters:
action: AuthorityAction-SetPendingorAcceptPendingnew_authority: Pubkey- New admin address
Accounts:
signer- Current authority (forSetPending) or pending authority (forAcceptPending)config- Global config
Flow:
- Current admin calls with
SetPendingand new admin's address - New admin calls with
AcceptPendingto complete transfer
| Constant | Value | Description |
|---|---|---|
BOND_PERIOD_MIN |
2,592,000 (30 days) | Minimum bond duration |
BOND_AMOUNT_MIN |
100 | Minimum bond amount (base units) |
| Error | Description |
|---|---|
AlreadyBonded |
User already has an active bond |
InvalidBondAmount |
Amount doesn't match config or below minimum |
NoBondToWithdraw |
No active bond or wrong signer |
CannotWithdrawBondYet |
Lock period hasn't elapsed |
MinimumBondDurationNotMet |
Duration below 30 days |
Unauthorized |
Caller lacks permission |
InvalidMint |
Mint doesn't match allowed mint |
# Build the program
anchor build
# Run tests
anchor test
# Deploy to devnet
anchor deploy --provider.cluster devnetimport * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Bond } from "../target/types/bond";
// Initialize config (admin only, once)
await program.methods
.initialize(
new anchor.BN(60 * 60 * 24 * 30), // 30 days
new anchor.BN(100), // 100 tokens (base units)
null // use signer as authority
)
.accounts({
signer: admin.publicKey,
allowedMint: tokenMint,
config: configPda,
systemProgram: SystemProgram.programId,
})
.signers([admin])
.rpc();
// Create a bond
await program.methods
.createBond(new anchor.BN(100_000_000)) // 100 tokens with 6 decimals
.accounts({
signer: user.publicKey,
mint: tokenMint,
bondAccount: bondAccountPda,
signerTokenAccount: userTokenAccount,
bondTokenAccount: bondTokenAccountPda,
config: configPda,
// ... other accounts
})
.signers([user])
.rpc();
// Withdraw after lock period
await program.methods
.withdrawBond()
.accounts({
signer: user.publicKey,
mint: tokenMint,
bondAccount: bondAccountPda,
signerTokenAccount: userTokenAccount,
bondTokenAccount: bondTokenAccountPda,
config: configPda,
// ... other accounts
})
.signers([user])
.rpc();- Single bond per user per mint: Each user can only have one active bond for the configured mint
- Time-based unlock: Bonds cannot be withdrawn before
start_time + bond_duration_seconds - Admin controls: Only the authority can update config parameters
- 2-step ownership: Prevents accidental admin lockout during transfers
- Mint validation: Only the configured mint is accepted for bonding
MIT