Withdraw from Wallet - Self-Custody MPC Hot (Simple)
Overview
The simple withdrawal flow for self-custody MPC hot wallets enables you to build, sign, and send transactions, all in 1 call, using the BitGo JavaScript SDK. The simple flow suffices for most use cases. If you require more granular control, see the integration guide for the advanced flow.
The simple flow also supports sending to many recipients in 1 transaction, lowering the aggregate amount of blockchain fees when compared to creating multiple transactions. Sending to many aligns with the simple transaction flow, where in 1 call you build, sign, and send a half-signed transaction to BitGo. BitGo then uses the BitGo key to create a fully signed send-to-many transaction.
Just like with the advanced flow, you can configure wallet policies to require approvals on withdrawals. Once the transaction is half signed and approved, you send it to BitGo for final signing and broadcasting to the blockchain.
Prerequisites
1. Build, Sign, and Send Transaction
Build and sign the transaction and send it to BitGo, all in 1 call.
Additionally, you can create a send-to-many transaction that automatically invokes a batcher contract to send assets to multiple recipients. Currently, send-to-many transaction are available for ETH and MATIC and are limited to 200 recipients.
Endpoints:
import * as dotenv from "dotenv";
import {EnvironmentName} from "bitgo";
import {BitGoAPI} from "@bitgo/sdk-api";
import {Hteth} from "@bitgo/sdk-coin-eth";
dotenv.config();
const config = {
USERNAME: process.env.USERNAME as string,
PASSWORD: process.env.PASSWORD as string,
ENV: process.env.ENV as EnvironmentName,
OTP: process.env.OTP as string,
ENTERPRISE_ID: process.env.ENTERPRISE_ID as string,
WALLET_ID: process.env.WALLET_ID as string,
WALLET_PASS_PHRASE: process.env.WALLET_PASS_PHRASE as string
};
const bitgo = new BitGoAPI({env: config.ENV});
bitgo.register("hteth", Hteth.createInstance);
const coin = bitgo.coin("hteth");
async function auth() {
await bitgo.authenticate({
username: config.USERNAME,
password: config.PASSWORD,
otp: config.OTP,
});
await bitgo.lock();
await bitgo.unlock({otp: "000000", duration: 3600});
}
async function main() {
await auth();
const destinatonAddress = "0x1037c88b10fbd0754b9fbd3ba7be41fe7cb61f59";
const wallet = await coin.wallets().get({id: config.WALLET_ID});
const sendAmount = wallet.balanceString() ?? 0;
const res = await wallet.sendMany({
walletPassphrase: config.WALLET_PASS_PHRASE,
recipients: [{address: destinatonAddress, amount: sendAmount}],
type: "transfer",
});
console.log(res);
}
main().catch((err) => console.error(err));
import * as dotenv from "dotenv";
import {EnvironmentName} from "bitgo";
import {BitGoAPI} from "@bitgo/sdk-api";
import {Hteth} from "@bitgo/sdk-coin-eth";
dotenv.config();
const config = {
USERNAME: process.env.USERNAME as string,
PASSWORD: process.env.PASSWORD as string,
ENV: process.env.ENV as EnvironmentName,
OTP: process.env.OTP as string,
ENTERPRISE_ID: process.env.ENTERPRISE_ID as string,
WALLET_ID: process.env.WALLET_ID as string,
WALLET_PASS_PHRASE: process.env.WALLET_PASS_PHRASE as string
};
const bitgo = new BitGoAPI({env: config.ENV});
bitgo.register("hteth", Hteth.createInstance);
const coin = bitgo.coin("hteth");
async function auth() {
await bitgo.authenticate({
username: config.USERNAME,
password: config.PASSWORD,
otp: config.OTP,
});
await bitgo.lock();
await bitgo.unlock({otp: "000000", duration: 3600});
}
async function main() {
await auth();
const destinatonAddress = "0x1037c88b10fbd0754b9fbd3ba7be41fe7cb61f59";
const wallet = await coin.wallets().get({id: config.WALLET_ID});
const totalAmount = wallet.balanceString();
let totalAmountAsNumber = +totalAmount;
const sendAmount = (totalAmountAsNumber / 200) - 10000; //desired amount to send
const recipients = Array(10).fill( //Construct the required recipients array
{
amount: String(sendAmount),
address: destinatonAddress
}, 0, 10);
console.log(JSON.stringify(recipients))
const res = await wallet.sendMany({
walletPassphrase: config.WALLET_PASS_PHRASE,
recipients: recipients,
type: "transfer",
isTss: true,
});
console.log(`Batch Transfer for coin: ${coin} completed. TxId: ${res.txid}`)
console.log(JSON.stringify(res));
console.log("Batch transfers completed.");
}
main().catch((err) => console.error(err));
Step Result
BitGo uses the data you pass to build a half-signed transaction. The transaction remains in a pending-approval status until a wallet admin approves it.
{
"transfer": {
"entries": [
{
"address": "0x1a52b70e708f4aec9d92260aa035b3c71e51df84",
"wallet": "665e33f768f9b4b1cc2ca87a6b87cdfb",
"value": -1,
"valueString": "-1"
},
{
"address": "0xe2c5b494162bd9033283af31e35be27c6ee4bbf7",
"value": 1,
"valueString": "1"
}
],
"id": "665e352e29c225381225e8f5f74f429d",
"coin": "hteth",
"wallet": "665e33f768f9b4b1cc2ca87a6b87cdfb",
"walletType": "hot",
"enterprise": "66325011d4b1ff58feae3b311d669aae",
"organization": "66325011d4b1ff58feae3b76c2d30987",
"txid": "0x43f7bef95dd02eea33d447afc625908a429902fbd35a35f0da6b7befddc69d9b",
"txidType": "transactionHash",
"txRequestId": "2c7e52a6-a422-49d3-876c-c050fb4cbf36",
"height": 999999999,
"heightId": "999999999-665e352e29c225381225e8f5f74f429d",
"date": "2024-06-03T21:27:10.071Z",
"type": "send",
"value": -1,
"valueString": "-1",
"intendedValueString": "-1",
"baseValue": -1,
"baseValueString": "-1",
"baseValueWithoutFees": -1,
"baseValueWithoutFeesString": "-1",
"feeString": "0",
"payGoFee": 0,
"payGoFeeString": "0",
"usd": 0,
"usdRate": 3775.4311802956,
"state": "signed",
"instant": false,
"isReward": false,
"isUnlock": false,
"isFee": false,
"tags": [
"665e33f768f9b4b1cc2ca87a6b87cdfb",
"66325011d4b1ff58feae3b311d669aae"
],
"history": [
{
"date": "2024-06-03T21:27:10.071Z",
"action": "signed"
},
{
"date": "2024-06-03T21:27:10.020Z",
"user": "660d7c9fa6fbf0d00b415146cf70405a",
"action": "created"
}
],
"signedDate": "2024-06-03T21:27:10.071Z",
"coinSpecific": {
"isMev": false
},
"metadata": [],
"signedTime": "2024-06-03T21:27:10.071Z",
"createdTime": "2024-06-03T21:27:10.020Z"
},
"txRequest": {
"apiVersion": "full",
"txRequestId": "2c7e52a6-a422-49d3-876c-c050fb4cbf36",
"walletId": "665e33f768f9b4b1cc2ca87a6b87cdfb",
"walletType": "hot",
"version": 10,
"enterpriseId": "66325011d4b1ff58feae3b311d669aae",
"organizationId": "66325011d4b1ff58feae3b76c2d30987",
"userId": "660d7c9fa6fbf0d00b415146cf70405a",
"initiatedBy": "660d7c9fa6fbf0d00b415146cf70405a",
"updatedBy": "660d7c9fa6fbf0d00b415146cf70405a",
"policiesChecked": true,
"date": "2024-06-03T21:27:10.203Z",
"createdDate": "2024-06-03T21:27:01.066Z",
"intent": {
"intentType": "payment",
"recipients": [
{
"address": {
"address": "0xE2C5b494162bd9033283Af31e35Be27C6Ee4Bbf7"
},
"amount": {
"value": "1",
"symbol": "hteth"
}
}
],
"feeOptions": {
"baseFee": "1005178132",
"gasUsedRatio": "0.2743939",
"safeLowMinerTip": "10896128",
"normalMinerTip": "22751361",
"standardMinerTip": "498266940",
"fastestMinerTip": "437486468",
"ludicrousMinerTip": "1500000000",
"maxPriorityFeePerGas": "498266940",
"maxFeePerGas": "2508623204"
},
"senderAddressIndex": 0
},
"intents": [
{
"intentType": "payment",
"recipients": [
{
"address": {
"address": "0xE2C5b494162bd9033283Af31e35Be27C6Ee4Bbf7"
},
"amount": {
"value": "1",
"symbol": "hteth"
}
}
],
"feeOptions": {
"baseFee": "1005178132",
"gasUsedRatio": "0.2743939",
"safeLowMinerTip": "10896128",
"normalMinerTip": "22751361",
"standardMinerTip": "498266940",
"fastestMinerTip": "437486468",
"ludicrousMinerTip": "1500000000",
"maxPriorityFeePerGas": "498266940",
"maxFeePerGas": "2508623204"
},
"senderAddressIndex": 0
}
],
"state": "delivered",
"latest": true,
"transactions": [
{
"state": "delivered",
"signatureShares": [],
"commitmentShares": [],
"unsignedTx": {
"parsedTx": {
"minerFee": "0",
"spendAmount": "1",
"spendAmounts": [
{
"coinName": "hteth",
"amountString": "1"
}
],
"outputs": [
{
"address": "0xe2c5b494162bd9033283af31e35be27c6ee4bbf7",
"wallets": [],
"enterprises": [],
"valueString": "1",
"coinName": "hteth"
}
],
"inputs": [
{
"value": 1,
"address": "0x1a52b70e708f4aec9d92260aa035b3c71e51df84",
"valueString": "1"
}
],
"hasUnvalidatedData": false
},
"serializedTxHex": "02ed82426880841db2f33c8495868d648301637894e2c5b494162bd9033283af31e35be27c6ee4bbf70180c0808080",
"signableHex": "02ea82426880841db2f33c8495868d648301637894e2c5b494162bd9033283af31e35be27c6ee4bbf70180c0",
"feeInfo": {
"fee": 136813501552000,
"feeString": "136813501552000"
},
"derivationPath": "m"
},
"txHash": "0x43f7bef95dd02eea33d447afc625908a429902fbd35a35f0da6b7befddc69d9b",
"signedTx": {
"id": "0x43f7bef95dd02eea33d447afc625908a429902fbd35a35f0da6b7befddc69d9b",
"tx": "0x02f86d82426880841db2f33c8495868d648301637894e2c5b494162bd9033283af31e35be27c6ee4bbf70180c080a0631c4464bcb29519ac7e3998955c1bda374d058868557661c3711d531cb37e62a04836e3cf75969a2ddd9c38981d1b6fade02733059544c74784f9a85e62da8719"
},
"updatedDate": "2024-06-03T21:27:10.215Z",
"createdDate": "2024-06-03T21:27:01.072Z"
}
],
"messages": [],
"isCanceled": false
},
"txid": "0x43f7bef95dd02eea33d447afc625908a429902fbd35a35f0da6b7befddc69d9b",
"tx": "0x02f86d82426880841db2f33c8495868d648301637894e2c5b494162bd9033283af31e35be27c6ee4bbf70180c080a0631c4464bcb29519ac7e3998955c1bda374d058868557661c3711d531cb37e62a04836e3cf75969a2ddd9c38981d1b6fade02733059544c74784f9a85e62da8719",
"status": "signed"
}
2. Approve Transaction (Optional)
Note: You can't approve your own transactions - another admin must approve them.
Endpoint: Update Pending Approval
export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export WALLET_PASSPHRASE="<YOUR_WALLET_PASSPHRASE>"
curl -X PUT \
https://$BITGO_EXPRESS_HOST/api/v2/$COIN/pendingApprovals/$APPROVAL_ID \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"state": "approved",
"walletPassphrase": "'"$WALLET_PASSPHRASE"'"
}'
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": "66b66201c45ab3125fc2d8a77b541d12",
"coin": "tsol",
"wallet": "669569464cec3c82c9b524faf0d42087",
"wallets": [],
"enterprise": "6672220dd9e61ce3d62e9e2d710cbd6f",
"organization": "66722210d9e61ce3d62e9e997100b40a",
"bitgoOrg": "BitGo Trust",
"creator": "6672175d79369c8ad556deaca06abeea",
"createDate": "2024-08-09T18:37:53.078Z",
"approvedDate": "2024-08-09T18:38:47.371Z",
"info": {
"type": "transactionRequestFull",
"transactionRequestFull": {
"txRequestId": "0b259f23-d5a8-47d0-8666-47a3df07e244",
"videoApprovers": [],
"intent": {
"intentType": "payment",
"recipients": [
{
"address": {
"address": "ESda41265eU4typ7Q7MFnBuaYUvV3rYsJyrGQzqo6YZn"
},
"amount": {
"value": "500000",
"symbol": "tsol"
}
}
]
}
}
},
"approvers": [],
"state": "approved",
"scope": "wallet",
"userIds": [
"6672175d79369c8ad556deaca06abeea",
"6672292d89bd038e41cfb93b6ee482f1"
],
"approvalsRequired": 1,
"singleRunResults": [],
"txRequestId": "0b259f23-d5a8-47d0-8666-47a3df07e244",
"resolvers": [
{
"user": "6672292d89bd038e41cfb93b6ee482f1",
"date": "2024-08-09T18:38:45.245Z",
"resolutionType": "pending",
"resolutionAction": "approve"
}
],
"policyEvaluationId": "8dc3ffdd-50ea-4ab6-9742-963a12eb4f6c",
"actions": [
{
"id": "d0bcc3af-b341-4b1c-9c7e-723ff4bc7259",
"status": "COMPLETE",
"name": "approvals.customer.walletAdmin",
"parameters": {
"minRequired": "1",
"userIds": []
},
"resolvers": [
{
"user": "6672292d89bd038e41cfb93b6ee482f1",
"date": "2024-08-09T18:38:45.245Z",
"resolutionType": "pending",
"resolutionAction": "approve"
}
],
"approvers": []
}
],
"resolutionOrder": [
{
"actions": [
"d0bcc3af-b341-4b1c-9c7e-723ff4bc7259"
]
}
]
}
{
"id": "66b662a6229e503a24b4f1eca9787cb7",
"coin": "tsol",
"wallet": "669569464cec3c82c9b524faf0d42087",
"wallets": [],
"enterprise": "6672220dd9e61ce3d62e9e2d710cbd6f",
"organization": "66722210d9e61ce3d62e9e997100b40a",
"bitgoOrg": "BitGo Trust",
"creator": "6672175d79369c8ad556deaca06abeea",
"createDate": "2024-08-09T18:40:38.927Z",
"approvedDate": "2024-08-09T18:41:06.681Z",
"info": {
"type": "transactionRequestFull",
"transactionRequestFull": {
"txRequestId": "ae8d598e-f084-4ecb-a3b3-b81ff68e67e4",
"videoApprovers": [],
"intent": {
"intentType": "payment",
"recipients": [
{
"address": {
"address": "ESda41265eU4typ7Q7MFnBuaYUvV3rYsJyrGQzqo6YZn"
},
"amount": {
"value": "500000",
"symbol": "tsol"
}
},
{
"address": {
"address": "BaKnSGoQCrTU6NJhVT8tdaMceSFmb7RGv74AEebfqJcg"
},
"amount": {
"value": "500000",
"symbol": "tsol"
}
}
]
}
}
},
"approvers": [],
"state": "approved",
"scope": "wallet",
"userIds": [
"6672175d79369c8ad556deaca06abeea",
"6672292d89bd038e41cfb93b6ee482f1"
],
"approvalsRequired": 1,
"singleRunResults": [],
"txRequestId": "ae8d598e-f084-4ecb-a3b3-b81ff68e67e4",
"resolvers": [
{
"user": "6672292d89bd038e41cfb93b6ee482f1",
"date": "2024-08-09T18:41:04.386Z",
"resolutionType": "pending",
"resolutionAction": "approve"
}
],
"policyEvaluationId": "cc99d8a1-dd13-479b-b300-77f6a1cefb64",
"actions": [
{
"id": "a41f81a2-bc8e-49fa-b28a-e6215746161e",
"status": "COMPLETE",
"name": "approvals.customer.walletAdmin",
"parameters": {
"minRequired": "1",
"userIds": []
},
"resolvers": [
{
"user": "6672292d89bd038e41cfb93b6ee482f1",
"date": "2024-08-09T18:41:04.386Z",
"resolutionType": "pending",
"resolutionAction": "approve"
}
],
"approvers": []
}
],
"resolutionOrder": [
{
"actions": [
"a41f81a2-bc8e-49fa-b28a-e6215746161e"
]
}
]
}
Next
You can view your completed withdrawal in BitGo or on a blockchain explorer.
See Also
Updated 21 days ago