Get Started
SDK
- Quickstart
- Concepts
- Bridge functions
- Token Prices
- Bridge API
- Chain Adapters
Widget
Smart Contracts
General information
Solana
This guide explains how to interact with the rhino.fi Bridge program on Solana.
Contract Overview
The Bridge program exposes the following function for bridging:
pub fn deposit_with_id(
ctx: Context<DepositLiquidity>,
amount: u64,
commitment_id: u128,
)
IDL
{
version: '0.1.0',
name: 'bridge',
constants: [
{
name: 'AUTHORITY_SEED',
type: 'string',
value: '"authority"',
},
{
name: 'BRIDGE_SEED',
type: 'string',
value: '"rhino_bridge"',
},
],
instructions: [
{
name: 'createBridge',
accounts: [
{
name: 'bridge',
isMut: true,
isSigner: false,
},
{
name: 'admin',
isMut: false,
isSigner: false,
docs: ['The admin of the Bridge'],
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
],
args: [],
},
{
name: 'deposit',
accounts: [
{
name: 'bridge',
isMut: false,
isSigner: false,
},
{
name: 'pool',
isMut: false,
isSigner: false,
},
{
name: 'poolAuthority',
isMut: false,
isSigner: false,
},
{
name: 'depositor',
isMut: false,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'mint',
isMut: false,
isSigner: false,
},
{
name: 'poolAccount',
isMut: true,
isSigner: false,
},
{
name: 'depositorAccount',
isMut: true,
isSigner: false,
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'tokenProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
{
name: 'associatedTokenProgram',
isMut: false,
isSigner: false,
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
},
{
name: 'eventAuthority',
isMut: false,
isSigner: false,
},
{
name: 'program',
isMut: false,
isSigner: false,
},
],
args: [
{
name: 'amount',
type: 'u64',
},
{
name: 'ethAddressUpper',
type: 'u32',
},
{
name: 'ethAddressLower',
type: 'u128',
},
],
},
{
name: 'depositWithId',
accounts: [
{
name: 'bridge',
isMut: false,
isSigner: false,
},
{
name: 'pool',
isMut: false,
isSigner: false,
},
{
name: 'poolAuthority',
isMut: false,
isSigner: false,
},
{
name: 'depositor',
isMut: false,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'mint',
isMut: false,
isSigner: false,
},
{
name: 'poolAccount',
isMut: true,
isSigner: false,
},
{
name: 'depositorAccount',
isMut: true,
isSigner: false,
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'tokenProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
{
name: 'associatedTokenProgram',
isMut: false,
isSigner: false,
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
},
{
name: 'eventAuthority',
isMut: false,
isSigner: false,
},
{
name: 'program',
isMut: false,
isSigner: false,
},
],
args: [
{
name: 'amount',
type: 'u64',
},
{
name: 'commitmentId',
type: 'u128',
},
],
},
{
name: 'createPool',
accounts: [
{
name: 'bridge',
isMut: false,
isSigner: false,
},
{
name: 'pool',
isMut: true,
isSigner: false,
},
{
name: 'poolAuthority',
isMut: false,
isSigner: false,
},
{
name: 'mint',
isMut: false,
isSigner: false,
},
{
name: 'poolAccount',
isMut: true,
isSigner: false,
},
{
name: 'admin',
isMut: false,
isSigner: true,
docs: ['The admin of the Bridge'],
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'tokenProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
{
name: 'associatedTokenProgram',
isMut: false,
isSigner: false,
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
},
],
args: [],
},
{
name: 'allowOperator',
accounts: [
{
name: 'bridge',
isMut: false,
isSigner: false,
},
{
name: 'operatorStorage',
isMut: true,
isSigner: false,
},
{
name: 'operator',
isMut: false,
isSigner: false,
},
{
name: 'admin',
isMut: false,
isSigner: true,
docs: ['The admin of the Bridge'],
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
],
args: [
{
name: 'isAllowed',
type: 'bool',
},
],
},
{
name: 'withdraw',
accounts: [
{
name: 'bridge',
isMut: false,
isSigner: false,
},
{
name: 'pool',
isMut: false,
isSigner: false,
},
{
name: 'poolAuthority',
isMut: false,
isSigner: false,
},
{
name: 'operatorStorage',
isMut: false,
isSigner: false,
},
{
name: 'recipient',
isMut: false,
isSigner: false,
},
{
name: 'operator',
isMut: false,
isSigner: true,
},
{
name: 'mint',
isMut: false,
isSigner: false,
},
{
name: 'poolAccount',
isMut: true,
isSigner: false,
},
{
name: 'recipientAccount',
isMut: true,
isSigner: false,
},
{
name: 'payer',
isMut: true,
isSigner: true,
docs: ['The account paying for all rents'],
},
{
name: 'tokenProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
{
name: 'associatedTokenProgram',
isMut: false,
isSigner: false,
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
},
],
args: [
{
name: 'amount',
type: 'u64',
},
],
},
{
name: 'transferAdmin',
accounts: [
{
name: 'bridge',
isMut: true,
isSigner: false,
},
{
name: 'newAdmin',
isMut: false,
isSigner: false,
docs: ['The admin of the Bridge'],
},
{
name: 'admin',
isMut: false,
isSigner: true,
docs: ['The admin of the Bridge'],
},
{
name: 'systemProgram',
isMut: false,
isSigner: false,
docs: ['Solana ecosystem accounts'],
},
],
args: [],
},
],
accounts: [
{
name: 'bridge',
type: {
kind: 'struct',
fields: [
{
name: 'admin',
docs: ['Account that has admin authority over the Bridge'],
type: 'publicKey',
},
],
},
},
{
name: 'pool',
type: {
kind: 'struct',
fields: [
{
name: 'bridge',
docs: ['Primary key of the Bridge'],
type: 'publicKey',
},
{
name: 'mint',
docs: ['Mint of token A'],
type: 'publicKey',
},
],
},
},
{
name: 'operator',
type: {
kind: 'struct',
fields: [
{
name: 'operator',
docs: ['Public key of the operator'],
type: 'publicKey',
},
{
name: 'isAllowed',
docs: ['Whether the operator has access'],
type: 'bool',
},
],
},
},
],
events: [
{
name: 'BridgedDeposit',
fields: [
{
name: 'from',
type: 'publicKey',
index: false,
},
{
name: 'to',
type: 'publicKey',
index: false,
},
{
name: 'token',
type: 'publicKey',
index: false,
},
{
name: 'amount',
type: 'u64',
index: false,
},
{
name: 'ethAddressUpper',
type: 'u32',
index: false,
},
{
name: 'ethAddressLower',
type: 'u128',
index: false,
},
],
},
{
name: 'BridgedDepositWithId',
fields: [
{
name: 'from',
type: 'publicKey',
index: false,
},
{
name: 'to',
type: 'publicKey',
index: false,
},
{
name: 'token',
type: 'publicKey',
index: false,
},
{
name: 'amount',
type: 'u64',
index: false,
},
{
name: 'commitmentId',
type: 'u128',
index: false,
},
],
},
{
name: 'BridgeWithdraw',
fields: [
{
name: 'from',
type: 'publicKey',
index: false,
},
{
name: 'to',
type: 'publicKey',
index: false,
},
{
name: 'amount',
type: 'u64',
index: false,
},
],
},
],
errors: [
{
code: 6000,
name: 'DepositNotAllowed',
msg: 'DEPOSIT_NOT_ALLOWED',
},
{
code: 6001,
name: 'Unauthorized',
msg: 'UNAUTHORIZED',
},
{
code: 6002,
name: 'NotEnoughLiquidity',
msg: 'NOT_ENOUGH_LIQUIDITY',
},
{
code: 6003,
name: 'NotEnoughBalance',
msg: 'NOT_ENOUGH_BALANCE',
},
{
code: 6004,
name: 'InvalidAmount',
msg: 'INVALID_AMOUNT',
},
],
}
Setup
For this example we’ll use @solana/web3.js
, @solana/spl-token
and @coral-xyz/anchor
to interact with the contract, but you can replace it with any Solana library of your choice.
Make sure to install it first:
yarn add @solana/web3.js @solana/spl-token @coral-xyz/anchor
Set-up helpers for Solana accounts
import { PublicKey } from '@solana/web3.js'
import { getAssociatedTokenAddressSync } from '@solana/spl-token'
import { Buffer } from 'buffer'
export const getSolanaBridgeKey = async (bridgeContractAddress) => {
const programId = new PublicKey(bridgeContractAddress)
return PublicKey.findProgramAddressSync([Buffer.from('rhino_bridge')], programId)[0]
}
export const getSolanaPoolKey = async (bridgeContractAddress, mint) => {
const programId = new PublicKey(bridgeContractAddress)
const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress)
const mintId = new PublicKey(mint)
return PublicKey.findProgramAddressSync([bridgeKey.toBuffer(), mintId.toBuffer()], programId)[0]
}
export const getSolanaPoolAuthority = async (bridgeContractAddress, mint) => {
const programId = new PublicKey(bridgeContractAddress)
const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress)
const mintId = new PublicKey(mint)
return PublicKey.findProgramAddressSync(
[bridgeKey.toBuffer(), mintId.toBuffer(), Buffer.from('authority')],
programId,
)[0]
}
export const getSolanaPoolTokenAccount = async (bridgeContractAddress, mint) => {
const poolAuthority = await getSolanaPoolAuthority(bridgeContractAddress, mint)
const mintId = new PublicKey(mint)
return getAssociatedTokenAddressSync(mintId, poolAuthority, true)
}
export const getSolanaDepositorAccount = async (secondaryWalletAddress, mint) => {
const mintId = new PublicKey(mint)
const depositor = new PublicKey(secondaryWalletAddress)
return getAssociatedTokenAddressSync(mintId, depositor, true)
}
Interacting with the Contract
To execute a bridge transaction, call the depositWithId
(ERC20) or depositNativeWithId
(native) function as follows:
The commitmentId
required by the contract corresponds to the quoteId
returned by the API.
To obtain the commitmentId
, first request a quote from the API, then commit it. The response will include the quoteId
, which should be used as the commitmentId
in the contract call.
See the API bridge example for details.
import {Connection, PublicKey} from '@solana/web3.js'
import {AnchorProvider, Program, BN} from '@coral-xyz/anchor'
import {
getSolanaBridgeKey,
getSolanaDepositorAccount,
getSolanaPoolAuthority,
getSolanaPoolKey,
getSolanaPoolTokenAccount,
} from './solanaBridgeHelpers'
import {IDL} from "./idl";
import getBridgeConfigs from './getBridgeConfigs'
// Import a solana wallet / or use your wallet library
const secretKey = base58.decode(SOLANA_PRIVATE_KEY)
const keypair = Keypair.fromSecretKey(new Uint8Array(secretKey))
const wallet = new Wallet(keypair)
export const callSolanaBridgeContract = async (
{
token,
chain, // SOLANA
commitmentId,
amount,
}) => {
// See API Integration -> Fetch Bridge Configs for the full implementation
const configs = await getBridgeConfigs()
const chainConfig = configs[chain]
const solanaWalletAddress = wallet.publicKey.toString()
const tokenConfig = chainConfig.tokens[token]
const tokenAmount = +amount * 10 ** tokenConfig.decimals
const tokenAddress = tokenConfig.address
const bridgeContractAddress = chainConfig.contractAddress
const connection = new Connection(chainConfig.rpc)
const options = AnchorProvider.defaultOptions()
const anchorProvider = new AnchorProvider(connection, wallet, options)
const depositor = new PublicKey(solanaWalletAddress)
const bridgeKey = await getSolanaBridgeKey(bridgeContractAddress)
const poolKey = await getSolanaPoolKey(bridgeContractAddress, tokenAddress)
const poolAuthority = await getSolanaPoolAuthority(bridgeContractAddress, tokenAddress)
const poolAccount = await getSolanaPoolTokenAccount(bridgeContractAddress, tokenAddress)
const depositorAccount = await getSolanaDepositorAccount(solanaWalletAddress, tokenAddress)
const program = new Program(IDL, bridgeContractAddress, anchorProvider)
const accounts = {
bridge: bridgeKey,
pool: poolKey,
poolAuthority,
poolAccount: poolAccount,
depositor,
mint: new PublicKey(tokenAddress),
depositorAccount: depositorAccount,
}
const depositTxHash = await program.methods['depositWithId'](new BN(tokenAmount), new BN(commitmentId, 'hex'))
.accounts(accounts)
.signers([])
.rpc()
await connection.getSignatureStatus(depositTxHash)
return depositTxHash
}