Skip to main content

Overview

The Global Configuration manages program-wide settings including the admin address, fee wallet, protocol fees, and global pause state. These instructions are primarily admin-only operations.

Account Structure

pub struct GlobalConfig {
    pub admin: Pubkey,                      // Program administrator
    pub fee_wallet: Pubkey,                 // Protocol fee collection wallet
    pub protocol_fees_bps: u16,             // Protocol fees (0-9900 BPS)
    pub paused: bool,                       // Program-wide pause flag
    pub pending_new_admin: Option<Pubkey>,  // Two-step admin transfer
    pub bump: u8,                           // PDA bump seed
}
PDA Seeds: ["global_config"]

Instructions

init_global_config

Initialize the program’s global configuration. This is a one-time operation called immediately after deployment.
pub fn init_global_config(
    ctx: Context<InitGlobalConfig>,
    fee_wallet: Pubkey,
    protocol_fees_bps: u16,
) -> Result<()>

Parameters

ParameterTypeDescription
fee_walletPubkeyWallet to receive claimed protocol fees
protocol_fees_bpsu16Protocol fee in basis points (0-9900)

Required Accounts

#[derive(Accounts)]
pub struct InitGlobalConfig<'info> {
    #[account(
        init,
        payer = admin,
        space = 8 + GlobalConfig::INIT_SPACE,
        seeds = [GLOBAL_CONFIG_PREFIX],
        bump,
    )]
    pub global_config: Account<'info, GlobalConfig>,

    #[account(mut)]
    pub admin: Signer<'info>,

    pub system_program: Program<'info, System>,
}

Behavior

  • Creates the GlobalConfig PDA account
  • Sets caller as admin
  • Sets program to paused by default
  • Validates protocol_fees_bps is within range

Access Control

Access: Permissionless (one-time only) The first caller becomes the admin. Subsequent calls will fail as the account already exists.

Example

await program.methods
  .initGlobalConfig(
    feeWallet,
    100  // 1% protocol fee (100 BPS)
  )
  .accounts({
    globalConfig,
    admin: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .rpc();

update_global_config

Update program-wide configuration settings.
pub fn update_global_config(
    ctx: Context<UpdateGlobalConfig>,
    fee_wallet: Option<Pubkey>,
    paused: Option<bool>,
    protocol_fees_bps: Option<u16>,
) -> Result<()>

Parameters

ParameterTypeDescription
fee_walletOption<Pubkey>New fee wallet (None to keep current)
pausedOption<bool>New pause state (None to keep current)
protocol_fees_bpsOption<u16>New protocol fee (None to keep current)

Required Accounts

#[derive(Accounts)]
pub struct UpdateGlobalConfig<'info> {
    #[account(
        mut,
        seeds = [GLOBAL_CONFIG_PREFIX],
        bump = global_config.bump,
        has_one = admin,
    )]
    pub global_config: Account<'info, GlobalConfig>,

    pub admin: Signer<'info>,
}

Behavior

  • Updates only the fields provided (non-None values)
  • Validates protocol_fees_bps if provided

Access Control

Access: Admin only

Example

// Unpause the program and update fees
await program.methods
  .updateGlobalConfig(
    null,    // Keep current fee wallet
    false,   // Unpause
    50       // 0.5% protocol fee (50 BPS)
  )
  .accounts({
    globalConfig,
    admin: adminWallet.publicKey,
  })
  .rpc();

set_new_admin

Propose a new admin address (first step of two-step transfer).
pub fn set_new_admin(
    ctx: Context<SetNewAdmin>,
    new_admin: Pubkey,
) -> Result<()>

Parameters

ParameterTypeDescription
new_adminPubkeyProposed new admin address

Required Accounts

#[derive(Accounts)]
pub struct SetNewAdmin<'info> {
    #[account(
        mut,
        seeds = [GLOBAL_CONFIG_PREFIX],
        bump = global_config.bump,
    )]
    pub global_config: Account<'info, GlobalConfig>,

    #[account(
        constraint = global_config.admin == admin.key()
            @ MultiliquidError::Unauthorized
    )]
    pub admin: Signer<'info>,
}

