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
field | info | sample |
chain | An 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 |
label | A wallet name or identifier you can use to differentiate wallets internally can use emails, user ids, usernames etc | user7098710 |
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 balanceCongratulations. 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!