Skip to content

Commit 0bc06ce

Browse files
Merge pull request #7626 from BitGo/WP-6461-add-isWalletAddress-endpoint
feat: add isWalletAddress endpoint
2 parents 6d02170 + 4c86891 commit 0bc06ce

File tree

6 files changed

+1241
-0
lines changed

6 files changed

+1241
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Verify that an address belongs to a wallet using the BitGo SDK.
3+
*
4+
* This example demonstrates using the SDK's isWalletAddress method which verifies:
5+
* - Forwarder addresses (deposit addresses)
6+
* - Base addresses (wallet contract addresses)
7+
*
8+
* Copyright 2024, BitGo, Inc. All Rights Reserved.
9+
*/
10+
11+
const BitGoJS = require('bitgo');
12+
13+
const coin = 'hteth'; // change to 'eth' for production
14+
const env = 'test'; // change to 'prod' for production
15+
16+
// TODO: set your access token here
17+
const accessToken = '';
18+
19+
// TODO: set your wallet ID here
20+
const walletId = '';
21+
22+
async function main() {
23+
// Initialize BitGo SDK
24+
const bitgo = new BitGoJS.BitGo({ env, accessToken });
25+
26+
console.log('Step 1: Getting wallet...');
27+
const wallet = await bitgo.coin(coin).wallets().get({ id: walletId });
28+
29+
const coinSpecific = wallet.coinSpecific();
30+
const baseAddress = coinSpecific.baseAddress;
31+
const walletVersion = coinSpecific.walletVersion;
32+
const feeAddress = coinSpecific.feeAddress;
33+
const walletSalt = coinSpecific.salt;
34+
35+
console.log(' Base Address:', baseAddress);
36+
console.log(' Wallet Version:', walletVersion);
37+
38+
console.log('Step 2: Fetching keychains...');
39+
const keychainIds = wallet.keyIds();
40+
const keychains = [];
41+
42+
for (const keychainId of keychainIds) {
43+
const keychain = await bitgo.coin(coin).keychains().get({ id: keychainId });
44+
45+
// For TSS keychains, derive pub from commonKeychain (first 66 characters)
46+
const pub = keychain.pub || (keychain.commonKeychain && keychain.commonKeychain.slice(0, 66));
47+
48+
if (!pub) {
49+
throw new Error(`Unable to derive pub for keychain ${keychainId}`);
50+
}
51+
52+
keychains.push({
53+
pub: pub,
54+
...(keychain.ethAddress && { ethAddress: keychain.ethAddress }),
55+
...(keychain.commonKeychain && { commonKeychain: keychain.commonKeychain })
56+
});
57+
}
58+
console.log(' Retrieved', keychains.length, 'keychains');
59+
60+
console.log('Step 3: Getting address details...');
61+
const receiveAddress = wallet.receiveAddress();
62+
const addressObj = await wallet.getAddress({ address: receiveAddress });
63+
64+
const addressIndex = addressObj.index;
65+
const forwarderVersion = addressObj.coinSpecific?.forwarderVersion;
66+
const forwarderSalt = addressObj.coinSpecific?.salt;
67+
68+
console.log(' Address:', receiveAddress);
69+
console.log(' Index:', addressIndex);
70+
console.log(' Forwarder Version:', forwarderVersion);
71+
72+
console.log('Step 4: Verifying forwarder address using SDK...');
73+
const forwarderParams = {
74+
address: receiveAddress,
75+
keychains: keychains,
76+
baseAddress: baseAddress,
77+
walletVersion: walletVersion,
78+
index: addressIndex,
79+
coinSpecific: {
80+
forwarderVersion: forwarderVersion,
81+
salt: forwarderSalt,
82+
feeAddress: feeAddress,
83+
baseAddress: baseAddress
84+
}
85+
};
86+
87+
const forwarderResult = await wallet.baseCoin.isWalletAddress(forwarderParams);
88+
console.log(' Result:', forwarderResult ? '✓ Valid' : '✗ Invalid');
89+
90+
console.log('Step 5: Verifying base address using SDK...');
91+
const baseAddressParams = {
92+
address: baseAddress,
93+
keychains: keychains,
94+
baseAddress: baseAddress,
95+
walletVersion: walletVersion,
96+
index: 0,
97+
coinSpecific: {
98+
salt: walletSalt,
99+
feeAddress: feeAddress,
100+
baseAddress: baseAddress
101+
}
102+
};
103+
104+
const baseResult = await wallet.baseCoin.isWalletAddress(baseAddressParams);
105+
console.log(' Result:', baseResult ? '✓ Valid' : '✗ Invalid');
106+
107+
console.log('\n' + (forwarderResult && baseResult ? '✅ Success: Both addresses verified' : '❌ Failed: Verification failed'));
108+
}
109+
110+
main().catch((e) => console.error(e));
111+
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* Verify that an address belongs to a wallet using the BitGo Express API.
3+
*
4+
* This example demonstrates the isWalletAddress endpoint which verifies:
5+
* - Forwarder addresses (deposit addresses)
6+
* - Base addresses (wallet contract addresses)
7+
*
8+
* Copyright 2024, BitGo, Inc. All Rights Reserved.
9+
*/
10+
11+
const fetch = require('node-fetch');
12+
13+
const coin = 'hteth'; // change to 'eth' for production
14+
15+
// TODO: set your access token here
16+
const accessToken = '';
17+
18+
// TODO: set your wallet ID here
19+
const walletId = '';
20+
21+
const expressUrl = '';
22+
23+
// Helper function to make API requests to Express
24+
async function apiRequest(method, endpoint, body = null) {
25+
const response = await fetch(`${expressUrl}${endpoint}`, {
26+
method,
27+
headers: {
28+
'Authorization': `Bearer ${accessToken}`,
29+
'Content-Type': 'application/json'
30+
},
31+
body: body ? JSON.stringify(body) : null
32+
});
33+
34+
if (!response.ok) {
35+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
36+
}
37+
38+
return await response.json();
39+
}
40+
41+
async function main() {
42+
console.log('Step 1: Fetching wallet data...');
43+
const wallet = await apiRequest('GET', `/api/v2/${coin}/wallet/${walletId}`);
44+
45+
const keychainIds = wallet.keys;
46+
const baseAddress = wallet.coinSpecific.baseAddress;
47+
const walletVersion = wallet.coinSpecific.walletVersion;
48+
const feeAddress = wallet.coinSpecific.feeAddress;
49+
const walletSalt = wallet.coinSpecific.salt;
50+
const addressToVerify = wallet.receiveAddress.address;
51+
52+
console.log(' Base Address:', baseAddress);
53+
console.log(' Wallet Version:', walletVersion);
54+
55+
console.log('Step 2: Fetching keychains...');
56+
const keychains = [];
57+
for (const keychainId of keychainIds) {
58+
const keychain = await apiRequest('GET', `/api/v2/${coin}/key/${keychainId}`);
59+
60+
// For TSS keychains, derive pub from commonKeychain (first 66 characters)
61+
const pub = keychain.pub || (keychain.commonKeychain && keychain.commonKeychain.slice(0, 66));
62+
63+
if (!pub) {
64+
throw new Error(`Unable to derive pub for keychain ${keychainId}`);
65+
}
66+
67+
keychains.push({
68+
pub: pub,
69+
...(keychain.ethAddress && { ethAddress: keychain.ethAddress }),
70+
...(keychain.commonKeychain && { commonKeychain: keychain.commonKeychain })
71+
});
72+
}
73+
console.log(' Retrieved', keychains.length, 'keychains');
74+
75+
console.log('Step 3: Fetching address details...');
76+
const addressData = await apiRequest('GET', `/api/v2/${coin}/wallet/${walletId}/address/${addressToVerify}`);
77+
78+
const addressIndex = addressData.index;
79+
const forwarderVersion = addressData.coinSpecific?.forwarderVersion;
80+
const forwarderSalt = addressData.coinSpecific?.salt;
81+
82+
console.log(' Address:', addressToVerify);
83+
console.log(' Index:', addressIndex);
84+
console.log(' Forwarder Version:', forwarderVersion);
85+
86+
console.log('Step 4: Verifying forwarder address...');
87+
const forwarderParams = {
88+
address: addressToVerify,
89+
keychains: keychains,
90+
baseAddress: baseAddress,
91+
walletVersion: walletVersion,
92+
index: addressIndex,
93+
coinSpecific: {
94+
forwarderVersion: forwarderVersion,
95+
salt: forwarderSalt,
96+
feeAddress: feeAddress,
97+
baseAddress: baseAddress
98+
}
99+
};
100+
101+
const forwarderResult = await apiRequest('POST', `/api/v2/${coin}/wallet/${walletId}/iswalletaddress`, forwarderParams);
102+
console.log(' Result:', forwarderResult ? '✓ Valid' : '✗ Invalid');
103+
104+
console.log('Step 5: Verifying base address...');
105+
const baseAddressParams = {
106+
address: baseAddress,
107+
keychains: keychains,
108+
baseAddress: baseAddress,
109+
walletVersion: walletVersion,
110+
index: 0,
111+
coinSpecific: {
112+
salt: walletSalt,
113+
feeAddress: feeAddress,
114+
baseAddress: baseAddress
115+
}
116+
};
117+
118+
const baseResult = await apiRequest('POST', `/api/v2/${coin}/wallet/${walletId}/iswalletaddress`, baseAddressParams);
119+
console.log(' Result:', baseResult ? '✓ Valid' : '✗ Invalid');
120+
}
121+
122+
main().catch((e) => console.error(e));
123+

