Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions modules/express/src/typedRoutes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down Expand Up @@ -44,6 +45,9 @@ export const ExpressApi = apiSpec({
'express.v1.wallet.signTransaction': {
post: PostSignTransaction,
},
'express.verifycoinaddress': {
post: PostVerifyCoinAddress,
},
});

export type ExpressApi = typeof ExpressApi;
Expand Down
48 changes: 48 additions & 0 deletions modules/express/src/typedRoutes/api/v2/verifyAddress.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
29 changes: 29 additions & 0 deletions modules/express/test/unit/typedRoutes/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(codec: t.Type<T, unknown>, input: unknown): T {
Expand Down Expand Up @@ -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), {}));
Expand Down