Skip to main content

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?

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
Choose this when you want to:
  • Integrate with existing contracts on BOB
  • Avoid deploying new contracts
  • Implement simple approve + deposit patterns
  • Get started quickly
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: 0n },
          { target: PROTOCOL_ADDRESS, callData: depositCall, value: 0n },
        ],
        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 / 2n],
      }),
      value: 0n,
    },
    // 2. Swap half to another token
    {
      target: DEX_ROUTER,
      callData: encodeFunctionData({
        abi: parseAbi(['function swapExactTokensForTokens(...)']),
        functionName: 'swapExactTokensForTokens',
        args: [/* swap params */],
      }),
      value: 0n,
    },
    // 3. Add liquidity
    {
      target: DEX_ROUTER,
      callData: encodeFunctionData({
        abi: parseAbi(['function addLiquidity(...)']),
        functionName: 'addLiquidity',
        args: [/* liquidity params */],
      }),
      value: 0n,
    },
  ];

  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: [12345n],
  }),
  value: 0n,
};

const call2 = {
  target: TARGET2,
  callData: encodeFunctionData({
    abi: [{ type: 'function', name: 'notify', inputs: [{ name: 'user', type: 'address' }] }],
    functionName: 'notify',
    args: [userAddress],
  }),
  value: 0n,
};

// 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