wormhole-token-bridge-solana

This package implements Wormhole's Token Bridge specification on Solana with some modifications (due to the nature of how Solana works). The program itself is written using the [Anchor] framework.

Example Integration (Outbound Transfer)

In order to bridge assets from Solana with a program integrating with Token Bridge, there are a few traits that you the integrator will have to implement:

These traits are found in the SDK submodule of the Token Bridge program crate.

rust,ignore use wormhole_token_bridge_solana::sdk::{self as token_bridge_sdk, core_bridge_sdk};

Your account context may resemble the following:

```rust,ignore

[derive(Accounts)]

pub struct TransferHelloWorld<'info> { #[account(mut)] payer: Signer<'info>,

#[account(
    mut,
    associated_token::mint = mint,
    associated_token::authority = payer,
)]
payer_token: Account<'info, token::TokenAccount>,

/// CHECK: Mint of our token account.
#[account(owner = token::ID)]
mint: AccountInfo<'info>,

/// CHECK: This account acts as the signer for our Token Bridge transfer with payload. This PDA
/// validates the sender address as this program's ID.
#[account(
    seeds = [token_bridge_sdk::PROGRAM_SENDER_SEED_PREFIX],
    bump,
)]
sender_authority: AccountInfo<'info>,

/// CHECK: This account is needed for the Token Bridge program.
token_bridge_transfer_authority: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// native tokens.
token_bridge_custody_token_account: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// native tokens.
token_bridge_custody_authority: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// wrapped tokens.
token_bridge_wrapped_asset: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program.
token_bridge_core_emitter: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_bridge_config: UncheckedAccount<'info>,

/// CHECK: This account will be created using a generated keypair.
#[account(mut)]
core_message: AccountInfo<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_emitter_sequence: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_fee_collector: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
core_bridge_program: UncheckedAccount<'info>,

system_program: Program<'info, System>,
token_bridge_program: Program<'info, token_bridge_sdk::cpi::TokenBridge>,
token_program: Program<'info, token::Token>,

} ```

This account context must have all of the accounts required by the Token Bridge program in order to transfer assets out:

You are not required to re-derive these PDA addresses in your program's account context because the Core Bridge program already does these derivations. Doing so is a waste of compute units.

The traits above would be implemented by calling to_account_info on the appropriate accounts in your context.

By making sure that the token_bridge_program account is the correct program, your context will use the [Program] account wrapper with the TokenBridge type. Implementing the InvokeTokenBridge trait required for the TransferTokens trait and is as simple as:

rust,ignore impl<'info> core_bridge_sdk::cpi::InvokeTokenBridge<'info> for TransferHelloWorld<'info> { fn token_bridge_program(&self) -> AccountInfo<'info> { self.token_bridge_program.to_account_info() } }

Because transferring assets out message requires publishing a Wormhole message, you must implement the PublishMessage trait and the other traits it depends on (CreateAccount and InvokeCoreBridge). Please see the [Core Bridge program documentation] for more details.

Finally implement the PublishMessage trait by providing the necessary Core Bridge accounts.

NOTE: For transfers where the sender address is your program ID, the token_bridge_sender_authority in this case is Some(sender_authority), which is your program's PDA address derived using [b"sender"] as its seeds. This seed prefix is provided for you as PROGRAM_SENDER_SEED_PREFIX and is used in your account context to validate the correct sender authority is provided.

```rust,ignore impl<'info> tokenbridgesdk::cpi::TransferTokens<'info> for TransferHelloWorld<'info> { fn tokenprogram(&self) -> AccountInfo<'info> { self.tokenprogram.toaccountinfo() }

fn src_token_account(&self) -> AccountInfo<'info> {
    self.payer_token.to_account_info()
}

fn mint(&self) -> AccountInfo<'info> {
    self.mint.to_account_info()
}

fn token_bridge_transfer_authority(&self) -> AccountInfo<'info> {
    self.token_bridge_transfer_authority.to_account_info()
}

fn token_bridge_custody_authority(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_custody_authority.clone()
}

fn token_bridge_custody_token_account(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_custody_token_account.clone()
}

fn token_bridge_wrapped_asset(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_wrapped_asset.clone()
}

fn token_bridge_sender_authority(&self) -> Option<AccountInfo<'info>> {
    Some(self.sender_authority.to_account_info())
}

} ```

In your instruction handler/processor method, you would use the transfer_tokens method from the CPI SDK with the TransferTokensDirective::ProgramTransferWithPayload with your program ID. The Token Bridge program will verify that your sender authority can be derived the same way using the provided program ID (this validates the correct sender address will be used for your transfer).

This directive with the other transfer arguments (nonce, amount, redeemer, redeemer_chain and message payload) will invoke the Token Bridge to bridge assets out, which is basically a Worhole message emitted by the Token Bridge observed by the Guardians. When the Wormhole Guardians sign this message attesting to its observation, you may redeem this attested transfer (VAA) on the specified redeemer's network (specified by redeemer_chain) where a Token Bridge smart contract is deployed.

```rust,ignore pub fn transferhelloworld(ctx: Context, amount: u64) -> Result<()> { let nonce = 420; let redeemer = [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, ]; let redeemerchain = 2; let payload = b"Hello, world!".tovec();

token_bridge_sdk::cpi::transfer_tokens(
    ctx.accounts,
    token_bridge_sdk::cpi::TransferTokensDirective::ProgramTransferWithPayload {
        program_id: crate::ID,
        nonce,
        amount,
        redeemer,
        redeemer_chain,
        payload,
    },
    Some(&[&[
        token_bridge_sdk::PROGRAM_SENDER_SEED_PREFIX,
        &[ctx.bumps["sender_authority"]],
    ]]),
)

} ```

