Set Up Standby Wallet
Overview
The standby wallet is a self-custody hot wallet that serves as an intermediate buffer between your custody wallet (cold storage) and your deposit/withdraw wallet (daily operations). This wallet holds an intermediate amount of funds and requires admin approval for withdrawals.
In the custody starter architecture, this wallet:
- Holds an intermediate amount of assets (more than deposit/withdraw, less than custody).
- Requires admin approval for all withdrawals.
- Only allows withdrawals to the custody wallet and deposit/withdraw wallet through whitelist.
Prerequisites
Create Standby Wallet
Generate a self-custody hot wallet using BitGo Express or the SDK. This creates the wallet and keys in one step.
Endpoint: Generate Wallet
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ENTERPRISE="<YOUR_ENTERPRISE_ID>"
export PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"
export LABEL="<YOUR_DESIRED_WALLET_NAME>"
curl -X POST \
http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/generate \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"enterprise": "'"$ENTERPRISE"'",
"passphrase": "'"$PASSPHRASE"'",
"label": "'"$LABEL"'",
"multisigType": "onchain",
"type": "hot"
}'const { BitGo } = require('bitgo');
const accessToken = '<YOUR_ACCESS_TOKEN>';
const bitgo = new BitGo({
accessToken: accessToken,
env: 'prod',
});
async function createStandbyWallet() {
const newWallet = await bitgo.coin('<ASSET_ID>').wallets().generateWallet({
label: 'Standby Wallet - Hot',
passphrase: '<YOUR_WALLET_PASSPHRASE>',
enterprise: '<YOUR_ENTERPRISE_ID>',
multisigType: 'onchain',
type: 'hot'
});
console.log('Standby Wallet Created:');
console.log('Wallet ID:', newWallet.wallet.id());
console.log('Receive Address:', newWallet.wallet.receiveAddress());
// IMPORTANT: Save the backup keychain securely
console.log('Backup Keychain (SAVE SECURELY):', newWallet.backupKeychain);
}
createStandbyWallet();Step Result
NoteThis response contains critical key material. Save the backup keychain in a secure place.
{
"wallet": {
"id": "6849948ac0623f81f74f63dbd8351d4f",
"users": [
{
"user": "62ab90e06dfda30007974f0a52a12995",
"permissions": ["admin", "spend", "view"]
}
],
"coin": "btc",
"label": "Standby Wallet - Hot",
"m": 2,
"n": 3,
"keys": [
"68499487e6c77351bd3bbf04281fa8bb",
"68499487cd07fc57de18f6481baa1903",
"6849948902be620840a384c148d19979"
],
"enterprise": "62c5ae8174ac860007aff138a2d74df7",
"approvalsRequired": 1,
"isCold": false,
"type": "hot",
"multisigType": "onchain",
"receiveAddress": {
"address": "bc1p..."
}
},
"userKeychain": {
"id": "68499487e6c77351bd3bbf04281fa8bb",
"pub": "xpub661MyMwAqRbcEst4tb4F36AfvoFtAy7U9viB7zapRqNnXhPknsPwNqhxpD1CqMGSGhq3hDMKQR1Br8gGxYygoR6SGic3XdJoTEzM5v9wyFy",
"source": "user",
"encryptedPrv": "{...}"
},
"backupKeychain": {
"id": "68499487cd07fc57de18f6481baa1903",
"pub": "xpub661MyMwAqRbcEfnfBkVRk9BB1SrYaFR884ndYpmNXnci6U2wrAQCsFiD21c49Aq7EvtK6QFzDzMwmFKVqi1bTL7kmuCEJ78bFn9Rq6NyLDV",
"source": "backup",
"encryptedPrv": "{...}"
},
"bitgoKeychain": {
"id": "6849948902be620840a384c148d19979",
"pub": "xpub661MyMwAqRbcGCXsEAmctkLstGa92f5ugiD3hvCL3Wyt8BqHYGCVsL2x6PgNgJeq8Aabtz92sMzq4Ezac459QkDmxowKqoL35gNJXEJDdeo",
"source": "bitgo",
"isBitGo": true
}
}
NoteSave the wallet ID and backup keychain from the response. You need the wallet ID to configure whitelist policies.
Configure Admin Approval Policy
Create a policy that requires admin approval for all withdrawals from the standby wallet.
Endpoint: Create Policy Rule
export ENTERPRISE_ID="<YOUR_ENTERPRISE_ID>"
export TOUCHPOINT="wallet.segregated.transfer"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export APPROVER_USER_ID="<APPROVER_USER_ID>"
export STANDBY_WALLET_ID="<STANDBY_WALLET_ID>"
curl -X POST \
"https://app.bitgo.com/api/policy/v1/enterprises/$ENTERPRISE_ID/touchpoints/$TOUCHPOINT/rules" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"name": "Standby Wallet - Require Admin Approval",
"adminOnly": false,
"clauses": [
{
"conditions": [],
"actions": [
{
"name": "approvals.customer.enterpriseUser",
"parameters": {
"userIds": ["'"$APPROVER_USER_ID"'"],
"minRequired": "1",
"initiatorIsAllowedToApprove": false
}
}
]
}
],
"filteringConditions": [
{
"name": "wallet.ids",
"parameters": {
"walletId": ["'"$STANDBY_WALLET_ID"'"]
}
}
]
}'Step Result
{
"id": "684a1b2c3d4e5f6g7h8i9j0m",
"name": "Standby Wallet - Require Admin Approval",
"adminOnly": false,
"touchpoint": "wallet.segregated.transfer",
"enterprise": "62c5ae8174ac860007aff138a2d74df7",
"clauses": [
{
"conditions": [],
"actions": [
{
"name": "approvals.customer.enterpriseUser",
"parameters": {
"userIds": ["62ab90e06dfda30007974f0a52a12995"],
"minRequired": "1",
"initiatorIsAllowedToApprove": false
}
}
]
}
],
"filteringConditions": [
{
"name": "wallet.ids",
"parameters": {
"walletId": ["6849948ac0623f81f74f63dbd8351d4f"]
}
}
]
}Withdrawal Flow
Withdrawals from self-custody hot wallets can use either the simple or manual flow. The simple flow builds, signs, and sends a transaction in one call. The manual flow provides granular control with separate build, sign, and send steps.
Overview
The simple withdrawal flow for self-custody multisignature hot wallets enables you to build, sign, and send transactions, all in one call, using BitGo Express. This flow suffices for most mulsitignature use cases.
1. Build, Sign, and Send Transaction
Build and sign the transaction and send it to BitGo, all in one call.
Endpoint: Send Transaction
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"
export ADDRESS="<DESTINATION_ADDRESS>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"
curl -X POST \
http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/sendcoins \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"address": "'"$ADDRESS"'",
"amount": "'"$AMOUNT"'",
"walletPassphrase": "'"$WALLET_PASSPHRASE"'",
"type": "transfer",
"txFormat": "psbt"
}'const tx = await wallet.send({
address: `<DESTINATION_ADDRESS>`,
amount: `<AMOUNT>`,
walletPassphrase: process.env.PASSWORD,
txFormat: `psbt`,
type: `transfer`
});
Note: If you are building transactions for a UTXO asset in quick succession, BitGo recommends reserving unspents by passing thereservationandexpireTimeparameters. Reserving unspents avoids errors by ensuring the UTXO are not included in subsequent builds.
Step Result
BitGo uses the data you pass to build a half-signed transaction. If your withdrawal does not require approval, BitGo applies the final signature using the BitGo key and broadcasts the transaction to the blockchain.
{
"transfer": {
"entries": [
{
"address": "2N1poiHTi5ur8hz5QBhNoy88bYzqrWYvBbV",
"wallet": "6553e933288be490293ae748efafeaaf",
"value": -100000,
"valueString": "-100000"
},
{
"address": "2Myx8nY8ReERqUwu9H96Lb2K4yYjs3xY8GH",
"value": 10000,
"valueString": "10000",
"isChange": false,
"isPayGo": false
}
],
"id": "6553ee12d5a49ecc9baccdcbe0563448",
"coin": "tbtc4",
"wallet": "6553e933288be490293ae748efafeaaf",
"walletType": "hot",
"txid": "e7648c85edac7f9870e511b4ef95b62b1878556791bd52ac715cb2cd4b466e6f",
"state": "signed"
},
"txid": "e7648c85edac7f9870e511b4ef95b62b1878556791bd52ac715cb2cd4b466e6f",
"status": "signed"
}2. Approve Transaction (Optional)
Note: If you configure an approval requirement for withdrawals, you cannot approve your own transactions - another admin must approve them.
Endpoint: Update Pending Approval
export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export OTP="<YOUR_OTP>"
curl -X PUT \
https://app.bitgo-test.com/api/v2/pendingApprovals/$APPROVAL_ID \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"state": "approved",
"otp": "'"$OTP"'"
}'const baseCoin = this.bitgoSDK.coin(initialPendingApproval.coin);
const pendingApproval = await baseCoin.pendingApprovals().get({ id: initialPendingApproval.id });
const result = await pendingApproval.approve(params);Step Result
Once approved, BitGo rebuilds the half-signed transaction, applying the most up-to-date fees. BitGo then applies the final signature using the BitGo key and broadcasts the transaction to the blockchain.
{
"id": "655686880765186f0b3e9e88e1bdd0f4",
"coin": "tbtc4",
"wallet": "6553e933288be490293ae748efafeaaf",
"state": "approved",
"approvalsRequired": 1,
"resolvers": [
{
"user": "627ff9325a5c1b0007c05a40d15e1522",
"date": "2023-11-16T21:33:24.644Z",
"resolutionType": "pending"
}
]
}Next
See Also
- Custody Starter Architecture Overview
- Create Wallets
- Policies Overview
- API Reference: Build a Transaction
- API Reference: Create transaction request
- API Reference: Create a signature share for the transaction request
- API Reference: Get transaction requests by wallet
- API Reference: Send Half-Signed Transaction
- API Reference: Send to Many
- API Reference: Send Transaction
- API Reference: Sign MPC transaction
- API Reference: Sign Transaction
- API Reference: Update Pending Approval
Updated 1 day ago