Skip to content

Repository files navigation

Cron Wallet Program

A Solana smart wallet program with social recovery features, built using the Anchor framework. This program enables users to create secure smart accounts protected by guardians who can help recover access if the owner loses their keys.

Table of Contents

Overview

Cron Wallet is a smart wallet program that provides:

  • Smart Account Management: Create and manage PDA-based accounts that hold SOL and SPL tokens
  • Social Recovery: Designate trusted guardians who can help recover your account
  • Multi-signature Recovery: Set approval thresholds to prevent single-guardian attacks
  • Payment Operations: Send both SOL and SPL tokens from your smart wallet

Program ID: 4TUaUyAqjbZr4vPCm8bq2Qojy7pswQaWJgoA6AB5m6uW

Features

Core Functionality

  1. Smart Account Initialization

    • Create a smart wallet with a unique 16-byte identifier
    • Automatically initialize an associated guardian registry
    • Set yourself as the initial owner
  2. Guardian Management

    • Add up to 4 guardians to protect your account
    • Set approval threshold (minimum guardians needed for recovery)
    • Only the owner can add guardians or change the threshold
  3. Social Recovery

    • Any guardian can propose a recovery to a new owner
    • Multiple guardians must approve (based on threshold)
    • Anyone can execute once the threshold is met
    • Owner can cancel recovery proposals at any time
  4. Payment Operations

    • Send SOL from your smart account
    • Transfer SPL tokens to other accounts
    • Payments blocked when recovery is active (account locked)

Architecture

Account Structure

The program uses three main account types:

1. SmartAccount

pub struct SmartAccount {
    pub smart_account_id: [u8; 16],      // Unique identifier
    pub guardian_registry: Pubkey,        // Associated guardian registry
    pub owner: Pubkey,                    // Current owner
    pub is_locked: bool,                  // True during recovery
    pub bump: u8,                         // PDA bump
}
  • PDA Seeds: ["smart_account", smart_account_id]
  • Size: 90 bytes

2. GuardianRegistry

pub struct GuardianRegistry {
    pub smart_account: Pubkey,            // Associated smart account
    pub guardians: Vec<Pubkey>,           // List of guardians (max 4)
    pub threshold: u8,                    // Required approvals
    pub bump: u8,                         // PDA bump
}
  • PDA Seeds: ["guardian_registry", smart_account_pubkey]
  • Size: 170 bytes (supports up to 4 guardians)

3. RecoveryProposal

pub struct RecoveryProposal {
    pub smart_account: Pubkey,            // Smart account being recovered
    pub new_owner: Pubkey,                // Proposed new owner
    pub approvals: Vec<Pubkey>,           // Guardians who approved
    pub proposed_at: i64,                 // Timestamp
    pub bump: u8,                         // PDA bump
}
  • PDA Seeds: ["recovery_proposal", smart_account_pubkey]
  • Size: 209 bytes

State Diagram

┌─────────────────────────────────────────────────────────────┐
│                    SMART ACCOUNT LIFECYCLE                   │
└─────────────────────────────────────────────────────────────┘

[Uninitialized]
      │
      │ init_smart_account()
      ↓
[Active - is_locked: false] ←─────────────────────┐
      │                                            │
      │ • pay_with_sol()                          │
      │ • pay_with_spl()                          │
      │ • add_guardian()           cancel_recovery()
      │ • set_threshold()                         │
      │                                            │
      │ propose_recovery()                         │
      ↓                                            │
[Locked - is_locked: true] ─────────────────────────┘
      │                       ↑
      │ approve_recovery()    │ (by owner)
      │ (by guardians)        │
      ↓                       │
[Threshold Met]              │
      │                       │
      │ execute_recovery()    │
      ↓                       │
[Ownership Transferred] ─────┘

Program Flow

1. Initial Setup Flow

Step 1: Initialize Smart Account
├─ Owner signs transaction
├─ Creates SmartAccount PDA
├─ Creates GuardianRegistry PDA
└─ Sets owner and initializes empty guardian list

