Withdraw Crypto On Chain

Overview

You can build on-chain crypto withdrawals from your Go Account by submitting transaction details to BitGo. BitGo uses the data you pass to construct an unsigned transaction that wallet admins can approve. After video verification, a BitGo Bank & Trust operator signs the transaction with the user key. Then a different BitGo Bank & Trust operator downloads and signs the transaction in the BitGo Offline Vault Console (OVC). BitGo then uploads and broadcasts the transaction.

Prerequisites

1. Build Transaction

To withdraw assets from your Go Account to an on-chain address, you must build a transaction and send the assets.

Endpoint: Build a Transaction

export COIN="<CRYPTO_ASSET_ID>" # Crypto asset IDs in Go Accounts always start with "ofc" (such as ofctbtc)
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"
export ADDRESS="<DESTINATION_ADDRESS>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/build \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
      {
        "amount": "'"$AMOUNT"'",
        "address": "'"$ADDRESS"'"
      }
    ]
}'
let params = {
  recipients: [
    {
      amount: 1000,
      address: '2NFfxvXpAWjKng7enFougtvtxxCJ2hQEMo4',
    },
  ],
};
wallet.prebuildTransaction(params).then(function (transaction) {
  // print transaction details
  console.dir(transaction);
});

Step Result

{
  "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"1\"}],\"fromAccount\":\"62c5b1de8a0c5200071c9a603bdbadc5\",\"nonce\":\"d8e5d930-f827-42b1-ad85-40544dad1be6\",\"timestamp\":\"2025-06-25T17:45:56.394Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}",
  "feeInfo": { "feeString": "0" },
  "coin": "ofc",
  "token": "ofctbtc"
}

2. Authenticate Transaction

Use your Go Account passphrase to authenticate the transaction. To ensure your passphrase isn't passed over the Internet, so you must use BitGo Express in external-signing mode or the JavaScript SDK. If you're using Express in external signing-mode, configure your Go Account passphrase as the WALLET_<walletId>_PASSPHRASE environment variable.

export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export WALLET_ID="<YOUR_WALLET_ID>"
export WALLET_PASSPHRASE="<YOUR_GO_ACCOUNT_PASSPHRASE>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/ofc/signPayload \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
   "walletId": "'"$WALLET_ID"'",
   "walletPassphrase": "'"$WALLET_PASSPHRASE"'",
   "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"190000\"}],\"fromAccount\":\"67ab85b5190bf872e84da2b6e527d9f3\",\"nonce\":\"77d91a11-a7fa-4ad7-91e5-f054328717f9\",\"timestamp\":\"2025-06-26T15:02:06.924Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}"
}
// assuming that "bitgo" is an already initialized bitgo instance
  const wallet = await bitgo.coin('ofc').wallets().get({ id: goAccountId });

  if (wallet === undefined) {
    throw new Error(`Could not find OFC wallet ${goAccountId}`);
  }

  const walletPassphrase = "<YOUR_WALLET_PASSPHRASE>";
  const tradingAccount = wallet.toTradingAccount();
  const stringifiedPayload = JSON.stringify(<payload_returned_by_tx/build_api>);
  const signature = await tradingAccount.signPayload({
    payload: stringifiedPayload,
    walletPassphrase,
  });

Step Result

{
  "coin": "ofctbtc",
  "recipients": [
    {
      "address": "tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t",
      "amount": "1"
    }
  ],
  "fromAccount": "62c5b1de8a0c5200071c9a603bdbadc5",
  "nonce": "77d91a11-a7fa-4ad7-91e5-f054328717f9",
  "timestamp": "2025-06-26T15:02:06.924Z",
  "feeString": "0",
  "shortCircuitBlockchainTransfer": false,
  "isIntraJXTransfer": false,
  "payload": "{\"coin\":\"ofctbtc\",\"recipients\":[{\"address\":\"tb1q5mwcr2749nnm5c5d6rzx5h92l2u9ex5jzygzd39vfv9qerj5fmjqwlgy4t\",\"amount\":\"1\"}],\"fromAccount\":\"62c5b1de8a0c5200071c9a603bdbadc5\",\"nonce\":\"77d91a11-a7fa-4ad7-91e5-f054328717f9\",\"timestamp\":\"2025-06-26T15:02:06.924Z\",\"feeString\":\"0\",\"shortCircuitBlockchainTransfer\":false,\"isIntraJXTransfer\":false}",
  "signature": "20dda1e9558adb297f69020f94cbf4955fabec73478506347dbb5ac8e73b506fc908bbc48bde75b339b99f5de808ed34b2017055c9df198fd1f8b4e524899f9583"
}

