Creating Wallet Addresses for your Users

Photo by Nick Chong on Unsplash

Creating Wallet Addresses for your Users

Generating deposit addresses programmatically - Bitgo Express SDK

Introduction - Creating a wallet

BitGo wallets have the additional benefit of advanced treasury controls that can allow multiple users to share a wallet with a specific set of policies as well as spending limits and address whitelisting. In the previous blog, we officially created a multi-sig hot wallet through the Web UI! Think of it as a "Root" wallet.

The next step will be to Create deposit wallets that can be used by your users to deposit BTC to your site. BitGo enables BIP32 HD Keys on all wallets so you can generate up to 2^32–1 children addresses per wallet Id.

I'm using CURL but you can write a request wrapper around it on any programming language. To create a wallet address run the following on your terminal

curl -X POST -H "Content-Type: application/json" 
             -H "Authorization: Bearer MY_BITGO_TOKEN_HERE" 
             -d '{ "chain": 10, "label": "someone@example.com" }'  
             https://app.bitgo.com/api/v2/btc/wallet/WALLET_ID_HERE/address
fieldinfosample
chainAn integer denotes the address type. For example: LEGACY_DEPOSIT = 0, LEGACY_CHANGE = 1, P2SH_DEPOSIT = 10, P2SH_CHANGE = 11, BECH32_DEPOSIT = 20, BECH32_CHANGE = 21, BECH32M_DEPOSIT = 30, and BECH32M_CHANGE = 31.10
labelA wallet name or identifier you can use to differentiate wallets internally can use emails, user ids, usernames etcuser7098710

Voila! You have a new deposit address. Whenever possible, BitGo recommends creating a new receive address for every transaction, as this enhances your privacy. There's no limit to the number of addresses you can create

You can display the new deposit address in your front-end code via a QR Code for your users in an Image tag. The Google API Chart endpoint allows developers to dynamically generate QR codes by simply constructing the appropriate URL with the desired parameters.

<img src="https://chart.googleapis.com/chart?chs=250x250&cht=qr&chl={{address}}" height="170"/>

Setting a callback endpoint

You can set up webhooks to programmatically receive callbacks or notifications from BitGo when a deposit or withdrawal is made by your users. In simple terms, you can attach webhooks to wallets to receive transaction notifications.

Before you process a webhook notification, I recommend you verify the information sent in it by fetching the transfer data from BitGo. Also, ensure your application can succeed even if:

  • You encounter transient network errors.

  • You receive the same webhook twice, due to improper acknowledgment.

BitGo servers make POST HTTP requests to your URL in a JSON payload. If they don't receive a successful HTTP 200 OK response, they re-attempt the webhook with an increasing delay between each retry at least 7 times

To create a webhook for your wallet login to the BitGo dashboard under Wallets and Connections click on your Wallet then under Wallet Settings click Add Webhook

Great! To see how this webhook works, you can Simulate an event by manually triggering it. Send some Bitcoins to the wallet you just made. Or click Test to use a previous transaction. Use the last BitGo Transfer ID or enter a specific BitGo Transfer ID you'd like to test and log the webhook on your remote server

Handling Callback data - Crediting BTC Deposits in PHP

You can use the callback data to credit BTC to a users account if you're running an exchange model

 //Transactions, triggered when bitgo detects a transfer
    public function incomingca68b56a8bf68403650bitgo()
    {
        $requestIp = $this->request->server('REMOTE_ADDR');

        try {
//Log the response
            file_put_contents(storage_path() . "/logs/bitgo.log", $this->request->getContent() . PHP_EOL, FILE_APPEND);

            $captureRequest = json_decode($this->request->getContent());
            $hash = $captureRequest->hash;
            $transfer = $captureRequest->transfer;
            $coin = $captureRequest->coin;
            $type = $captureRequest->type;
            $state = $captureRequest->state;
            $wallet = $captureRequest->wallet;
            switch ($coin) {

                case('btc'):

                    //Check if the transaction based on the id has been captured already for the said type
                    $confirmPaymentIncoming = BitgoModel::query()->where('hash', $hash)->where('transfer', $transfer)->first();
                    if (is_null($confirmPaymentIncoming)) {
                        //Query transfer by id using Bitgo transfer id
                        $data = $this->getTX($transfer);
                        if ($data != false) {
                            //Ensure the Bitgo Transfer Id is correct by comparing given hash to bitgo hash
                            if (strcmp($hash, $data['txid']) !== 0) {
                                return $this->successResponse('Error getting this transaction. Invalid hash', 200, true);
                            }
                            //If receive, we insert

                            if ($data['type'] == 'receive') {
                                //credit the btc to the wallet $label
                                //send the user a telegram message by fetching the email and the tg_id
                                $params = [

                                    'hash' => $data['txid'],
                                    'transfer' => $transfer,
                                    'coin' => $coin,
                                    'state' => $state,
                                    'wallet' => $wallet,
                                    'ip' => $requestIp,
                                    'type' => $data['type'],
                                    'height' => $data['height'],
                                    'confirmations' => $data['confirmations'],
                                    'satoshi' => $data['value'],
                                    'usd' => round($data['usd'], 2, PHP_ROUND_HALF_DOWN),
                                    'usdRate' => $data['usdRate'],
                                    'email' => $data['label'],
                                    'amount' => BitGoSDK::toBTC($data['value'])


                                ];


                                $store = BitgoModel::insert($params);
                                //Tell the user we have a new incoming TX
                                $fetchUserTGID = WalletModel::query()->where('email', $data['label'])->first();
                                if (is_null($fetchUserTGID)) {

                                    return $this->successResponse($data, 200, true);
                                }
                                $amount = BitGoSDK::toBTC($data['value']);
                                $messageToBitcoin = "Hi $fetchUserTGID->username, you have an Incoming Bitcoin transaction of $amount. Once this transaction has 2 confirmations, your Bitcoin will be available. This can take anywhere between 20 and 90 minutes. We'll notify you again once the Bitcoin successfully arrives in your Wallet.";

                                (new NotificationsController($this->request))->insert_notification($fetchUserTGID->email, $fetchUserTGID->username, 'Bitcoin Incoming', 'wallet/basic', $messageToBitcoin, true);
                            } else if ($data['type'] == 'send') {
                                //If it a send we update by the transfer id
                                $update = BitgoModel::query()->where('transfer', $transfer)->update([
                                    'coin' => $coin,
                                    'state' => $state,
                                    'ip' => $requestIp,
                                    'height' => $data['height'],
                                    'confirmations' => $data['confirmations'],
                                    'satoshi' => $data['value'],
                                    'usd' => round($data['usd'], 2, PHP_ROUND_HALF_DOWN),
                                    'usdRate' => $data['usdRate'],
                                    'amount' => BitGoSDK::toBTC($data['value'])
                                ]);

                                return $this->successResponse('Send-out updated', 200, true);
                            }

                            return $this->successResponse($data, 200, true);

                        } else {//Here should update the previous record
                            return $this->successResponse('Error getting this transaction. Invalid transfer', 200, true);
                        }
                    } else {
                        return $this->successResponse("Duplicate Data Recorded For Hash $hash", 200, true);
                    }


                    break;

                default:
                    return $this->successResponse('Invalid coin', 200, true);
                    break;

            }

        } catch (\Throwable $e) {
            return $this->successResponse($e->getMessage(), 200, true);
        }
    }

This sample code snippet handles an incoming transaction for Bitcoin (BTC) and performs various actions based on the transaction type. Here's a simplified explanation:

  • The code starts with a try block, which is used to catch any errors that might occur during the execution of the code.

  • The file_put_contents function is used to append the content of the incoming request to a log file named "bitgo.log".

  • The incoming request data is extracted and stored in variables such as $hash, $transfer, $coin, $type, $state, and $wallet.

  • The code then switches on the value of $coin to handle different cryptocurrency cases. In this case, it only handles the 'btc' (Bitcoin) case.

  • Within the 'btc' case, it checks if the transaction has already been captured by querying the database(to avoid double txns). If it hasn't been captured, it retrieves and verifies the transaction details by fetching the transfer ID from BitGo using a getTX function.

  • If the transaction is a 'receive' type, it credits the Bitcoin to a specified wallet, sends a notification to the user, and stores the transaction details in the database.

  • If the transaction is a 'send' type, it updates the existing record based on the transfer ID.

  • If the 'coin' value is not 'btc' or an invalid value, it returns a response indicating an invalid coin.

  • If any exceptions occur during the execution of the code, the catch block is triggered.

