Skip to content

Creating a Proxy Wallet

Before interacting with the Orderbook API, users must create a proxy wallet from their Externally Owned Account (EOA). This guide shows you how to create a proxy wallet using TypeScript and Viem.

Overview

The proxy wallet is a smart contract wallet that acts as an intermediary between your EOA and the Orderbook API. All API interactions should use the proxy wallet address, not your EOA address.

Proxy Wallet Factory Contract:
  • Address: 0xB99159aBF0bF59a512970586F38292f8b9029924
  • Network: BSC (Binance Smart Chain)
  • Chain ID: 56

Prerequisites

Install the required dependencies:

npm install viem

Step-by-Step Guide

Step 1: Setup Viem Client

First, create a wallet client connected to BSC:

import { createWalletClient, http, custom } from 'viem';
import { bsc } from 'viem/chains';
 
// For browser environments (MetaMask, etc.)
const walletClient = createWalletClient({
  chain: bsc,
  transport: custom(window.ethereum),
});
 
// For Node.js environments with private key
import { privateKeyToAccount } from 'viem/accounts';
 
const account = privateKeyToAccount('0x...' as `0x${string}`);
const walletClient = createWalletClient({
  account,
  chain: bsc,
  transport: http(),
});

Step 2: Get Your EOA Address

// For browser environments
const [address] = await walletClient.getAddresses();
console.log('EOA Address:', address);
 
// For Node.js with private key
const eoaAddress = account.address;
console.log('EOA Address:', eoaAddress);

Step 3: Proxy Wallet Factory ABI

The proxy wallet factory contract uses the following interface:

const PROXY_WALLET_FACTORY_ABI = [
  {
    inputs: [{ internalType: 'address', name: 'user', type: 'address' }],
    name: 'computeProxyAddress',
    outputs: [{ internalType: 'address', name: '', type: 'address' }],
    stateMutability: 'view',
    type: 'function',
  },
  {
    inputs: [
      { internalType: 'address', name: 'paymentToken', type: 'address' },
      { internalType: 'uint256', name: 'payment', type: 'uint256' },
      { internalType: 'address payable', name: 'paymentReceiver', type: 'address' },
      {
        components: [
          { internalType: 'uint8', name: 'v', type: 'uint8' },
          { internalType: 'bytes32', name: 'r', type: 'bytes32' },
          { internalType: 'bytes32', name: 's', type: 'bytes32' },
        ],
        internalType: 'struct SafeProxyFactory.Sig',
        name: 'createSig',
        type: 'tuple',
      },
    ],
    name: 'createProxy',
    outputs: [],
    stateMutability: 'nonpayable',
    type: 'function',
  },
  {
    inputs: [{ internalType: 'address', name: 'user', type: 'address' }],
    name: 'getSalt',
    outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
    stateMutability: 'pure',
    type: 'function',
  },
] as const;

Step 4: Compute and Check Proxy Wallet Address

The proxy wallet address is deterministically computed from your EOA address. First, compute the address and check if it already exists:

import { createPublicClient } from 'viem';
 
const publicClient = createPublicClient({
  chain: bsc,
  transport: http(),
});
 
const PROXY_WALLET_FACTORY_ADDRESS = '0xB99159aBF0bF59a512970586F38292f8b9029924' as const;
 
async function computeProxyAddress(eoaAddress: `0x${string}`): Promise<`0x${string}`> {
  const proxyAddress = await publicClient.readContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: PROXY_WALLET_FACTORY_ABI,
    functionName: 'computeProxyAddress',
    args: [eoaAddress],
  });
  
  return proxyAddress;
}
 
async function proxyWalletExists(proxyAddress: `0x${string}`): Promise<boolean> {
  try {
    const code = await publicClient.getBytecode({ address: proxyAddress });
    // If code exists and is not empty, the wallet is deployed
    return code !== undefined && code !== '0x' && code.length > 2;
  } catch (error) {
    return false;
  }
}
 
// Compute and check proxy wallet
const proxyAddress = await computeProxyAddress(eoaAddress);
const exists = await proxyWalletExists(proxyAddress);
 
if (exists) {
  console.log('Proxy wallet already exists:', proxyAddress);
  // Use existing wallet
} else {
  console.log('Proxy wallet not deployed yet:', proxyAddress);
  console.log('Creating proxy wallet...');
}

Step 5: Create Proxy Wallet Signature

The createProxy function requires a signature. You need to sign an EIP-712 message. First, get the domain separator and create the signature:

import { encodeAbiParameters, keccak256, toBytes, recoverMessageAddress } from 'viem';
 