Step 2: Add Guardians (repeat 1-4 times)
├─ Owner signs transaction
├─ Adds guardian public key to registry
└─ Maximum 4 guardians allowed

Step 3: Set Recovery Threshold
├─ Owner signs transaction
├─ Sets number of approvals needed (1-4)
└─ Must be ≤ total number of guardians

2. Normal Operation Flow

Payment with SOL:
├─ Owner signs transaction
├─ Checks account is not locked
├─ Verifies sufficient balance
└─ Transfers lamports directly from PDA

Payment with SPL Tokens:
├─ Owner signs transaction
├─ Checks account is not locked
├─ Uses PDA as token account authority
└─ Executes CPI to token program

3. Recovery Flow

Scenario: Owner loses access to their keys

Step 1: Propose Recovery
├─ ANY guardian initiates proposal
├─ Specifies new owner address
├─ Locks the smart account (is_locked = true)
└─ Creates RecoveryProposal PDA

Step 2: Gather Approvals
├─ Other guardians approve the proposal
├─ Each guardian can only approve once
└─ Approvals tracked in proposal.approvals[]

Step 3: Execute Recovery
├─ Anyone can execute when threshold met
├─ Ownership transferred to new owner
├─ Account unlocked (is_locked = false)
└─ RecoveryProposal PDA closed

Alternative: Cancel Recovery
├─ Original owner can cancel at any time
├─ Account unlocked
└─ RecoveryProposal PDA closed

Setup & Installation

Prerequisites

  • Rust: 1.70.0 or later
  • Solana CLI: 1.18.0 or later
  • Anchor CLI: 0.31.1 or later
  • Node.js: 18.0.0 or later
  • Yarn: 1.22.0 or later

Installation

  1. Clone the repository
git clone <repository-url>
cd cron-wallet-program
  1. Install dependencies
# Install Rust dependencies
cargo build

# Install Node.js dependencies
yarn install
  1. Configure Anchor
# Generate a new keypair (if needed)
solana-keygen new

# Update Anchor.toml with your wallet path
# wallet = "~/.config/solana/id.json"
  1. Build the program
anchor build

Deployment

Local Deployment (Localnet)

  1. Start local validator
solana-test-validator
  1. Deploy the program
anchor deploy
  1. Run tests
anchor test --skip-local-validator

Devnet Deployment

  1. Configure for Devnet
# Update Anchor.toml
[provider]
cluster = "devnet"

# Configure Solana CLI
solana config set --url devnet
  1. Airdrop SOL for deployment
solana airdrop 2
  1. Deploy
anchor deploy --provider.cluster devnet

Mainnet Deployment

  1. Configure for Mainnet
# Update Anchor.toml
[provider]
cluster = "mainnet"

# Configure Solana CLI
solana config set --url mainnet-beta
  1. Deploy (ensure you have sufficient SOL)
anchor deploy --provider.cluster mainnet-beta

Usage Guide

TypeScript SDK Integration

The program automatically generates TypeScript types after building. Here's how to use them:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { CronWalletProgram } from "../target/types/cron_wallet_program";
import { uuid } from "uuidv4";

// Initialize connection
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.CronWalletProgram as Program<CronWalletProgram>;

// 1. Initialize Smart Account
const smartAccountId = Buffer.from(uuid().replace(/-/g, ""), "hex");
const [smartAccountPDA] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from("smart_account"), smartAccountId],
  program.programId
);
const [guardianRegistryPDA] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from("guardian_registry"), smartAccountPDA.toBuffer()],
  program.programId
);

await program.methods
  .initSmartAccount(Array.from(smartAccountId))
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    authority: provider.wallet.publicKey,
    systemProgram: anchor.web3.SystemProgram.programId,
  })
  .rpc();

// 2. Add Guardian
const guardianPubkey = new anchor.web3.PublicKey("Guardian123...");
await program.methods
  .addGuardian()
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    newGuardian: guardianPubkey,
    owner: provider.wallet.publicKey,
  })
  .rpc();

