- deBridge Solana SDK Development Guide
- A comprehensive guide for building Solana programs with the deBridge Solana SDK - enabling decentralized cross-chain transfers of arbitrary messages and value between blockchains.
- Overview
- deBridge is a cross-chain infrastructure protocol enabling:
- Cross-Chain Transfers
-
- Bridge assets between Solana and 20+ EVM chains
- Message Passing
-
- Send arbitrary messages across blockchains
- External Calls
-
- Execute smart contract calls on destination chains
- Sub-Second Settlement
-
- ~2 second median settlement time
- Capital Efficiency
- Intent-based architecture with 4bps lowest spreads Key Features 26+ security audits (Halborn, Zokyo, Ackee Blockchain) $200K bug bounty on Immunefi 100% uptime since launch Zero security incidents Quick Start Installation Add the SDK to your Anchor/Solana program: cargo add --git ssh://git@github.com/debridge-finance/debridge-solana-sdk.git debridge-solana-sdk Or add to Cargo.toml : [ dependencies ] debridge-solana-sdk = { git = "ssh://git@github.com/debridge-finance/debridge-solana-sdk.git" } Basic Setup (Anchor) use anchor_lang :: prelude :: * ; use debridge_solana_sdk :: prelude :: * ; declare_id! ( "YourProgramId11111111111111111111111111111" ) ;
[program]
pub mod my_bridge_program { use super :: * ; pub fn send_cross_chain ( ctx : Context < SendCrossChain
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, amount : u64 , ) -> Result < ( )
{ // Invoke deBridge send debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : false , // Use native SOL for fees amount , submission_params : None , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } }
[derive(Accounts)]
pub struct SendCrossChain < 'info
{
[account(mut)]
pub sender : Signer < 'info
, // Additional accounts passed via remaining_accounts } Core Concepts 1. Chain IDs deBridge uses 32-byte chain identifiers for all supported networks: use debridge_solana_sdk :: chain_ids :: * ; // Solana let solana = SOLANA_CHAIN_ID ; // Solana mainnet // EVM Chains let ethereum = ETHEREUM_CHAIN_ID ; // Chain ID: 1 let polygon = POLYGON_CHAIN_ID ; // Chain ID: 137 let bnb = BNB_CHAIN_CHAIN_ID ; // Chain ID: 56 let arbitrum = ARBITRUM_CHAIN_ID ; // Chain ID: 42161 let avalanche = AVALANCHE_CHAIN_ID ; // Chain ID: 43114 let fantom = FANTOM_CHAIN_ID ; // Chain ID: 250 let heco = HECO_CHAIN_ID ; // Chain ID: 128 2. Program IDs use debridge_solana_sdk :: { DEBRIDGE_ID , SETTINGS_ID } ; // Main deBridge program for sending/claiming let debridge_program = DEBRIDGE_ID ; // Settings and confirmation storage program let settings_program = SETTINGS_ID ; 3. Fee Structure deBridge supports multiple fee payment methods: // Native Fee (SOL) is_use_asset_fee : false // Pay fees in SOL // Asset Fee is_use_asset_fee : true // Pay fees in the bridged token // Fee Constants const BPS_DENOMINATOR : u64 = 10000 ; // Basis points divisor 4. Flags Control transfer behavior with flags: use debridge_solana_sdk :: flags :: * ; // Available flags (bit positions) const UNWRAP_ETH : u8 = 0 ; // Unwrap to native ETH on destination const REVERT_IF_EXTERNAL_FAIL : u8 = 1 ; // Revert if external call fails const PROXY_WITH_SENDER : u8 = 2 ; // Include sender in proxy call const SEND_HASHED_DATA : u8 = 3 ; // Send data as hash const DIRECT_WALLET_FLOW : u8 = 31 ; // Use direct wallet flow // Setting flags on submission params let mut flags = [ 0u8 ; 32 ] ; flags . set_reserved_flag ( UNWRAP_ETH ) ; flags . set_reserved_flag ( REVERT_IF_EXTERNAL_FAIL ) ; Sending Cross-Chain Transfers Basic Token Transfer use debridge_solana_sdk :: prelude :: * ; pub fn send_tokens ( ctx : Context < SendTokens
, amount : u64 , ) -> Result < ( )
{ debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id : chain_ids :: ETHEREUM_CHAIN_ID , receiver : recipient_eth_address . to_vec ( ) , is_use_asset_fee : false , amount , submission_params : None , referral_code : Some ( 12345 ) , // Optional referral } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Transfer with Fixed Native Fee pub fn send_with_native_fee ( ctx : Context < Send
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, amount : u64 , ) -> Result < ( )
{ // Get the fixed fee for the target chain let fee = debridge_sending :: get_chain_native_fix_fee ( & target_chain_id , ctx . remaining_accounts , ) ? ; debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : false , amount , submission_params : None , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Transfer with Asset Fee pub fn send_with_asset_fee ( ctx : Context < Send
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, amount : u64 , ) -> Result < ( )
{ // Check if asset fee is available for this chain let is_available = debridge_sending :: is_asset_fee_available ( & target_chain_id , ctx . remaining_accounts , ) ? ; if ! is_available { return Err ( error! ( ErrorCode :: AssetFeeNotAvailable ) ) ; } debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : true , // Use asset for fees amount , submission_params : None , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Transfer with Exact Amount pub fn send_exact_amount ( ctx : Context < Send
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, exact_receive_amount : u64 , ) -> Result < ( )
{ // Calculate total amount including fees let total_with_fees = debridge_sending :: add_all_fees ( exact_receive_amount , & target_chain_id , ctx . remaining_accounts , ) ? ; debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : true , amount : total_with_fees , submission_params : None , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Transfer from PDA (Signed) pub fn send_from_pda ( ctx : Context < SendFromPda
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, amount : u64 , pda_seeds : Vec < Vec < u8
, ) -> Result < ( )
{ // Use signed variant for PDA-owned tokens debridge_sending :: invoke_debridge_send_signed ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : false , amount , submission_params : None , referral_code : None , } , ctx . remaining_accounts , & pda_seeds , ) ? ; Ok ( ( ) ) } Message Passing Send messages without token transfers: use debridge_solana_sdk :: prelude :: * ; pub fn send_message ( ctx : Context < SendMessage
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, message_data : Vec < u8
, ) -> Result < ( )
{ // Create submission params with message let submission_params = debridge_sending :: SendSubmissionParamsInput { execution_fee : 0 , flags : [ 0u8 ; 32 ] , fallback_address : receiver . clone ( ) , external_call_shortcut : compute_keccak256 ( & message_data ) , } ; // Send message (zero amount) debridge_sending :: invoke_send_message ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : false , amount : 0 , // No token transfer submission_params : Some ( submission_params ) , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } External Calls Execute smart contract calls on destination chains: Initialize External Call Buffer pub fn init_external_call ( ctx : Context < InitExternalCall
, target_chain_id : [ u8 ; 32 ] , external_call_data : Vec < u8
, ) -> Result < ( )
{ let shortcut = compute_keccak256 ( & external_call_data ) ; debridge_sending :: invoke_init_external_call ( debridge_sending :: InitExternalCallIx { external_call_len : external_call_data . len ( ) as u32 , chain_id : target_chain_id , external_call_shortcut : shortcut , external_call : external_call_data , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Send with External Call pub fn send_with_external_call ( ctx : Context < SendWithExternalCall
, target_chain_id : [ u8 ; 32 ] , receiver : Vec < u8
, // Target contract address amount : u64 , external_call_data : Vec < u8
, execution_fee : u64 , // Fee for executor on destination ) -> Result < ( )
{ let shortcut = compute_keccak256 ( & external_call_data ) ; // Set flags for external call behavior let mut flags = [ 0u8 ; 32 ] ; flags . set_reserved_flag ( flags :: REVERT_IF_EXTERNAL_FAIL ) ; let submission_params = debridge_sending :: SendSubmissionParamsInput { execution_fee , flags , fallback_address : ctx . accounts . fallback . key ( ) . to_bytes ( ) . to_vec ( ) , external_call_shortcut : shortcut , } ; debridge_sending :: invoke_debridge_send ( debridge_sending :: SendIx { target_chain_id , receiver , is_use_asset_fee : false , amount , submission_params : Some ( submission_params ) , referral_code : None , } , ctx . remaining_accounts , ) ? ; Ok ( ( ) ) } Claim Verification Verify claims on the receiving side: Validate Incoming Claims use debridge_solana_sdk :: check_claiming :: * ; pub fn receive_tokens ( ctx : Context < ReceiveTokens
) -> Result < ( )
{ // Get and validate the parent claim instruction let claim_ix = ValidatedExecuteExtCallIx :: try_from_current_ix ( ) ? ; // Validate submission details let validation = SubmissionAccountValidation { receiver_validation : Some ( ctx . accounts . receiver . key ( ) ) , token_mint_validation : Some ( ctx . accounts . token_mint . key ( ) ) , source_chain_id_validation : Some ( chain_ids :: ETHEREUM_CHAIN_ID ) , .. Default :: default ( ) } ; claim_ix . validate_submission_account ( & ctx . accounts . submission_account , & validation , ) ? ; // Proceed with claim logic Ok ( ( ) ) } Get Submission Key pub fn get_claim_info ( ctx : Context < ClaimInfo
) -> Result < Pubkey
{ let claim_ix = ValidatedExecuteExtCallIx :: try_from_current_ix ( ) ? ; let submission_key = claim_ix . get_submission_key ( ) ? ; Ok ( submission_key ) } Fee Queries Get Transfer Fees // Get base transfer fee (in BPS) let transfer_fee = debridge_sending :: get_transfer_fee ( ctx . remaining_accounts , ) ? ; // Get transfer fee for specific chain let chain_fee = debridge_sending :: get_transfer_fee_for_chain ( & target_chain_id , ctx . remaining_accounts , ) ? ; // Get default native fix fee let default_fee = debridge_sending :: get_default_native_fix_fee ( ctx . remaining_accounts , ) ? ; // Get chain-specific native fix fee let native_fee = debridge_sending :: get_chain_native_fix_fee ( & target_chain_id , ctx . remaining_accounts , ) ? ; // Get asset fix fee for chain let asset_fee = debridge_sending :: try_get_chain_asset_fix_fee ( & target_chain_id , ctx . remaining_accounts , ) ? ; Calculate Total Amount with Fees // Add transfer fee to amount let with_transfer_fee = debridge_sending :: add_transfer_fee ( amount , ctx . remaining_accounts , ) ? ; // Add all fees (transfer + execution + asset fees) let total_amount = debridge_sending :: add_all_fees ( amount , & target_chain_id , ctx . remaining_accounts , ) ? ; Chain Support Queries // Check if chain is supported let is_supported = debridge_sending :: is_chain_supported ( & target_chain_id , ctx . remaining_accounts , ) ? ; // Get chain support info let chain_info = debridge_sending :: get_chain_support_info ( & target_chain_id , ctx . remaining_accounts , ) ? ; // Check if asset fee is available let asset_fee_available = debridge_sending :: is_asset_fee_available ( & target_chain_id , ctx . remaining_accounts , ) ? ; PDA Derivation Bridge Account use debridge_solana_sdk :: keys :: * ; // Find bridge PDA for a token mint let ( bridge_address , bump ) = BridgePubkey :: find_bridge_address ( & token_mint ) ; // Create with known bump let bridge_address = BridgePubkey :: create_bridge_address ( & token_mint , bump ) ? ; Chain Support Info // Find chain support info PDA let ( chain_support_info , bump ) = ChainSupportInfoPubkey :: find_chain_support_info_address ( & target_chain_id , ) ; Asset Fee Info // Find asset fee info PDA let ( asset_fee_info , bump ) = AssetFeeInfoPubkey :: find_asset_fee_info_address ( & bridge_pubkey , & target_chain_id , ) ; // Get default bridge fee address let default_fee = AssetFeeInfoPubkey :: default_bridge_fee_address ( ) ; External Call Storage // Find external call storage PDA let ( storage , bump ) = ExternalCallStoragePubkey :: find_external_call_storage_address ( & shortcut , & owner , ) ; // Find external call meta PDA let ( meta , bump ) = ExternalCallMetaPubkey :: find_external_call_meta_address ( & storage_account , ) ; Required Accounts The SDK requires specific accounts passed via remaining_accounts . The account order is important: Index Account Signer Writable Description 0 Bridge No Yes Bridge account for token 1 Token Mint No No SPL Token mint 2 Staking Wallet No Yes Staking rewards wallet 3 Mint Authority No No Token mint authority 4 Chain Support Info No No Target chain config 5 Settings Program No No deBridge settings 6 SPL Token Program No No Token program 7 State No No Protocol state 8 deBridge Program No No Main deBridge program ... Additional accounts - - Varies by operation TypeScript Client Integration Setup import { Connection , Keypair , PublicKey , Transaction } from '@solana/web3.js' ; import { Program , AnchorProvider , Wallet } from '@coral-xyz/anchor' ; const connection = new Connection ( 'https://api.mainnet-beta.solana.com' ) ; const wallet = new Wallet ( keypair ) ; const provider = new AnchorProvider ( connection , wallet , { } ) ; // deBridge Program IDs const DEBRIDGE_PROGRAM_ID = new PublicKey ( 'DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh' ) ; const SETTINGS_PROGRAM_ID = new PublicKey ( 'DeSetTwWhjZq6Pz9Kfdo1KoS5NqtsM6G8ERbX4SSCSft' ) ; Build Send Transaction import { TOKEN_PROGRAM_ID , getAssociatedTokenAddress } from '@solana/spl-token' ; async function buildSendTransaction ( tokenMint : PublicKey , amount : bigint , targetChainId : Uint8Array , receiver : Uint8Array , ) : Promise < Transaction
{ // Derive required PDAs const [ bridge ] = PublicKey . findProgramAddressSync ( [ Buffer . from ( 'BRIDGE' ) , tokenMint . toBuffer ( ) ] , DEBRIDGE_PROGRAM_ID ) ; const [ chainSupportInfo ] = PublicKey . findProgramAddressSync ( [ Buffer . from ( 'CHAIN_SUPPORT_INFO' ) , targetChainId ] , SETTINGS_PROGRAM_ID ) ; const [ state ] = PublicKey . findProgramAddressSync ( [ Buffer . from ( 'STATE' ) ] , DEBRIDGE_PROGRAM_ID ) ; // Build instruction with remaining accounts const instruction = await program . methods . sendViaDebridge ( Array . from ( targetChainId ) , Array . from ( receiver ) , new BN ( amount . toString ( ) ) , ) . remainingAccounts ( [ { pubkey : bridge , isSigner : false , isWritable : true } , { pubkey : tokenMint , isSigner : false , isWritable : false } , // ... additional required accounts ] ) . instruction ( ) ; return new Transaction ( ) . add ( instruction ) ; } Build External Call Data import { ethers } from 'ethers' ; import { keccak256 } from '@ethersproject/keccak256' ; function buildExternalCallData ( targetContract : string , functionSig : string , params : any [ ] ) : { data : Uint8Array ; shortcut : Uint8Array } { const iface = new ethers . Interface ( [ functionSig ] ) ; const calldata = iface . encodeFunctionData ( functionSig . split ( '(' ) [ 0 ] . replace ( 'function ' , '' ) , params ) ; const data = ethers . getBytes ( calldata ) ; const shortcut = ethers . getBytes ( keccak256 ( data ) ) ; return { data , shortcut } ; } // Example: ERC20 approve call const { data , shortcut } = buildExternalCallData ( '0xTargetContract...' , 'function approve(address spender, uint256 amount)' , [ '0xSpenderAddress...' , ethers . parseEther ( '1000' ) ] ) ; Testing Anchor Test Setup
Anchor.toml
[ provider ] cluster = "mainnet"
Use mainnet for testing with real deBridge
[ programs.mainnet ] my_program = "YourProgramId..." Run Tests
Full build and test
cd example_program && anchor build && anchor test
Test only (skip rebuild)
- anchor
- test
- --skip-build --skip-deploy
- Local Testing Tips
- Use Mainnet Fork
-
- deBridge infrastructure is on mainnet
- Mock Remaining Accounts
-
- Create mock accounts for unit tests
- Test Fee Calculations
- Verify fee amounts before sending Build Features The SDK supports different environments via Cargo features:
Production (default) - uses hardcoded program IDs
debridge-solana-sdk
{ git = "..." }
Custom environment - uses env vars
debridge-solana-sdk
- {
- git
- =
- "..."
- ,
- features
- =
- [
- "env"
- ]
- }
- Environment variables for custom networks:
- DEBRIDGE_PROGRAM_PUBKEY
-
- Custom deBridge program ID
- DEBRIDGE_SETTINGS_PROGRAM_PUBKEY
- Custom settings program ID Resources deBridge Documentation Solana SDK GitHub deBridge App Widget Integration API Reference Skill Structure debridge/ ├── SKILL.md # This file ├── resources/ │ ├── sdk-api-reference.md # Complete SDK API reference │ ├── chain-ids.md # Supported chain identifiers │ ├── program-ids.md # Program IDs and PDAs │ └── error-codes.md # Error types and handling ├── examples/ │ ├── basic-transfer/ # Simple cross-chain transfer │ ├── external-calls/ # External call execution │ ├── message-passing/ # Message-only transfers │ └── fee-configurations/ # Fee payment options └── docs/ └── troubleshooting.md # Common issues and solutions