3. Send Transaction

Endpoint: Send Half-Signed Transaction

export COIN="<CRYPTO_ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ADDRESS="<DESTINATION_ADDRESS>"
export AMOUNT="<AMOUNT_IN_BASE_UNITS>"

curl -X POST \
  https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/tx/send \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "recipients": [
      {
        "address": "'"$ADDRESS"'",
        "amount": "'"$AMOUNT"'"
      }
    ],
    "halfSigned": {
      "payload": {
        "coin": "ofctbtc",
        "recipients": [
          {
            "address": "'"$ADDRESS"'",
            "amount": "'"$AMOUNT"'"
          }
        ],
        "fromAccount": "<WALLET_ID>",
        "nonce": "f4af25d0-a037-4afc-8ce6-d7f158d826d1",
        "timestamp": "2021-09-10T22:45:20.777Z",
        "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
        "idfVersion": 1,
        "idfUserId": "<USER_ID>",
        "feeString": "1000"
      },
      "signature": "<SIGNATURE>"
    }
  }'

Step Result

{
    "transfer": {
        "id": "<ID>",
        "coin": "ofctbtc",
        "wallet": "<WALLET_ID>",
        "walletType": "trading",
        "enterprise": "603ca88c44f82002560378421dc7a191",
        "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
        "height": 999999999,
        "heightId": "999999999-613be001f3de560006842d52251d5d49",
        "date": "2021-09-10T22:45:21.342Z",
        "type": "send",
        "value": -10000,
        "valueString": "-10000",
        "baseValue": -10000,
        "baseValueString": "-10000",
        "feeString": "0",
        "payGoFee": 0,
        "payGoFeeString": "0",
        "usd": -100,
        "usdRate": 1,
        "state": "signed",
        "instant": false,
        "isReward": false,
        "isFee": false,
        "tags": [
            "603ca8c01b83cb000656311787dab4cd",
            "603ca88c44f82002560378421dc7a191"
        ],
        "history": [
            {
                "date": "2021-09-10T22:45:21.340Z",
                "action": "signed"
            },
            {
                "date": "2021-09-10T22:45:21.216Z",
                "user": "5e4ed695ae2e542100336fb16e586019",
                "action": "created"
            }
        ],
        "signedDate": "2021-09-10T22:45:21.340Z",
        "entries": [
            {
                "address": "address",
                "wallet": "603ca8c01b83cb000656311787dab4cd",
                "value": -10000,
                "valueString": "-10000"
            },
            {
                "address": "bf6cc5252438dc85",
                "value": 10000,
                "valueString": "10000"
            }
        ],
        "signedTime": "2021-09-10T22:45:21.340Z",
        "createdTime": "2021-09-10T22:45:21.216Z"
    },
    "txid": "ddd6875812fa59f899a68059ff303bfa6d39fc350ee687c407383d0444b43d2b",
    "tx": {
    "transaction": {
      "coin": "ofctbtc",
      "recipients": [
        {
          "address": "<DESTINATION_ADDRESS>",
          "amount": "10000"
        }
      ],
      "fromAccount": "<WALLET_ID>",
      "nonce": "f4af25d0-a037-4afc-8ce6-d7f158d826d1",
      "timestamp": "2021-09-10T22:45:20.777Z",
      "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
      "idfVersion": 1,
      "idfUserId": "<USER_ID>",
      "feeString": "0"
    },
    "signature": "<SIGNATURE>",
    "idfVersion": 1,
    "idfSignedTimestamp": "2021-09-10T22:45:20.113Z",
    "idfUserId": "<USER_ID>"
  },
    "status": "signed"
}

4. Approve Transaction (Optional)

Note: If you configure an approval requirement for withdrawals, you can't approve your own transactions - another admin must approve them.

Endpoint: Update Pending Approval

export COIN="<CRYPTO_ASSET_ID>"
export APPROVAL_ID="<APPROVAL_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export OTP="<OTP>"

curl -X POST \
  http://api/v2/$COIN/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 unsigned transaction, applying the most up-to-date fees.

{
  "id": "655686880765186f0b3e9e88e1bdd0f4",
  "coin": "tbtc",
  "wallet": "6553e933288be490293ae748efafeaaf",
  "enterprise": "62c5ae8174ac860007aff138a2d74df7",
  "creator": "62ab90e06dfda30007974f0a52a12995",
  "createDate": "2023-11-16T21:15:52.703Z",
  "info": {
    "type": "transactionRequest",
    "transactionRequest": {
      "requestedAmount": "10000",
      "fee": 45242,
      "sourceWallet": "6553e933288be490293ae748efafeaaf",
      "policyUniqueId": "6553e933288be490293ae753",
      "recipients": [
        {
          "address": "2N3sBpM1RnWRMXnEVoUWnM7xtYzL756JE2Q",
          "amount": "10000",
          "_id": "655686880765186f0b3e9e8a"
        }
      ],
      "buildParams": {
        "recipients": [
          {
            "address": "2N3sBpM1RnWRMXnEVoUWnM7xtYzL756JE2Q",
            "amount": "10000"
          }
        ]
      },
      "coinSpecific": {
        "tbtc": {
          "txHex": "01000000000101eac5e7d68acfc2349672fb99094e79c2006e5fd663475c8f1eb6f29095970b740100000000ffffffff02102700000000000017a914747e6b7f5db53794b0fc01d95878e0f9b6db96738746c1020000000000220020efb9deaabeab5d62cecc363f24cd6deafcdd14d93d8734f7f01ed3953e732a640500483045022100cd6c221e4cefbb51aa82087d86e7d089b2473eb21ae809dba89eb9f13c4caf19022000f3b7f1b7fec2dc49cbfce35baa69ca37ab9ee8e51c779cde5b98a653f3e142010000695221029bde55661e4f359cf9ca2d1846c235e752f760ed6d65e2018f821253e78b3c722103f2b4a813ab79e4fb46edf3a6a87fb154a85b45810158e949f41a3fce3c9bf574210399a6d766f6d3a3843f8479446ee766b5745dc15c24d1db5149214d27987bd29453ae00000000"
        }
      },
      "validTransaction": "01000000000101eac5e7d68acfc2349672fb99094e79c2006e5fd663475c8f1eb6f29095970b740100000000ffffffff02102700000000000017a914747e6b7f5db53794b0fc01d95878e0f9b6db96738746c1020000000000220020efb9deaabeab5d62cecc363f24cd6deafcdd14d93d8734f7f01ed3953e732a640400483045022100cd6c221e4cefbb51aa82087d86e7d089b2473eb21ae809dba89eb9f13c4caf19022000f3b7f1b7fec2dc49cbfce35baa69ca37ab9ee8e51c779cde5b98a653f3e14201473044022002b246c43722fec49858caf6b0605a0d01a6b6c13c964b84bf272604fd3951780220324ee44ba3ce8febb154c317e688a5f6ed410ad8e2bd77c5678bf1f7f3eb45f601695221029bde55661e4f359cf9ca2d1846c235e752f760ed6d65e2018f821253e78b3c722103f2b4a813ab79e4fb46edf3a6a87fb154a85b45810158e949f41a3fce3c9bf574210399a6d766f6d3a3843f8479446ee766b5745dc15c24d1db5149214d27987bd29453ae00000000",
      "validTransactionHash": "ff40ccd5c8ba75ffdce27d8584b6e8ee625cb3f71f7cbb6d072a445a97c2c8c3"
    }
  },
  "state": "approved",
  "scope": "wallet",
  "userIds": [
    "62ab90e06dfda30007974f0a52a12995",
    "627ff9325a5c1b0007c05a40d15e1522"
  ],
  "approvalsRequired": 1,
  "singleRunResults": [
    {
      "ruleId": "Custody Enterprise Transaction ID Verification",
      "triggered": false,
      "_id": "655686880765186f0b3e9e89"
    }
  ],
  "resolvers": [
    {
      "user": "627ff9325a5c1b0007c05a40d15e1522",
      "date": "2023-11-16T21:33:24.644Z",
      "resolutionType": "pending"
    }
  ]
}

See Also