Skip to content

Commit 19e2f44

Browse files
committed
feat: add isWalletAddress endpoint
TICKET: WP-6461
1 parent 6add082 commit 19e2f44

File tree

4 files changed

+989
-0
lines changed

4 files changed

+989
-0
lines changed

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,
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Path parameters for verifying if an address belongs to a wallet
7+
*/
8+
export const IsWalletAddressParams = {
9+
/** Coin ticker / chain identifier */
10+
coin: t.string,
11+
/** The ID of the wallet */
12+
id: t.string,
13+
} as const;
14+
15+
/**
16+
* Keychain codec for address verification
17+
* Supports both BIP32 wallets (need ethAddress) and TSS/MPC wallets (need commonKeychain)
18+
*/
19+
export const KeychainCodec = t.intersection([
20+
// Required field
21+
t.type({
22+
pub: t.string,
23+
}),
24+
// Optional fields for different wallet types
25+
t.partial({
26+
/** Ethereum address (required for BIP32 wallet base address verification: V1, V2, V4) */
27+
ethAddress: t.string,
28+
/** Common keychain (required for TSS/MPC wallets: V3, V5, V6) */
29+
commonKeychain: t.string,
30+
}),
31+
]);
32+
33+
/**
34+
* Request body for verifying if an address belongs to a wallet
35+
*/
36+
export const IsWalletAddressBody = {
37+
/** The address to verify */
38+
address: t.string,
39+
/** Keychains for verification */
40+
keychains: t.array(KeychainCodec),
41+
/** Base address of the wallet */
42+
baseAddress: optional(t.string),
43+
/** Wallet version */
44+
walletVersion: optional(t.number),
45+
/** Address index for TSS/MPC wallet derivation */
46+
index: optional(t.union([t.number, t.string])),
47+
/** Coin-specific address data */
48+
coinSpecific: optional(
49+
t.partial({
50+
/** Forwarder version */
51+
forwarderVersion: t.number,
52+
/** Salt for CREATE2 address derivation */
53+
salt: t.string,
54+
/** Fee address for v4 forwarders */
55+
feeAddress: t.string,
56+
/** Base address (alternative to top-level baseAddress) */
57+
baseAddress: t.string,
58+
})
59+
),
60+
/** Implied forwarder version */
61+
impliedForwarderVersion: optional(t.number),
62+
/** Format for the address */
63+
format: optional(t.string),
64+
/** Root address for coins that use root address */
65+
rootAddress: optional(t.string),
66+
} as const;
67+
68+
/**
69+
* Response for verifying if an address belongs to a wallet
70+
*/
71+
export const IsWalletAddressResponse = {
72+
200: t.boolean,
73+
400: BitgoExpressError,
74+
} as const;
75+
76+
/**
77+
* Verify if an address belongs to a wallet
78+
*
79+
* This endpoint verifies whether a given address belongs to the specified wallet.
80+
* It performs cryptographic verification, checking address derivation
81+
* against wallet keychains and configuration.
82+
*
83+
* Returns `true` if the address belongs to the wallet, `false` otherwise.
84+
* Throws an error if verification fails or parameters are invalid.
85+
*
86+
* To verify a baseAddress, set the `baseAddress` and `address` to the base address of the wallet.
87+
*
88+
* **Limitations:**
89+
* - Forwarder v0: Cannot verify (nonce not stored). Returns `true` without verification.
90+
*
91+
* @operationId express.v2.wallet.isWalletAddress
92+
* @tag Express
93+
*/
94+
export const PostIsWalletAddress = httpRoute({
95+
path: '/api/v2/{coin}/wallet/{id}/iswalletaddress',
96+
method: 'POST',
97+
request: httpRequest({
98+
params: IsWalletAddressParams,
99+
body: IsWalletAddressBody,
100+
}),
101+
response: IsWalletAddressResponse,
102+
});

0 commit comments

Comments
 (0)