// 3. Set Threshold
await program.methods
  .setThreshold(2) // Require 2 guardians to approve recovery
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    owner: provider.wallet.publicKey,
  })
  .rpc();

// 4. Pay with SOL
const recipient = new anchor.web3.PublicKey("Recipient123...");
await program.methods
  .payWithSol(new anchor.BN(1_000_000)) // Amount in lamports
  .accounts({
    smartAccount: smartAccountPDA,
    dstSolAccount: recipient,
    owner: provider.wallet.publicKey,
  })
  .rpc();

// 5. Propose Recovery (as guardian)
const newOwner = new anchor.web3.PublicKey("NewOwner123...");
const [recoveryProposalPDA] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from("recovery_proposal"), smartAccountPDA.toBuffer()],
  program.programId
);

await program.methods
  .proposeRecovery()
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    recoveryProposal: recoveryProposalPDA,
    newOwner: newOwner,
    guardian: guardianWallet.publicKey,
    systemProgram: anchor.web3.SystemProgram.programId,
  })
  .signers([guardianWallet])
  .rpc();

// 6. Approve Recovery (as other guardians)
await program.methods
  .approveRecovery()
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    recoveryProposal: recoveryProposalPDA,
    guardian: anotherGuardianWallet.publicKey,
  })
  .signers([anotherGuardianWallet])
  .rpc();

// 7. Execute Recovery (anyone can execute once threshold is met)
await program.methods
  .executeRecovery()
  .accounts({
    smartAccount: smartAccountPDA,
    guardianRegistry: guardianRegistryPDA,
    recoveryProposal: recoveryProposalPDA,
    executor: provider.wallet.publicKey,
  })
  .rpc();

// 8. Cancel Recovery (only original owner)
await program.methods
  .cancelRecovery()
  .accounts({
    smartAccount: smartAccountPDA,
    recoveryProposal: recoveryProposalPDA,
    owner: provider.wallet.publicKey,
  })
  .rpc();

CLI Example (using Solana CLI)

You can also interact with the program using the Solana CLI with the generated IDL:

# Get the program's IDL
anchor idl init <PROGRAM_ID> -f target/idl/cron_wallet_program.json

# Call instructions
solana program invoke <PROGRAM_ID> --data <BASE58_ENCODED_INSTRUCTION_DATA>

Testing

Run All Tests

# Build and test in one command
anchor test

# Or separately
anchor build
anchor test --skip-build

# Test on specific network
anchor test --skip-local-validator  # Uses existing validator

Test Structure

The test suite (tests/cron-wallet-program.ts) covers:

  • ✅ Smart account initialization
  • ✅ Guardian management (add/remove)
  • ✅ Threshold configuration
  • ✅ Payment operations (SOL and SPL)
  • ✅ Full recovery flow
  • ✅ Recovery cancellation
  • ✅ Edge cases and error conditions

Running Individual Tests

# Run with verbose output
yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts --grep "test_name"

Security Considerations

Smart Contract Security

  1. PDA Security

    • All accounts use Program Derived Addresses (PDAs)
    • Seeds are deterministic and collision-resistant
    • No user-controlled seeds in critical paths
  2. Access Control

    • Owner-only operations validated with has_one constraints
    • Guardian verification in recovery operations
    • Double-spend prevention via account locking
  3. Recovery Safety

    • Account locks during active recovery proposals
    • Threshold prevents single guardian attacks
    • Owner can always cancel malicious recovery attempts
    • No funds can leave while account is locked
  4. Arithmetic Safety

    • All arithmetic uses checked operations
    • Overflow/underflow protection
    • Balance verification before transfers

