Overview
BOB Gateway enables users to interact with DeFi protocols using a single Bitcoin transaction. There are three integration approaches:
Custom Strategy Full control with smart contracts
Multicall No deployment needed
Cross-Chain Execute on destination chains
Which Approach Should I Choose?
Custom Strategy - For Complex Logic
Choose this when you need:
Complex multi-step operations
Gas optimization
Custom events and logging
Full control over execution flow
State management in your contract
Multicall - For Quick Integration
Choose this when you want to:
Integrate with existing contracts on BOB
Avoid deploying new contracts
Implement simple approve + deposit patterns
Get started quickly
Cross-Chain - For LayerZero Bridges
Choose this for:
Bridging to Ethereum, Base, or other LayerZero chains
Executing actions on destination chain after bridge
Depositing into lending protocols on destination
Multi-step operations post-bridge
Option 1: Custom Strategy Contract
Deploy a smart contract that implements the Gateway strategy interface for full control.
Strategy Interface
interface IStrategy {
function handleGatewayMessage (
IERC20 tokenSent , // Wrapped BTC token (WBTC, tBTC, etc.)
uint256 amountIn , // Amount received
address recipient , // User's EVM address
bytes memory message // Optional parameters
) external ;
}
Complete Example: Convert to SolvBTC
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol" ;
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol" ;
interface ISolvBTCRouter {
function deposit (
address targetToken_ ,
address currency_ ,
uint256 currencyAmount_ ,
uint256 minimumTargetTokenAmount_ ,
uint64 expireTime_
) external returns ( uint256 );
}
contract SolvBTCStrategy {
using SafeERC20 for IERC20 ;
ISolvBTCRouter public immutable solvBTCRouter;
IERC20 public immutable solvBTC;
constructor ( address _solvBTCRouter , address _solvBTC ) {
solvBTCRouter = ISolvBTCRouter (_solvBTCRouter);
solvBTC = IERC20 (_solvBTC);
}
function handleGatewayMessage (
IERC20 tokenSent ,
uint256 amountIn ,
address recipient ,
bytes memory message
) external {
// Transfer wrapped BTC from Gateway
tokenSent. safeTransferFrom ( msg.sender , address ( this ), amountIn);
// Decode minimum output from message (optional)
uint256 minOutput = message.length > 0
? abi . decode (message, ( uint256 ))
: 0 ;
// Approve SolvBTC router
tokenSent. safeIncreaseAllowance ( address (solvBTCRouter), amountIn);
// Convert to SolvBTC
uint256 solvBTCAmount = solvBTCRouter. deposit (
address (solvBTC),
address (tokenSent),
amountIn,
minOutput,
uint64 ( block .timestamp + 1 )
);
// Send SolvBTC to user
solvBTC. safeTransfer (recipient, solvBTCAmount);
}
}
Using Your Custom Strategy
import { GatewaySDK , parseBtc } from '@gobob/bob-sdk' ;
const gatewaySDK = new GatewaySDK ( bob . id );
// Encode minimum output parameter
const minOutput = parseUnits ( "0.099" , 8 ); // Minimum 0.099 BTC worth
const message = encodeAbiParameters (
[{ type: 'uint256' }],
[ minOutput ]
);
const quote = await gatewaySDK . getQuote ({
fromChain: 'bitcoin' ,
fromToken: '0x0000000000000000000000000000000000000000' ,
toChain: 'bob' ,
toToken: '0xYourStrategyAddress' , // Your deployed strategy
fromUserAddress: 'bc1q...' ,
toUserAddress: '0x...' , // Receives the SolvBTC
amount: parseBtc ( "0.1" ),
strategyMessage: message , // Optional parameters
});
The toToken parameter should be set to your deployed strategy contract address. Optionally, use strategyAddress for advanced cases where the strategy target differs from the token destination. The strategyMessage parameter passes ABI-encoded parameters to your strategy’s handleGatewayMessage function.
Option 2: Multicall Strategy
Execute multiple contract calls without deploying custom contracts.
Basic Approve + Deposit Pattern
import {
encodeFunctionData ,
parseAbi ,
encodeAbiParameters ,
parseAbiParameters ,
Address
} from 'viem' ;
import { GatewaySDK , parseBtc } from '@gobob/bob-sdk' ;
const WBTC_ADDRESS = '0x03C7054BCB39f7b2e5B2c7AcB37583e32D70Cfa3' ; // BOB mainnet
const PROTOCOL_ADDRESS = '0xYourProtocolAddress' ;
function generateMulticallMessage (
userAddress : Address ,
depositAmount : bigint
) {
// Step 1: Approve protocol to spend WBTC
const approveCall = encodeFunctionData ({
abi: parseAbi ([ 'function approve(address spender, uint256 value)' ]),
functionName: 'approve' ,
args: [ PROTOCOL_ADDRESS , depositAmount ],
});
// Step 2: Deposit into protocol
const depositCall = encodeFunctionData ({
abi: parseAbi ([ 'function deposit(address asset, uint256 amount, address onBehalfOf)' ]),
functionName: 'deposit' ,
args: [ WBTC_ADDRESS , depositAmount , userAddress ],
});
// Encode as multicall message
return encodeAbiParameters (
parseAbiParameters ( '((address target, bytes callData, uint256 value)[], address fallbackRecipient)' ),
[
[
[
{ target: WBTC_ADDRESS , callData: approveCall , value: 0 n },
{ target: PROTOCOL_ADDRESS , callData: depositCall , value: 0 n },
],
userAddress , // Fallback recipient if execution fails
],
]
);
}
// Get quote with multicall
const depositAmount = parseBtc ( "0.1" );
const multicallMessage = generateMulticallMessage ( userAddress , depositAmount );
const quote = await gatewaySDK . getQuote ({
fromChain: 'bitcoin' ,
fromToken: '0x0000000000000000000000000000000000000000' ,
toChain: 'bob' ,
toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
fromUserAddress: 'bc1q...' ,
toUserAddress: userAddress ,
amount: depositAmount ,
strategyMessage: multicallMessage ,
});
Advanced: Multi-Protocol Interaction
function generateComplexMulticall ( user : Address , amount : bigint ) {
const calls = [
// 1. Approve DEX router
{
target: WBTC_ADDRESS ,
callData: encodeFunctionData ({
abi: erc20Abi ,
functionName: 'approve' ,
args: [ DEX_ROUTER , amount / 2 n ],
}),
value: 0 n ,
},
// 2. Swap half to another token
{
target: DEX_ROUTER ,
callData: encodeFunctionData ({
abi: parseAbi ([ 'function swapExactTokensForTokens(...)' ]),
functionName: 'swapExactTokensForTokens' ,
args: [ /* swap params */ ],
}),
value: 0 n ,
},
// 3. Add liquidity
{
target: DEX_ROUTER ,
callData: encodeFunctionData ({
abi: parseAbi ([ 'function addLiquidity(...)' ]),
functionName: 'addLiquidity' ,
args: [ /* liquidity params */ ],
}),
value: 0 n ,
},
];
return encodeAbiParameters (
parseAbiParameters ( '((address target, bytes callData, uint256 value)[], address fallbackRecipient)' ),
[[ calls , user ]]
);
}
Option 3: Cross-Chain Destination Calls
Cross-Chain Destination Calls allow you to execute arbitrary smart contract calls on the destination chain as part of a cross-chain swap or bridge transaction. This is ideal for advanced integrations, multi-step DeFi actions, or custom workflows triggered by a cross-chain transfer.
How It Works
When you request a quote (via get-quote), you can specify a message containing an ABI-encoded vector of calls. Each call uses the following Solidity struct:
struct Call {
address target;
bytes callData;
uint256 value;
}
target: Contract address to call on the destination chain
callData: ABI-encoded function call data
value: Amount of native token (e.g., ETH) to send with the call
Encode a vector of these Call structs using standard Solidity ABI encoding and include it in the strategyMessage field of your quote request.
On the destination chain, the solver decodes the vector and executes each call using a LayerZero (L0) composer contract. This ensures atomic execution and proper sequencing of all calls.
SDK Example: Sending Cross-Chain Calls
import { encodeAbiParameters , parseAbiParameters , encodeFunctionData , Address } from 'viem' ;
import { GatewaySDK , parseBtc } from '@gobob/bob-sdk' ;
const gatewaySDK = new GatewaySDK ( bob . id );
// Example: Call two contracts on destination chain
const TARGET1 = '0xTargetContract1' ;
const TARGET2 = '0xTargetContract2' ;
const userAddress = '0xUserAddress' ;
// Prepare call data for each target
const call1 = {
target: TARGET1 ,
callData: encodeFunctionData ({
abi: [{ type: 'function' , name: 'doSomething' , inputs: [{ name: 'amount' , type: 'uint256' }] }],
functionName: 'doSomething' ,
args: [ 12345 n ],
}),
value: 0 n ,
};
const call2 = {
target: TARGET2 ,
callData: encodeFunctionData ({
abi: [{ type: 'function' , name: 'notify' , inputs: [{ name: 'user' , type: 'address' }] }],
functionName: 'notify' ,
args: [ userAddress ],
}),
value: 0 n ,
};
// ABI encode the vector of calls
const crossChainMessage = encodeAbiParameters (
parseAbiParameters ( '(address target, bytes callData, uint256 value)[]' ),
[[ call1 , call2 ]]
);
// Request a quote with the cross-chain message
const quote = await gatewaySDK . getQuote ({
fromChain: 'bitcoin' ,
fromToken: '0x0000000000000000000000000000000000000000' ,
toChain: 'ethereum' , // or other destination chain
toToken: '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
fromUserAddress: 'bc1q...' ,
toUserAddress: userAddress ,
amount: parseBtc ( '0.1' ),
strategyMessage: crossChainMessage ,
});
Benefits
Enables complex workflows and contract interactions as part of a cross-chain swap
Ensures atomic execution via the L0 composer
Note: Make sure all target contracts and call data are valid for the destination chain. The composer contract handles sequencing and error management; failed calls may revert the entire batch depending on configuration.
Next Steps