Sending Bitcoin - BitGo Express

Withdrawing BTC programmatically

Building your request body

Sending Bitcoin is made easy with the BitGo Express. All you need is some identifying information for the originating wallet. As well as the destination wallet address and the amount that is to be sent.

Note: If your access token doesn't have a spending limit, it will lock regularly, preventing you from sending a transaction. If your access token is locked, Unlock the Session to proceed. You can set a spending limit. Check the first blog to see how.

On your terminal run this to send BTC to a destination wallet address

curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $ACCESS_TOKEN" 
             -d '{ "address": "2MszopwgnnoV7WLouQyQz9U9DkaEVpcTByF",
                   "amount": 50000000,
                   "walletPassphrase": "superlongpassword" }'
              http://localhost:3080/api/v2/btc/wallet/<WALLET_ID>/sendcoins

Apart from the three parameters in your Request Body, you may finely adjust your transaction by adding other info such as blockchain fee information, add multiple addresses for bulk withdrawals.

{
  "address": {
    "value": "Adress 1"
  },
  "amount": {},
  "walletPassphrase": "My Wallet Password",
  "prv": "My Private Key If available",
  "numBlocks": 247,
  "feeRate": {},
  "maxFeeRate": {},
  "feeMultiplier": {},
  "minConfirms": -46419653,
  "enforceMinConfirmsForChange": false,
  "gasPrice": {},
  "eip1559": {
    "maxPriorityFeePerGas": {
      "value": "<Error: Too many levels of nesting to fake this schema>"
    },
    "maxFeePerGas": {
      "value": "<Error: Too many levels of nesting to fake this schema>"
    }
  },
  "gasLimit": {},
  "targetWalletUnspents": 1000,
  "minValue": {},
  "maxValue": {},
  "sequenceId": "dolore occaecat aliqua nostrud magn",
  "nonce": {},
  "noSplitChange": false,
  "unspents": [
    "12b147dd8b4f73c01f72bdbf5b589eea614f3de609ffdbdac84852d6505cf8a3:1",
    "12b147dd8b4f73c01f72bdbf5b589eea614f3de609ffdbdac84852d6505cf8a3:1"
  ],
  "changeAddress": {},
  "instant": true,
  "memo": {
    "type": "eiusmod al",
    "value": "magna cillum nostrud enim"
  },
  "comment": "ut culpa",
  "addressType": "rep",
  "startTime": "ea",
  "consolidateId": "59cd72485007a239fb00282ed480da1f",
  "lastLedgerSequence": -92846303,
  "ledgerSequenceDelta": 48422319,
  "cpfpTxIds": [
    "ex",
    "dolore amet veniam fugiat irure"
  ],
  "cpfpFeeRate": -42568202,
  "maxFee": 68424188,
  "strategy": "deserunt sunt c",
  "validFromBlock": 50509883,
  "validToBlock": 22214801,
  "type": "laborum quis sit Excepteur nulla",
  "trustlines": [
    {
      "value": "<Error: Too many levels of nesting to fake this schema>"
    },
    {
      "value": "<Error: Too many levels of nesting to fake this schema>"
    }
  ],
  "stakingOptions": {
    "value": "<Error: Too many levels of nesting to fake this schema>"
  },
  "unstakingOptions": {
    "from": "amet u",
    "receiver": "cupidatat enim",
    "unstakeCpuQuantity": "sint",
    "unstakeNetQuantity": "commodo esse in"
  },
  "refundOptions": {
    "address": "dolor labore cupidatat ut Lorem"
  },
  "messageKey": "amet",
  "reservation": {
    "expireTime": "1947-10-19T14:13:13.415Z"
  },
  "data": "laboris ut sunt do",
  "hop": false
}