Behavior

  • Sets pending_new_admin to proposed address
  • Current admin remains in control
  • Can be called multiple times to change proposed admin

Access Control

Access: Current admin only

Example

await program.methods
  .setNewAdmin(newAdminPubkey)
  .accounts({
    globalConfig,
    admin: currentAdmin.publicKey,
  })
  .rpc();

confirm_new_admin

Accept the admin role (second step of two-step transfer).
pub fn confirm_new_admin(
    ctx: Context<ConfirmNewAdmin>,
) -> Result<()>

Required Accounts

#[derive(Accounts)]
pub struct ConfirmNewAdmin<'info> {
    #[account(
        mut,
        seeds = [GLOBAL_CONFIG_PREFIX],
        bump = global_config.bump,
    )]
    pub global_config: Account<'info, GlobalConfig>,

    #[account(
        constraint = global_config.pending_new_admin == Some(new_admin.key())
            @ MultiliquidError::Unauthorized
    )]
    pub new_admin: Signer<'info>,
}

Behavior

  • Verifies caller matches pending_new_admin
  • Updates admin to new address
  • Clears pending_new_admin

Access Control

Access: Pending new admin only

Example

await program.methods
  .confirmNewAdmin()
  .accounts({
    globalConfig,
    newAdmin: newAdminWallet.publicKey,
  })
  .rpc();

claim_fees

Claim accumulated protocol fees from a fee vault.
pub fn claim_fees(
    ctx: Context<ClaimFees>,
    fee_wallet: Pubkey,
) -> Result<()>

Parameters

ParameterTypeDescription
fee_walletPubkeyThe fee wallet address configured in GlobalConfig

Required Accounts

#[derive(Accounts)]
#[instruction(fee_wallet: Pubkey)]
pub struct ClaimFees<'info> {
    pub stable_coin_mint_address: InterfaceAccount<'info, Mint>,

    #[account(
        mut,
        token::mint = stable_coin_mint_address,
        token::authority = fee_wallet,
        token::token_program = token_program,
    )]
    pub fee_wallet_token_account: InterfaceAccount<'info, TokenAccount>,

    /// CHECK: checked by seeds
    #[account(seeds = [PROGRAM_AUTHORITY_PREFIX], bump)]
    pub program_authority: UncheckedAccount<'info>,

    #[account(
        mut,
        seeds = [FEE_VAULT_PREFIX, stable_coin_mint_address.key().as_ref()],
        bump,
        token::mint = stable_coin_mint_address,
        token::authority = program_authority,
        token::token_program = token_program,
    )]
    pub fee_token_account: InterfaceAccount<'info, TokenAccount>,

    #[account(
        seeds = [GLOBAL_CONFIG_PREFIX],
        bump = global_config.bump,
        has_one = fee_wallet,
    )]
    pub global_config: Account<'info, GlobalConfig>,

    pub token_program: Interface<'info, TokenInterface>,
}

Behavior

  • Requires program to be unpaused
  • Transfers all tokens from fee vault to fee wallet
  • Fee vault remains open for future fee collection
  • Anyone can call, but fees always go to configured fee_wallet

Access Control

Access: Permissionless
While anyone can trigger fee claims, the fees are always sent to the fee_wallet configured in GlobalConfig, not to the caller.

Example

await program.methods
  .claimFees(feeWallet)
  .accounts({
    stableCoinMintAddress: stableMint,
    feeWalletTokenAccount,
    programAuthority,
    feeTokenAccount,
    globalConfig,
    tokenProgram: TOKEN_PROGRAM_ID,
  })
  .rpc();

Error Codes

ErrorDescription
UnauthorizedCaller is not admin or pending admin
OutOfRangeProtocol fees exceed 9900 BPS
ProgramPausedOperation blocked by global pause

Next: Asset Configuration

Learn about per-token NAV configuration and asset management