modules/express/src/clientRoutes.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,19 @@ export async function handleV2CreateAddress(req: ExpressApiRouteRequest<'express
660660
return wallet.createAddress(req.decoded);
661661
}
662662

663+
/**
664+
* handle v2 isWalletAddress - verify if an address belongs to a wallet
665+
* @param req
666+
*/
667+
export async function handleV2IsWalletAddress(
668+
req: ExpressApiRouteRequest<'express.v2.wallet.isWalletAddress', 'post'>
669+
) {
670+
const bitgo = req.bitgo;
671+
const coin = bitgo.coin(req.decoded.coin);
672+
const wallet = await coin.wallets().get({ id: req.decoded.id });
673+
return await wallet.baseCoin.isWalletAddress(req.decoded as any);
674+
}
675+
663676
/**
664677
* handle v2 approve transaction
665678
* @param req
@@ -1626,6 +1639,10 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
16261639
]);
16271640

16281641
router.post('express.v2.wallet.createAddress', [prepareBitGo(config), typedPromiseWrapper(handleV2CreateAddress)]);
1642+
router.post('express.v2.wallet.isWalletAddress', [
1643+
prepareBitGo(config),
1644+
typedPromiseWrapper(handleV2IsWalletAddress),
1645+
]);
16291646

16301647
router.post('express.v2.wallet.share', [prepareBitGo(config), typedPromiseWrapper(handleV2ShareWallet)]);
16311648
app.post(

modules/express/src/typedRoutes/api/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { PutV2PendingApproval } from './v2/pendingApproval';
4747
import { PostConsolidateAccount } from './v2/consolidateAccount';
4848
import { PostCanonicalAddress } from './v2/canonicalAddress';
4949
import { PostWalletSweep } from './v2/walletSweep';
50+
import { PostIsWalletAddress } from './v2/isWalletAddress';
5051

5152
// Too large types can cause the following error
5253
//
@@ -180,6 +181,12 @@ export const ExpressV2WalletCreateAddressApiSpec = apiSpec({
180181
},
181182
});
182183

184+
export const ExpressV2WalletIsWalletAddressApiSpec = apiSpec({
185+
'express.v2.wallet.isWalletAddress': {
186+
post: PostIsWalletAddress,
187+
},
188+
});
189+
183190
export const ExpressV2WalletSendManyApiSpec = apiSpec({
184191
'express.v2.wallet.sendmany': {
185192
post: PostSendMany,
@@ -316,6 +323,7 @@ export type ExpressApi = typeof ExpressPingApiSpec &
316323
typeof ExpressV2WalletConsolidateAccountApiSpec &
317324
typeof ExpressWalletFanoutUnspentsApiSpec &
318325
typeof ExpressV2WalletCreateAddressApiSpec &
326+
typeof ExpressV2WalletIsWalletAddressApiSpec &
319327
typeof ExpressKeychainLocalApiSpec &
320328
typeof ExpressKeychainChangePasswordApiSpec &
321329
typeof ExpressLightningWalletPaymentApiSpec &
@@ -354,6 +362,7 @@ export const ExpressApi: ExpressApi = {
354362
...ExpressWalletFanoutUnspentsApiSpec,
355363
...ExpressV2WalletCreateAddressApiSpec,
356364
...ExpressV2WalletConsolidateAccountApiSpec,
365+
...ExpressV2WalletIsWalletAddressApiSpec,
357366
...ExpressKeychainLocalApiSpec,
358367
...ExpressKeychainChangePasswordApiSpec,
359368
...ExpressLightningWalletPaymentApiSpec,

0 commit comments

Comments
 (0)