diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index 362969f4f6..9d75314bb6 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/src/typedRoutes/api/v2/verifyAddress.ts b/modules/express/src/typedRoutes/api/v2/verifyAddress.ts new file mode 100644 index 0000000000..725cf524fa --- /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 which should be verified for correct format */ + 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, + }, +}); 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), {}));