Confirming BTC Deposits

Before crediting the user balance you'd want to check if the transaction is cleared by the blockchain. You can retrieve detailed information about the transactions by querying the transfer from the callback against the Transfers endpoint

https://app.bitgo.com/api/v2/{coin}/wallet/{walletId}/transfer/{transferId}
  • coin

    A cryptocurrency or token ticker symbol.

    Example: "btc"

  • walletId

    Example: "59cd72485007a239fb00282ed480da1f"

    Pattern: ^[0-9a-f]{32}$

  • transferId

    a transfer or transaction id

curl https://app.bitgo.com/api/v2/btc/wallet/<<WALLET_ID>>/transfer/<<TRANSFER_ID>>
{
  "height": 0,
  "heightId": "string",
  "date": "2019-08-24T14:15:22Z",
  "type": "send",
  "value": 0,
  "valueString": "string",
  "intendedValueString": null,
  "baseValue": 0,
  "baseValueString": null,
  "feeString": "string",
  "payGoFee": 0,
  "payGoFeeString": "string",
  "usd": 0,
  "usdRate": 0,
  "state": "confirmed",
  "tags": [],
  "history": [
    {
      "date": "2019-08-24T14:15:22Z",
      "action": "created",
      "comment": "string"
    }
  ],
  "comment": "string",
  "vSize": 0,
  "coinSpecific": {},
  "sequenceId": "string",
  "entries": [
    {
      "address": "2NAUwNgXaoFj2VVnSEvNLGuez8CfdU2UCMZ",
      "wallet": null,
      "value": 0,
      "valueString": null,
      "isChange": true,
      "isPayGo": true,
      "token": "omg",
      "label": "string",
      "failed": true
    }
  ],
  "usersNotified": true,
  "label": "string",
  "confirmations": 0,
  "inputs": [
    {
      "id": "003f688cc349f1fca8ac5ffa21671ca911b6ef351085c60733ed8c2ebf162cb8:2",
      "address": "2MsKxhhkDo5WaLaYRGA9Cr3iSQPyXsu6Fi2",
      "value": 0,
      "blockHeight": 0,
      "date": "2017-03-25T23:01:40.248Z",
      "coinbase": true,
      "chain": 0,
      "index": 0,
      "redeemScript": "522102f1e990044d2a8be43d5b500bbdcb36277b97a4b07e01c5101ae8ec1568bfd6532103dab7dc82f2fc8c28200c1bdeca9c4cf181e0ca257395829cbd599395048afb57210205422e711827d8356f2fb75334d863941dd7eb45bd5788fa231dc5fa755135b653ae",
      "witnessScript": "52210351311cd81144e6cbdba561d24dfc22644cb02d053339d4beace03231b3be4f372103a8d0c1a375b9ee1a2411f9f8e18373be7f228b18260f63bbfca48809170ed08b2103c3bd8bd074657bbe9ee6714b31a4a54b6fd5b5cda0e1030122f9bf46b5034f6b53ae",
      "isSegwit": true
    }
  ],
  "outputs": []
}
$confirmation_count = $data['confirmations'];
if($confirmation_count == 0){
//Transaction is in mempool thus uncomrfirmed
}
if($confirmation_count >= 2){
//Credit coins to user
}
  • If the transaction has reached 2 or more confirmations and the transaction type is receive credit the received amount to the user's balance by updating their balance

  • Congratulations. You've successfully created a deposit address and sent a test deposit. Received a callback webhook and logged it. Adding more secure logic is now up to you

I'm excited to see what you’ll build next

BitGo Express helps power and protect some of the largest Bitcoin exchanges and hedge funds around the world. I'm excited to see how Express will help power the next generation of Bitcoin apps. In my next episode ill show you how to programmatically send BTC transactions for user accounts in a production-grade app. Let us know what you think of this on Twitter!