A Solana program implementing a simple banking system with SOL and SPL token support, featuring deposit/withdrawal operations and a 24-hour withdrawal cooldown.
- SOL Operations: Deposit and withdraw SOL to/from a program-controlled vault
- Token Operations: Deposit and withdraw SPL tokens using associated token accounts
- Security: 24-hour cooldown period for withdrawals based on slot progression
- Testing: Scripts for local development with Surfpool compatibility
- Rust and Cargo
- Solana CLI
- Anchor Framework
- Node.js and Yarn
- Surfpool (for advanced testing)
-
Clone and build the program:
git clone <repository-url> cd solbank anchor build
-
Configure environment:
- Copy
.env.exampleto.env - Generate keypairs and add base58 private keys:
npx ts-node scripts/generate-keypair.ts
- Set
MINT_PRIVATE_KEY,TOKEN_AUTH_PRIVATE_KEY,USER_PRIVATE_KEY, andUSDT_MINTin.env
- Copy
-
Start Surfpool:
surfpool start
(Note: Surfpool automatically deploys the program when started)
initialize: Set up the program vault and store initialization slotdeposit_sol(amount): Deposit SOL into the vaultwithdraw_sol(amount): Withdraw SOL from the vault (after 24-hour cooldown)deposit_token(amount): Deposit tokens into the vaultwithdraw_token(amount): Withdraw tokens from the vault (after 24-hour cooldown)
Use the Justfile for easy testing:
# Run tests without deploying, using local Surfpool RPC
just test-local-surfpool
# Start Surfpool and run tests (deploys automatically)
just test-with-surfpool-deploy
# Start Surfpool (uses MAIN_RPC environment variable)
just run-surfpool
# Generate a new keypair and output private key in base58
just generate-keypair
# Create a test mint
just create-mint
# Deposit tokens (requires mint address as argument)
just deposit-token <mint-address>
# Withdraw tokens (requires mint address as argument)
just withdraw-token <mint-address>
# Setup USDT balance to 1e12
just setup-usdt-balance
# Setup USDT account with custom balance (amount in lamports, default 1e9)
just setup-usdt-account <amount>
# Deposit USDT
just deposit-usdt
# Withdraw USDT
just withdraw-usdt
# Show help information
just help- MINT_PRIVATE_KEY: Funds transaction fees for mint creation
- TOKEN_AUTH_PRIVATE_KEY: Authority for minting tokens
- USER_PRIVATE_KEY: Signs program interactions (deposits/withdrawals)
- Vault PDA: Stores SOL and owns token accounts
- Program State: Tracks initialization slot for cooldown enforcement
- Associated Token Accounts: Used for token storage
- Slot-based Cooldown: Prevents withdrawals within ~24 hours of initialization
- PDA-based vault for secure fund storage
- Authority checks on token operations
- Slot-based time locks for withdrawals
- Proper account validation and ownership checks
anchor build# Run full test suite (includes initialize)
anchor test
# Test with Surfpool deployment (includes initialize)
just test-with-surfpool-deploy
# Quick test without full deployment
just test-local-surfpoolscripts/create-mint.ts: Create test token mintscripts/deposit-token.ts: Deposit tokensscripts/withdraw-token.ts: Withdraw tokensscripts/setup-usdt-account.ts: Set USDT balance by directly setting account data (demonstrates surfnet_setAccount)scripts/setup-usdt-balance.ts: Set USDT balance via token cheatcode (demonstrates surfnet_setTokenAccount)scripts/generate-keypair.ts: Generate new keypairs
This project demonstrates core Solana program development concepts and advanced testing techniques using Surfpool.
- Rust Program: Core banking logic in
programs/solbank/src/lib.rs - TypeScript Tests: Client-side testing in
tests/solbank.ts - Utility Scripts: Development helpers in
scripts/
Surfpool provides a local Solana-compatible network with advanced testing features:
surfpool startThis creates a local network that mimics mainnet behavior, including automatic program deployment.
Set Account Balance:
await connection._rpcRequest('surfnet_setAccount', [
accountPubkey.toString(),
{
lamports: 1000000000,
data: Buffer.from(accountData).toString('hex'),
owner: programId.toString(),
executable: false,
},
]);See scripts/setup-usdt-account.ts for token account creation example
Set Token Account Balance:
await connection._rpcRequest('surfnet_setTokenAccount', [
ownerPubkey.toString(),
mintPubkey.toString(),
{ amount: "1000000" },
TOKEN_PROGRAM_ID.toString(),
]);See scripts/setup-usdt-balance.ts for implementation
Advance to Specific Slot:
await connection._rpcRequest('surfnet_timeTravel', [{
absoluteSlot: 250000000
}]);Pause/Resume Block Production:
await connection._rpcRequest('surfnet_pauseClock', []);
await connection._rpcRequest('surfnet_resumeClock', []);-
Local Development:
- Start Surfpool for realistic testing
- Use scripts for account setup and token operations
- Test cooldown mechanisms with time travel
-
Account Management:
- PDAs for program-controlled accounts
- Associated Token Accounts for user tokens
- Proper rent exemption handling
-
Security Testing:
- Test withdrawal restrictions
- Verify authority checks
- Simulate various account states
- Slot-Based Logic: Use
Clock::get()for time-dependent operations - PDA Derivation: Secure account creation with program-derived addresses
- Cross-Program Calls: SPL Token program integration
- Error Handling: Custom error codes for user-friendly messages
- Always test with Surfpool for production-like conditions
- Use cheatcodes for setting up complex test scenarios
- Implement time-based restrictions using slots rather than timestamps
- Validate all account ownership and authorities
- Test edge cases with account manipulation tools
Here's an example of how to call the "call" action with "svm::process_instructions" using Pinocchio:
action "call" "svm::process_instructions" {
signers = [signer.deployer]
instruction {
program_id = "E4Ewh6dst6ZDW1jPpMCSbvTCTwYapuUyTQaFj3vQGgUY"
data ="0x0c05000000000000"
account {
public_key = signer.deployer.address
is_writable = true
is_signer = true
}
account {
public_key = "Andy1111111111111111111111111111111111111111"
is_writable = true
}
account {
public_key = svm::system_program_id()
}
}
}
This example demonstrates how to interact with SVM (Solana Virtual Machine) instructions using Pinocchio's action system.
For more information about the process_instructions action, see the Surfpool documentation.
This functionality was introduced in PR #295 for the txtx project.
MIT