And that is all you need to do to transfer assets from Solana. Putting everything together to make a simple Anchor program looks like the following:

```rust,ignore

![allow(clippy::resultlargeerr)]

use anchorlang::prelude::*; use anchorspl::token; use wormholetokenbridgesolana::sdk::{self as tokenbridgesdk, corebridge_sdk};

declare_id!("TokenBridgeHe11oWor1d1111111111111111111111");

[derive(Accounts)]

pub struct TransferHelloWorld<'info> { #[account(mut)] payer: Signer<'info>,

#[account(
    mut,
    associated_token::mint = mint,
    associated_token::authority = payer,
)]
payer_token: Account<'info, token::TokenAccount>,

/// CHECK: Mint of our token account.
#[account(owner = token::ID)]
mint: AccountInfo<'info>,

/// CHECK: This account acts as the signer for our Token Bridge transfer with payload. This PDA
/// validates the sender address as this program's ID.
#[account(
    seeds = [token_bridge_sdk::PROGRAM_SENDER_SEED_PREFIX],
    bump,
)]
sender_authority: AccountInfo<'info>,

/// CHECK: This account is needed for the Token Bridge program.
token_bridge_transfer_authority: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// native tokens.
token_bridge_custody_token_account: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// native tokens.
token_bridge_custody_authority: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program. This should not be None for
/// wrapped tokens.
token_bridge_wrapped_asset: Option<AccountInfo<'info>>,

/// CHECK: This account is needed for the Token Bridge program.
token_bridge_core_emitter: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_bridge_config: UncheckedAccount<'info>,

/// CHECK: This account will be created using a generated keypair.
#[account(mut)]
core_message: AccountInfo<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_emitter_sequence: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
#[account(mut)]
core_fee_collector: UncheckedAccount<'info>,

/// CHECK: This account is needed for the Token Bridge program.
core_bridge_program: UncheckedAccount<'info>,

system_program: Program<'info, System>,
token_bridge_program: Program<'info, token_bridge_sdk::cpi::TokenBridge>,
token_program: Program<'info, token::Token>,

}

impl<'info> corebridgesdk::cpi::InvokeCoreBridge<'info> for TransferHelloWorld<'info> { fn corebridgeprogram(&self) -> AccountInfo<'info> { self.corebridgeprogram.toaccountinfo() } }

impl<'info> corebridgesdk::cpi::CreateAccount<'info> for TransferHelloWorld<'info> { fn payer(&self) -> AccountInfo<'info> { self.payer.toaccountinfo() }

fn system_program(&self) -> AccountInfo<'info> {
    self.system_program.to_account_info()
}

}

impl<'info> corebridgesdk::cpi::PublishMessage<'info> for TransferHelloWorld<'info> { fn corebridgeconfig(&self) -> AccountInfo<'info> { self.corebridgeconfig.toaccountinfo() }

fn core_emitter(&self) -> Option<AccountInfo<'info>> {
    Some(self.token_bridge_core_emitter.to_account_info())
}

fn core_emitter_sequence(&self) -> AccountInfo<'info> {
    self.core_emitter_sequence.to_account_info()
}

fn core_fee_collector(&self) -> Option<AccountInfo<'info>> {
    Some(self.core_fee_collector.to_account_info())
}

fn core_message(&self) -> AccountInfo<'info> {
    self.core_message.to_account_info()
}

}

impl<'info> tokenbridgesdk::cpi::InvokeTokenBridge<'info> for TransferHelloWorld<'info> { fn tokenbridgeprogram(&self) -> AccountInfo<'info> { self.tokenbridgeprogram.toaccountinfo() } }

impl<'info> tokenbridgesdk::cpi::TransferTokens<'info> for TransferHelloWorld<'info> { fn tokenprogram(&self) -> AccountInfo<'info> { self.tokenprogram.toaccountinfo() }

fn src_token_account(&self) -> AccountInfo<'info> {
    self.payer_token.to_account_info()
}

fn mint(&self) -> AccountInfo<'info> {
    self.mint.to_account_info()
}

fn token_bridge_transfer_authority(&self) -> AccountInfo<'info> {
    self.token_bridge_transfer_authority.to_account_info()
}

fn token_bridge_custody_authority(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_custody_authority.clone()
}

fn token_bridge_custody_token_account(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_custody_token_account.clone()
}

fn token_bridge_wrapped_asset(&self) -> Option<AccountInfo<'info>> {
    self.token_bridge_wrapped_asset.clone()
}

fn token_bridge_sender_authority(&self) -> Option<AccountInfo<'info>> {
    Some(self.sender_authority.to_account_info())
}

}

[program]

pub mod tokenbridgehello_world { use super::*;

pub fn transfer_hello_world(ctx: Context<TransferHelloWorld>, amount: u64) -> Result<()> {
    let nonce = 420;
    let redeemer = [
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xde, 0xad, 0xbe, 0xef,
        0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad,
        0xbe, 0xef,
    ];
    let redeemer_chain = 2;
    let payload = b"Hello, world!".to_vec();

    token_bridge_sdk::cpi::transfer_tokens(
        ctx.accounts,
        token_bridge_sdk::cpi::TransferTokensDirective::ProgramTransferWithPayload {
            program_id: crate::ID,
            nonce,
            amount,
            redeemer,
            redeemer_chain,
            payload,
        },
        Some(&[&[
            token_bridge_sdk::PROGRAM_SENDER_SEED_PREFIX,
            &[ctx.bumps["sender_authority"]],
        ]]),
    )
}

}

```