async function createProxySignature(
  eoaAddress: `0x${string}`,
  paymentToken: `0x${string}`,
  payment: bigint,
  paymentReceiver: `0x${string}`
): Promise<{ v: number; r: `0x${string}`; s: `0x${string}` }> {
  // Get domain separator from contract
  const domainSeparator = await publicClient.readContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: [
      {
        inputs: [],
        name: 'domainSeparator',
        outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
        stateMutability: 'view',
        type: 'function',
      },
    ],
    functionName: 'domainSeparator',
  });
 
  // Get CREATE_PROXY_TYPEHASH
  const createProxyTypehash = await publicClient.readContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: [
      {
        inputs: [],
        name: 'CREATE_PROXY_TYPEHASH',
        outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
        stateMutability: 'view',
        type: 'function',
      },
    ],
    functionName: 'CREATE_PROXY_TYPEHASH',
  });
 
  // Encode the struct hash
  // CREATE_PROXY_TYPEHASH = keccak256("CreateProxy(address user,address paymentToken,uint256 payment,address paymentReceiver)")
  // structHash = keccak256(abi.encode(CREATE_PROXY_TYPEHASH, user, paymentToken, payment, paymentReceiver))
  const structHash = keccak256(
    encodeAbiParameters(
      [
        { type: 'bytes32' }, // CREATE_PROXY_TYPEHASH
        { type: 'address' }, // user
        { type: 'address' }, // paymentToken
        { type: 'uint256' }, // payment
        { type: 'address' }, // paymentReceiver
      ],
      [createProxyTypehash, eoaAddress, paymentToken, payment, paymentReceiver]
    )
  );
 
  // Create the message to sign: keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash))
  const message = keccak256(
    encodeAbiParameters(
      [{ type: 'bytes32' }, { type: 'bytes32' }, { type: 'bytes32' }],
      [
        '0x1901' as `0x${string}`, // EIP-191 prefix
        domainSeparator,
        structHash,
      ]
    )
  );
 
  // Sign the message
  const signature = await walletClient.signMessage({
    message: { raw: toBytes(message) },
  });
 
  // Split signature into v, r, s
  const r = `0x${signature.slice(2, 66)}` as `0x${string}`;
  const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
  const v = parseInt(signature.slice(130, 132), 16);
 
  return { v, r, s };
}

Step 6: Create Proxy Wallet

Now create the proxy wallet with the signature:

async function createProxyWallet(
  eoaAddress: `0x${string}`,
  paymentToken: `0x${string}` = '0x0000000000000000000000000000000000000000', // Zero address for no payment
  payment: bigint = 0n,
  paymentReceiver: `0x${string}` = '0x0000000000000000000000000000000000000000' // Zero address for no payment
): Promise<`0x${string}`> {
  // Compute the proxy address first
  const proxyAddress = await computeProxyAddress(eoaAddress);
  
  // Check if already exists
  if (await proxyWalletExists(proxyAddress)) {
    console.log('Proxy wallet already exists');
    return proxyAddress;
  }
 
  // Create signature
  const createSig = await createProxySignature(eoaAddress, paymentToken, payment, paymentReceiver);
 
  // Create the proxy
  const hash = await walletClient.writeContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: PROXY_WALLET_FACTORY_ABI,
    functionName: 'createProxy',
    args: [paymentToken, payment, paymentReceiver, createSig],
  });
 
  // Wait for transaction confirmation
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log('Transaction confirmed:', receipt.transactionHash);
 
  // Verify the wallet was created
  const exists = await proxyWalletExists(proxyAddress);
  if (!exists) {
    throw new Error('Failed to create proxy wallet');
  }
 
  return proxyAddress;
}
 
// Create proxy wallet (no payment required)
const proxyWalletAddress = await createProxyWallet(eoaAddress);
console.log('Proxy wallet created:', proxyWalletAddress);

Step 7: Complete Example

Here's a complete example that computes the address, checks if it exists, and creates it if needed:

import { 
  createWalletClient, 
  createPublicClient, 
  http, 
  custom,
  encodeAbiParameters,
  keccak256,
  toBytes,
} from 'viem';
import { bsc } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
 
const PROXY_WALLET_FACTORY_ADDRESS = '0xB99159aBF0bF59a512970586F38292f8b9029924' as const;
 
const PROXY_WALLET_FACTORY_ABI = [
  {
    inputs: [{ internalType: 'address', name: 'user', type: 'address' }],
    name: 'computeProxyAddress',
    outputs: [{ internalType: 'address', name: '', type: 'address' }],
    stateMutability: 'view',
    type: 'function',
  },
  {
    inputs: [
      { internalType: 'address', name: 'paymentToken', type: 'address' },
      { internalType: 'uint256', name: 'payment', type: 'uint256' },
      { internalType: 'address payable', name: 'paymentReceiver', type: 'address' },
      {
        components: [
          { internalType: 'uint8', name: 'v', type: 'uint8' },
          { internalType: 'bytes32', name: 'r', type: 'bytes32' },
          { internalType: 'bytes32', name: 's', type: 'bytes32' },
        ],
        internalType: 'struct SafeProxyFactory.Sig',
        name: 'createSig',
        type: 'tuple',
      },
    ],
    name: 'createProxy',
    outputs: [],
    stateMutability: 'nonpayable',
    type: 'function',
  },
  {
    inputs: [],
    name: 'domainSeparator',
    outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
    stateMutability: 'view',
    type: 'function',
  },
  {
    inputs: [],
    name: 'CREATE_PROXY_TYPEHASH',
    outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
    stateMutability: 'view',
    type: 'function',
  },
] as const;
 
