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 viemStep-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
-
Gas Fees: Creating a proxy wallet requires a transaction on BSC, so you'll need BNB for gas fees.
-
One Wallet Per EOA: Each EOA can only have one proxy wallet. The address is deterministically computed, so calling
computeProxyAddresswill always return the same address for the same EOA. -
Address Usage in API:
- Authentication (
prob_address): Always use your EOA address - Order
makerfield: Use your proxy wallet address - Order
signerfield: Use your EOA address (signs the order) - Order
ownerfield: Use your proxy wallet address
- Authentication (
-
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. -
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.