From ed5b056d9ce9ca6a49c487abd6e1cd4f2030c9d3 Mon Sep 17 00:00:00 2001 From: Daniel Zhao Date: Wed, 10 Sep 2025 13:01:49 -0400 Subject: [PATCH 1/5] feat(express): typed router for verifycoinaddress Typed router for verifycoinaddress TICKET: WP-5436 --- modules/express/encryptedPrivKeys.json | 4 +-- modules/express/src/clientRoutes.ts | 16 +++------- modules/express/src/typedRoutes/api/index.ts | 4 +++ .../express/test/unit/typedRoutes/decode.ts | 29 +++++++++++++++++++ 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/modules/express/encryptedPrivKeys.json b/modules/express/encryptedPrivKeys.json index 1c9f4ef5a2..9e26dfeeb6 100644 --- a/modules/express/encryptedPrivKeys.json +++ b/modules/express/encryptedPrivKeys.json @@ -1,3 +1 @@ -{ - "61f039aad587c2000745c687373e0fa9": "{\"iv\":\"O74H8BBv86GBpoTzjVyzWw==\",\"v\":1,\"iter\":10000,\"ks\":256,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"salt\":\"7n8pAjXCfug=\",\"ct\":\"14MjiKBksaaayrwuc/w8vJ5C3yflQ15//dhLiOgYVqjhJJ7iKrcrjtgfLoI3+MKLaKCycNKi6vTs2xs8xJeSm/XhsOE9EfapkfGHdYuf4C6O1whNOyugZ0ZSOA/buDC3rvBbvCNtLDOxN5XWJN/RADOnZdHuVGk=\"}" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index 362969f4f6..3f655aa502 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -356,26 +356,18 @@ function handleV2UserREST(req: express.Request, res: express.Response, next: exp * handle v2 address validation * @param req */ -function handleV2VerifyAddress(req: express.Request): { isValid: boolean } { - if (!_.isString(req.body.address)) { - throw new Error('Expected address to be a string'); - } - - if (req.body.supportOldScriptHashVersion !== undefined && !_.isBoolean(req.body.supportOldScriptHashVersion)) { - throw new Error('Expected supportOldScriptHashVersion to be a boolean.'); - } - +function handleV2VerifyAddress(req: ExpressApiRouteRequest<'express.verifycoinaddress', 'post'>): { isValid: boolean } { const bitgo = req.bitgo; const coin = bitgo.coin(req.params.coin); if (coin instanceof Coin.AbstractUtxoCoin) { return { - isValid: coin.isValidAddress(req.body.address, !!req.body.supportOldScriptHashVersion), + isValid: coin.isValidAddress(req.decoded.address, !!req.decoded.supportOldScriptHashVersion), }; } return { - isValid: coin.isValidAddress(req.body.address), + isValid: coin.isValidAddress(req.decoded.address), }; } @@ -1720,7 +1712,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void { // Miscellaneous app.post('/api/v2/:coin/canonicaladdress', parseBody, prepareBitGo(config), promiseWrapper(handleCanonicalAddress)); - app.post('/api/v2/:coin/verifyaddress', parseBody, prepareBitGo(config), promiseWrapper(handleV2VerifyAddress)); + router.post('express.verifycoinaddress', [prepareBitGo(config), typedPromiseWrapper(handleV2VerifyAddress)]); app.put( '/api/v2/:coin/pendingapprovals/:id', parseBody, diff --git a/modules/express/src/typedRoutes/api/index.ts b/modules/express/src/typedRoutes/api/index.ts index 4a45375e0d..6116861f9f 100644 --- a/modules/express/src/typedRoutes/api/index.ts +++ b/modules/express/src/typedRoutes/api/index.ts @@ -12,6 +12,7 @@ import { PostAcceptShare } from './v1/acceptShare'; import { PostSimpleCreate } from './v1/simpleCreate'; import { PutPendingApproval } from './v1/pendingApproval'; import { PostSignTransaction } from './v1/signTransaction'; +import { PostVerifyCoinAddress } from './v2/verifyAddress'; export const ExpressApi = apiSpec({ 'express.ping': { @@ -44,6 +45,9 @@ export const ExpressApi = apiSpec({ 'express.v1.wallet.signTransaction': { post: PostSignTransaction, }, + 'express.verifycoinaddress': { + post: PostVerifyCoinAddress, + }, }); export type ExpressApi = typeof ExpressApi; diff --git a/modules/express/test/unit/typedRoutes/decode.ts b/modules/express/test/unit/typedRoutes/decode.ts index 57641f6338..4f29bddb4c 100644 --- a/modules/express/test/unit/typedRoutes/decode.ts +++ b/modules/express/test/unit/typedRoutes/decode.ts @@ -4,6 +4,7 @@ import { DecryptRequestBody } from '../../../src/typedRoutes/api/common/decrypt' import { EncryptRequestBody } from '../../../src/typedRoutes/api/common/encrypt'; import { LoginRequest } from '../../../src/typedRoutes/api/common/login'; import { VerifyAddressBody } from '../../../src/typedRoutes/api/common/verifyAddress'; +import { VerifyAddressV2Body, VerifyAddressV2Params } from '../../../src/typedRoutes/api/v2/verifyAddress'; import { SimpleCreateRequestBody } from '../../../src/typedRoutes/api/v1/simpleCreate'; export function assertDecode(codec: t.Type, input: unknown): T { @@ -92,6 +93,34 @@ describe('io-ts decode tests', function () { address: 'some-address', }); }); + it('express.verifycoinaddress', function () { + // invalid coin param type + assert.throws(() => + assertDecode(t.type(VerifyAddressV2Params), { + coin: 123, + }) + ); + // valid coin param + assertDecode(t.type(VerifyAddressV2Params), { + coin: 'btc', + }); + + // invalid address type in body + assert.throws(() => + assertDecode(t.type(VerifyAddressV2Body), { + address: 123, + }) + ); + // valid body without optional flag + assertDecode(t.type(VerifyAddressV2Body), { + address: 'some-address', + }); + // valid body with optional flag + assertDecode(t.type(VerifyAddressV2Body), { + address: 'some-address', + supportOldScriptHashVersion: true, + }); + }); it('express.v1.wallet.simplecreate', function () { // passphrase is required assert.throws(() => assertDecode(t.type(SimpleCreateRequestBody), {})); From b73c31dce64e9b96c1c09c65895c6d1e2db7fe58 Mon Sep 17 00:00:00 2001 From: Daniel Zhao Date: Wed, 10 Sep 2025 13:08:48 -0400 Subject: [PATCH 2/5] feat(express): typed router for verifycoinaddress TICKET: WP-5436 --- .../src/typedRoutes/api/v2/verifyAddress.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 modules/express/src/typedRoutes/api/v2/verifyAddress.ts diff --git a/modules/express/src/typedRoutes/api/v2/verifyAddress.ts b/modules/express/src/typedRoutes/api/v2/verifyAddress.ts new file mode 100644 index 0000000000..890931bdfa --- /dev/null +++ b/modules/express/src/typedRoutes/api/v2/verifyAddress.ts @@ -0,0 +1,48 @@ +import * as t from 'io-ts'; +import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http'; +import { BitgoExpressError } from '../../schemas/error'; + +/** + * Path parameters for coin-specific address verification. + * @property coin - Ticker or identifier of the coin (e.g. 'btc', 'eth'). + */ +export const VerifyAddressV2Params = { + /** Coin ticker / chain identifier */ + coin: t.string, +}; + +/** + * Request body for coin-specific address verification. + * + * @property address - The address string to validate. + * @property supportOldScriptHashVersion - (UTXO only) When true, treat legacy script hash version as acceptable. + */ +export const VerifyAddressV2Body = { + /** Address to be validated */ + address: t.string, + /** Accept legacy script hash version for applicable UTXO coins (optional). */ + supportOldScriptHashVersion: optional(t.boolean), +}; + +/** + * Verify address for a given coin. + * + * Returns whether the address is valid for the specified coin. + * For UTXO coins, an optional legacy script hash flag can be provided to allow previous script hash versions. + * + * @operationId express.verifycoinaddress + */ +export const PostVerifyCoinAddress = httpRoute({ + path: '/api/v2/{coin}/verifyaddress', + method: 'POST', + request: httpRequest({ + params: VerifyAddressV2Params, + body: VerifyAddressV2Body, + }), + response: { + 200: t.type({ + isValid: t.boolean, + }), + 404: BitgoExpressError, + }, +}); From 7b55f11cf11f5746f87680ec6995fb56cf5dab42 Mon Sep 17 00:00:00 2001 From: Daniel Zhao Date: Wed, 10 Sep 2025 13:14:39 -0400 Subject: [PATCH 3/5] feat(express): typed router for verifycoinaddress This should not be edited. TICKET: WP-5436 --- modules/express/encryptedPrivKeys.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/express/encryptedPrivKeys.json b/modules/express/encryptedPrivKeys.json index 9e26dfeeb6..1c9f4ef5a2 100644 --- a/modules/express/encryptedPrivKeys.json +++ b/modules/express/encryptedPrivKeys.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "61f039aad587c2000745c687373e0fa9": "{\"iv\":\"O74H8BBv86GBpoTzjVyzWw==\",\"v\":1,\"iter\":10000,\"ks\":256,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"salt\":\"7n8pAjXCfug=\",\"ct\":\"14MjiKBksaaayrwuc/w8vJ5C3yflQ15//dhLiOgYVqjhJJ7iKrcrjtgfLoI3+MKLaKCycNKi6vTs2xs8xJeSm/XhsOE9EfapkfGHdYuf4C6O1whNOyugZ0ZSOA/buDC3rvBbvCNtLDOxN5XWJN/RADOnZdHuVGk=\"}" +} \ No newline at end of file From abda08011974ed2d05cd2ff66f1ff884c74a0773 Mon Sep 17 00:00:00 2001 From: Daniel Zhao Date: Wed, 10 Sep 2025 14:50:33 -0400 Subject: [PATCH 4/5] docs: changed docs for request body TICKET: WP-5436 --- modules/express/src/typedRoutes/api/v2/verifyAddress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/express/src/typedRoutes/api/v2/verifyAddress.ts b/modules/express/src/typedRoutes/api/v2/verifyAddress.ts index 890931bdfa..725cf524fa 100644 --- a/modules/express/src/typedRoutes/api/v2/verifyAddress.ts +++ b/modules/express/src/typedRoutes/api/v2/verifyAddress.ts @@ -18,7 +18,7 @@ export const VerifyAddressV2Params = { * @property supportOldScriptHashVersion - (UTXO only) When true, treat legacy script hash version as acceptable. */ export const VerifyAddressV2Body = { - /** Address to be validated */ + /** Address which should be verified for correct format */ address: t.string, /** Accept legacy script hash version for applicable UTXO coins (optional). */ supportOldScriptHashVersion: optional(t.boolean), From 0491a837eeebc21cdf5c9d8b2d57bce33eef8e39 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 10 Sep 2025 17:59:16 -0400 Subject: [PATCH 5/5] refactor: change to leverage decoded values TICKET: WP-5436 Co-authored-by: Zahin Mohammad --- modules/express/src/clientRoutes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index 3f655aa502..9d75314bb6 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -362,7 +362,7 @@ function handleV2VerifyAddress(req: ExpressApiRouteRequest<'express.verifycoinad if (coin instanceof Coin.AbstractUtxoCoin) { return { - isValid: coin.isValidAddress(req.decoded.address, !!req.decoded.supportOldScriptHashVersion), + isValid: coin.isValidAddress(req.decoded.address, req.decoded.supportOldScriptHashVersion), }; }