Any language capable of issuing HTTP requests can easily build on Bitgo Express through the RESTful interface. In the snippet below I used PHP to build the requested body and curl it on http://locahost:3080 where we installed the Bitgo express instance. Therefore you can manage client withdrawals in code directly by calling on your request wrapper 🤓

 /**
     * This API call allows you to create and send cryptocurrency to a destination address.
     * 
     * @param string $address                   Recipient address
     * @param int $amount                       Amount to be sent to the recipient
     * @param string $walletPassphrase          The passphrase to be used to decrypt the user key on this wallet
     * @param string $prv                       The private key in string form if the walletPassphrase is not available
     * @param int $numBlocks                    Estimates the approximate fee per kilobyte necessary for a transaction confirmation within 'numBlocks' blocks.
     * @param int $feeRate                      Fee rate in satoshis/litoshis/atoms per kilobyte.
     * @param string $comment                   Any additional comment to attach to the transaction
     * @param array $unspents                   The unspents to use in the transaction. Each unspent should be in the form prevTxId:nOutput.
     * @param int $minConfirms                  Minimum number of confirmations unspents going into this transaction should have.
     * @param bool $enforceMinConfirmsForChange Enforce minimum number of confirmations on change (internal) inputs.
     * @param int $targetWalletUnspents         The desired count of unspents in the wallet. If the wallet’s current unspent count is lower than the target, up to four additional change outputs will be added to the transaction. To reduce unspent count in your wallet see 'Consolidate Unspents’.
     * @param bool $noSplitChange               Set to true to disable automatic change splitting for purposes of unspent management.
     * @param int $minValue                     Ignore unspents smaller than this amount of satoshis
     * @param int $maxValue                     Ignore unspents larger than this amount of satoshis
     * @param int $gasPrice                     Custom gas price to be used for sending the transaction
     * @param int $gasLimit                     Custom gas limit for the transaction
     * @param int $sequenceId                   The sequence ID of the transaction
     * @param bool $segwit                      Allow SegWit unspents to be used, and create SegWit change.
     * @param int $lastLedgerSequence           Absolute max ledger the transaction should be accepted in, whereafter it will be rejected.
     * @param string $ledgerSequenceDelta       Relative ledger height (in relation to the current ledger) that the transaction should be accepted in, whereafter it will be rejected.
     * @return array
     */
    public function sendTransaction(string $address, int $amount, string $walletPassphrase, string $prv = null, int $numBlocks = null, int $feeRate = null,int $maxfeeRate = null, string $comment = null, array $unspents = null, int $minConfirms = null, bool $enforceMinConfirmsForChange = null, int $targetWalletUnspents = null, bool $noSplitChange = null, int $minValue = null, int $maxValue = null, int $gasPrice = null, int $gasLimit = null, int $sequenceId = null, bool $segwit = null, int $lastLedgerSequence = null, string $ledgerSequenceDelta = null) {
        $this->url = $this->APIEndpoint . '/wallet/' . $this->walletId . '/sendcoins';
        $this->params = [
            'address' => $address,
            'amount' => "$amount",
            'walletPassphrase' => $walletPassphrase,
            'prv' => $prv,
            'numBlocks' => in_array($this->coin, $this->UTXObased) ? $numBlocks : null,
            'feeRate' => in_array($this->coin, $this->UTXObased) ? $feeRate : null,
            'maxFeeRate' => $maxFeeRate,
            'comment' => $comment,
            'unspents' => in_array($this->coin, $this->UTXObased) ? $unspents : null,
            'minConfirms' => in_array($this->coin, $this->UTXObased) ? $minConfirms : null,
            'enforceMinConfirmsForChange' => in_array($this->coin, $this->UTXObased) ? $enforceMinConfirmsForChange : null,
            'targetWalletUnspents' => in_array($this->coin, $this->UTXObased) ? $targetWalletUnspents : null,
            'noSplitChange' => in_array($this->coin, $this->UTXObased) ? $noSplitChange : null,
            'minValue' => in_array($this->coin, $this->UTXObased) ? $minValue : null,
            'maxValue' => in_array($this->coin, $this->UTXObased) ? $maxValue : null,
            'gasPrice' => $this->coin === CurrencyCode::ETHEREUM ? $gasPrice : null,
            'gasLimit' => $this->coin === CurrencyCode::ETHEREUM ? $gasLimit : null,
            'sequenceId' => $this->coin === CurrencyCode::ETHEREUM ? $sequenceId : null,
            'segwit' => in_array($this->coin, [CurrencyCode::BITCOIN, CurrencyCode::LITECOIN, CurrencyCode::BITCOIN_GOLD]) ? $segwit : null,
            'lastLedgerSequence' => $this->coin === CurrencyCode::RIPPLE ? $lastLedgerSequence : null,
            'ledgerSequenceDelta' => $this->coin === CurrencyCode::RIPPLE ? $ledgerSequenceDelta : null
        ];
        return $this->__execute();
    }

 private function __execute(string $requestType = 'POST', bool $array = true) {
        $ch = curl_init($this->url);
        if ($requestType === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array_filter($this->params)));
        } elseif ($requestType === 'PUT') {
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array_filter($this->params)));
        }
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if (isset($this->accessToken) && !$this->login) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $this->accessToken
            ]);
        }
        curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
        if (defined('CURLOPT_IPRESOLVE') && defined('CURL_IPRESOLVE_V4')) {
            curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        }

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, $array);
    }

Your transaction will be processed. BitGo uses the BitGo key to sign the half-signed transaction in a hardware security module (HSM), creating a fully-signed transaction. Using a BitGo node, Bitgo broadcasts the transaction to the network for confirmation.

Here is a sample result from a successful withdrawal to another wallet

{
  "transfer": {
    "coin": "btc",
    "id": "59cd72485007a239fb00282ed480da1f",
    "wallet": "2NBYMZ9QYufZZTmgXbXKJ8Mh5oMNEpNHVm6",
    "enterprise": "62c5ae8174ac860007aff138a2d74df7",
    "txid": "b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26",
    "height": 0,
    "heightId": "string",
    "date": "2019-08-24T14:15:22Z",
    "type": "send",
    "value": 0,
    "valueString": "string",
    "baseValue": 0,
    "baseValueString": "string",
    "feeString": "string",
    "payGoFee": 0,
    "payGoFeeString": "string",
    "usd": 0,
    "usdRate": 0,
    "state": "confirmed",
    "tags": [
      "59cd72485007a239fb00282ed480da1f"
    ],
    "history": [
      {
        "date": "2019-08-24T14:15:22Z",
        "user": "59cd72485007a239fb00282ed480da1f",
        "action": "created",
        "comment": "string"
      }
    ],
    "comment": "string",
    "vSize": 0,
    "nSegwitInputs": 0,
    "coinSpecific": {},
    "sequenceId": "string",
    "entries": [
      {
        "address": "2NAUwNgXaoFj2VVnSEvNLGuez8CfdU2UCMZ",
        "wallet": "string",
        "value": 0,
        "valueString": "string",
        "isChange": true,
        "isPayGo": true,
        "token": "omg"
      }
    ],
    "usersNotified": true
  },
  "txid": "string",
  "tx": "string",
  "status": "confirmed"
}

Conclusion

Sending Bitcoin is made easy with the BitGo Express and since the transaction signing, occurs in a trusted environment i.e. your server, it offers a secure layer for you to conduct your withdrawals.

I'm excited to see how you can use Bitgo express to help power the next generation of Bitcoin apps. Let me know what you think of this on Twitter!