Best Practices

  1. Guardian Selection

    • Choose trustworthy individuals you know personally
    • Use diverse guardians (different geographies, relationships)
    • Consider using 3-4 guardians with threshold of 2-3
    • Regularly communicate with guardians
  2. Threshold Configuration

    • Too low: Single compromised guardian is dangerous
    • Too high: May be impossible to recover if guardians unavailable
    • Recommended: threshold = (num_guardians / 2) + 1
  3. Operational Security

    • Keep your owner keypair secure
    • Monitor your smart account for unauthorized recovery proposals
    • Set up notifications for guardian events
    • Test recovery process on devnet first
  4. Recovery Process

    • Always verify the new owner address multiple times
    • Use a secure channel to communicate with guardians
    • Consider time-delayed recovery (future enhancement)
    • Document your recovery plan

Known Limitations

  1. Maximum 4 Guardians: Current implementation limits to 4 guardians due to account size constraints
  2. No Time Lock: Recovery can execute immediately after threshold is met
  3. No Guardian Removal: Once added, guardians cannot be removed (only threshold can change)
  4. No Multi-Step Auth: Single signature transactions (no session keys or delegated signing)

Events

The program emits events for important state changes:

// Smart account created
pub struct SmartAccountInitialized {
    pub smart_account: Pubkey,
    pub smart_account_id: [u8; 16],
    pub guardian_registry: Pubkey,
    pub owner: Pubkey,
}

// Recovery proposed
pub struct RecoveryProposed {
    pub smart_account: Pubkey,
    pub recovery_proposal: Pubkey,
    pub new_owner: Pubkey,
    pub proposed_at: i64,
}

// Guardian approved recovery
pub struct RecoveryApproved {
    pub smart_account: Pubkey,
    pub recovery_proposal: Pubkey,
    pub guardian: Pubkey,
    pub approvals_count: u8,
}

// Recovery executed
pub struct RecoveryExecuted {
    pub smart_account: Pubkey,
    pub old_owner: Pubkey,
    pub new_owner: Pubkey,
}

// Recovery cancelled
pub struct RecoveryCancelled {
    pub smart_account: Pubkey,
    pub recovery_proposal: Pubkey,
}

// Token transfer
pub struct TokenTransfer {
    pub token_mint: Pubkey,
    pub amount: u64,
    pub sender: Pubkey,
    pub recipient: Pubkey,
}

Monitor these events to track account activity and security events.

Error Codes

Code Error Description
6000 InvalidOwner Only the owner can execute this operation
6001 InsufficientFunds Insufficient funds for transfer
6002 Overflow Arithmetic overflow
6003 AccountLocked Smart account is locked during recovery
6004 InvalidGuardian Only a guardian can execute this operation
6005 RecoveryProposalExists Recovery proposal already exists
6006 NoActiveRecoveryProposal No active recovery proposal
6007 GuardianAlreadyApproved Guardian has already approved this recovery
6008 ThresholdNotMet Threshold not met for recovery execution
6009 InvalidRecoveryProposal Recovery proposal does not match smart account

Future Enhancements

Potential improvements for future versions:

  • Time-locked Recovery: Add delay between proposal and execution
  • Guardian Rotation: Allow adding/removing guardians
  • Session Keys: Enable temporary spending limits without full ownership
  • Multi-signature Payments: Require guardian approval for large transfers
  • Recovery History: Track past recovery attempts
  • Emergency Freeze: Allow guardians to freeze account in case of compromise
  • Scheduled Payments: Enable recurring or scheduled transactions
  • Multi-owner Accounts: Support shared ownership models
  • Integration with DeFi: Direct integration with lending/staking protocols
  • Mobile SDK: Native mobile integration

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Guidelines

  • Write comprehensive tests for new features
  • Update documentation for any API changes
  • Follow Rust and Anchor best practices
  • Ensure all tests pass before submitting PR
  • Add events for significant state changes

License

ISC

Support

For questions, issues, or feature requests:

Acknowledgments

Built with:


⚠️ IMPORTANT: This is experimental software. Use at your own risk. Always test thoroughly on devnet before deploying to mainnet. Never risk funds you cannot afford to lose.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages