Docs

resource_accounts

Autonomous contracts and resource accounts

Resource accounts are special autonomous accounts on Aptos that have no private key and can only be controlled by smart contracts. They enable developers to create stable, deterministic addresses for protocols while maintaining programmatic control over account operations, making them essential for DeFi protocols, DAOs, and autonomous systems.

Overview

Resource accounts solve the problem of protocol-controlled accounts that need stable addresses but shouldn't have private keys that could be lost or compromised. They are created with deterministic addresses derived from a source account and seed, allowing protocols to own assets, publish modules, and execute operations entirely through smart contract logic.

Creating Resource Accounts

move
module 0x1::protocol {
    use std::signer;
    use aptos_framework::resource_account;
    use aptos_framework::account;

    struct ResourceAccountCap has key {
        cap: account::SignerCapability
    }

    // Create a resource account during protocol initialization
    public entry fun initialize(deployer: &signer, seed: vector<u8>) {
        // Create resource account with deterministic address
        let (resource_signer, signer_cap) = account::create_resource_account(
            deployer,
            seed
        );

        // Store capability to use resource account later
        move_to(deployer, ResourceAccountCap { cap: signer_cap });

        // Resource account can now hold assets, publish modules
        // Address is deterministic: derived from deployer + seed
    }

    // Use resource account for protocol operations
    public entry fun protocol_transfer(
        amount: u64,
        recipient: address
    ) acquires ResourceAccountCap {
        let cap = borrow_global<ResourceAccountCap>(@deployer);
        let resource_signer = account::create_signer_with_capability(&cap.cap);

        // Resource account executes transfer
        coin::transfer<AptosCoin>(&resource_signer, recipient, amount);
    }
}

Deterministic Addresses

Resource account addresses are deterministically computed:

move
// Address formula: hash(source_address, seed, 0xFF)
let resource_addr = account::create_resource_address(&source_address, seed);

This enables:

  • Predictable protocol addresses before deployment
  • Consistent addresses across different networks
  • Easy verification of protocol authenticity

Real-World Use Cases

  1. DeFi Protocols: Create liquidity pools, vaults, and treasury accounts that are controlled by protocol logic rather than private keys, eliminating single points of failure.

  2. DAO Treasuries: Establish autonomous treasuries where funds can only be moved through governance proposals and smart contract execution.

  3. Escrow Services: Build trustless escrow systems where locked assets are held by resource accounts with programmatic release conditions.

  4. Protocol Upgrades: Deploy protocol modules to resource accounts, enabling controlled upgrade paths through governance rather than admin keys.

  5. Cross-Chain Bridges: Operate bridge accounts that custody locked assets with release controlled by multi-sig validation logic.

  6. Automated Market Makers: Run AMM contracts where liquidity provider funds are held in resource accounts managed by protocol mathematics.

Best Practices

Secure Signer Capabilities: Store SignerCapability in a protected resource with appropriate access controls. Never expose it directly.

Use Meaningful Seeds: Choose descriptive seeds that indicate the resource account's purpose (e.g., b"liquidity_pool_v2").

Implement Access Control: Add authorization logic to functions that use resource account capabilities.

Test Address Generation: Verify resource account addresses are computed correctly before mainnet deployment.

Document Ownership: Clearly document which modules control which resource accounts for auditing and verification.

Capability Rotation: Consider patterns for safely transferring or revoking resource account control if needed.

Avoid Seed Collisions: Use unique seeds to prevent accidentally creating multiple resource accounts at the same address.

Advanced Patterns

Multi-Tier Resource Accounts

move
// Create hierarchy of resource accounts
public fun create_protocol_structure(creator: &signer) {
    let (treasury_signer, treasury_cap) = account::create_resource_account(
        creator,
        b"treasury"
    );

    let (rewards_signer, rewards_cap) = account::create_resource_account(
        creator,
        b"rewards"
    );

    let (insurance_signer, insurance_cap) = account::create_resource_account(
        creator,
        b"insurance"
    );

    // Store capabilities for different protocol functions
}

Controlled Capability Distribution

move
struct GovernanceCap has key {
    treasury_cap: account::SignerCapability,
    withdraw_limit: u64,
    last_withdraw: u64
}

public fun governed_withdraw(
    amount: u64,
    recipient: address
) acquires GovernanceCap {
    let gov = borrow_global_mut<GovernanceCap>(@governance);

    // Enforce time-locks and limits
    assert!(amount <= gov.withdraw_limit, EEXCEEDS_LIMIT);

    let resource_signer = account::create_signer_with_capability(&gov.treasury_cap);
    coin::transfer(&resource_signer, recipient, amount);

    gov.last_withdraw = timestamp::now_seconds();
}

Security Considerations

Capability Storage: The SignerCapability is extremely powerful - treat it like a master private key and protect it appropriately.

Access Control: Implement robust access control for any function that uses resource account capabilities.

Upgrade Safety: If resource accounts publish modules, carefully manage upgrade policies to prevent malicious changes.

Resource Exhaustion: Resource accounts need gas for operations, so ensure they maintain sufficient APT balances.

Deterministic Generation: Understand that anyone can compute your resource account address from the source and seed.

Querying Resource Accounts

move
#[view]
public fun get_resource_account_address(source: address, seed: vector<u8>): address {
    account::create_resource_address(&source, seed)
}

#[view]
public fun get_protocol_treasury(): address {
    account::create_resource_address(&@deployer, b"treasury")
}

Comparison with Regular Accounts

Resource Accounts:

  • No private key
  • Controlled by smart contracts
  • Deterministic addresses
  • Perfect for protocols

Regular Accounts:

  • Have private keys
  • User-controlled
  • Random addresses
  • For individual users