async function getOrCreateProxyWallet(eoaAddress: `0x${string}`): Promise<`0x${string}`> {
  // Setup clients
  const publicClient = createPublicClient({
    chain: bsc,
    transport: http(),
  });
 
  // For browser: use custom(window.ethereum)
  // For Node.js: use privateKeyToAccount
  const account = privateKeyToAccount('0x...' as `0x${string}`);
  const walletClient = createWalletClient({
    account,
    chain: bsc,
    transport: http(),
  });
 
  // Compute proxy address
  const proxyAddress = await publicClient.readContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: PROXY_WALLET_FACTORY_ABI,
    functionName: 'computeProxyAddress',
    args: [eoaAddress],
  });
 
  // Check if wallet already exists
  const code = await publicClient.getBytecode({ address: proxyAddress });
  if (code && code !== '0x' && code.length > 2) {
    console.log('Using existing proxy wallet:', proxyAddress);
    return proxyAddress;
  }
 
  // Create new proxy wallet
  console.log('Creating new proxy wallet...');
 
  // Get domain separator and typehash
  const [domainSeparator, createProxyTypehash] = await Promise.all([
    publicClient.readContract({
      address: PROXY_WALLET_FACTORY_ADDRESS,
      abi: PROXY_WALLET_FACTORY_ABI,
      functionName: 'domainSeparator',
    }),
    publicClient.readContract({
      address: PROXY_WALLET_FACTORY_ADDRESS,
      abi: PROXY_WALLET_FACTORY_ABI,
      functionName: 'CREATE_PROXY_TYPEHASH',
    }),
  ]);
 
  // Prepare payment parameters (zero for no payment)
  const paymentToken = '0x0000000000000000000000000000000000000000' as `0x${string}`;
  const payment = 0n;
  const paymentReceiver = '0x0000000000000000000000000000000000000000' as `0x${string}`;
 
  // Create struct hash
  const structHash = keccak256(
    encodeAbiParameters(
      [
        { type: 'bytes32' },
        { type: 'address' },
        { type: 'address' },
        { type: 'uint256' },
        { type: 'address' },
      ],
      [createProxyTypehash, eoaAddress, paymentToken, payment, paymentReceiver]
    )
  );
 
  // Create message hash
  const message = keccak256(
    encodeAbiParameters(
      [{ type: 'bytes32' }, { type: 'bytes32' }, { type: 'bytes32' }],
      ['0x1901' as `0x${string}`, domainSeparator, structHash]
    )
  );
 
  // Sign message
  const signature = await walletClient.signMessage({
    message: { raw: toBytes(message) },
  });
 
  // Split signature
  const r = `0x${signature.slice(2, 66)}` as `0x${string}`;
  const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
  const v = parseInt(signature.slice(130, 132), 16);
 
  // Create proxy
  const hash = await walletClient.writeContract({
    address: PROXY_WALLET_FACTORY_ADDRESS,
    abi: PROXY_WALLET_FACTORY_ABI,
    functionName: 'createProxy',
    args: [paymentToken, payment, paymentReceiver, { v, r, s }],
  });
 
  // Wait for confirmation
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log('Proxy wallet created in transaction:', receipt.transactionHash);
 
  // Verify creation
  const newCode = await publicClient.getBytecode({ address: proxyAddress });
  if (!newCode || newCode === '0x' || newCode.length <= 2) {
    throw new Error('Failed to create proxy wallet');
  }
 
  console.log('Proxy wallet address:', proxyAddress);
  return proxyAddress;
}
 
// Usage
const eoaAddress = '0x...' as `0x${string}`;
const proxyWallet = await getOrCreateProxyWallet(eoaAddress);
console.log('Proxy wallet ready:', proxyWallet);

Important Notes

  1. Gas Fees: Creating a proxy wallet requires a transaction on BSC, so you'll need BNB for gas fees.

  2. One Wallet Per EOA: Each EOA can only have one proxy wallet. The address is deterministically computed, so calling computeProxyAddress will always return the same address for the same EOA.

  3. Address Usage in API:

    • Authentication (prob_address): Always use your EOA address
    • Order maker field: Use your proxy wallet address
    • Order signer field: Use your EOA address (signs the order)
    • Order owner field: Use your proxy wallet address
  4. Deterministic Address: The proxy wallet address is deterministically computed from your EOA address using computeProxyAddress. This means the address is predictable and can be computed before deployment.

  5. Signature Required: Creating a proxy wallet requires an EIP-712 signature from your EOA. The signature authorizes the creation of the proxy wallet.

Next Steps

Once you have your proxy wallet address, proceed to the Getting Started Guide to learn how to authenticate and interact with the Orderbook API.