diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index f4d36d60ba..6848e44324 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -1586,6 +1586,7 @@ export function setupAPIRoutes(app: express.Application, config: Config): void { ); router.post('express.v1.wallet.signTransaction', [prepareBitGo(config), typedPromiseWrapper(handleSignTransaction)]); + router.get('express.lightning.getState', [prepareBitGo(config), typedPromiseWrapper(handleGetLightningWalletState)]); app.post('/api/v1/wallet/:id/simpleshare', parseBody, prepareBitGo(config), promiseWrapper(handleShareWallet)); router.post('express.v1.wallet.acceptShare', [prepareBitGo(config), typedPromiseWrapper(handleAcceptShare)]); @@ -1787,5 +1788,4 @@ export function setupLightningSignerNodeRoutes(app: express.Application, config: prepareBitGo(config), promiseWrapper(handleCreateSignerMacaroon) ); - app.get('/api/v2/:coin/wallet/:id/state', prepareBitGo(config), promiseWrapper(handleGetLightningWalletState)); } diff --git a/modules/express/src/lightning/lightningSignerRoutes.ts b/modules/express/src/lightning/lightningSignerRoutes.ts index 05ac16e757..8ca7c6ac07 100644 --- a/modules/express/src/lightning/lightningSignerRoutes.ts +++ b/modules/express/src/lightning/lightningSignerRoutes.ts @@ -170,15 +170,13 @@ export async function handleCreateSignerMacaroon(req: express.Request): Promise< /** * Handle the request to get the state of a wallet from the signer. */ -export async function handleGetLightningWalletState(req: express.Request): Promise { - const coinName = req.params.coin; +export async function handleGetLightningWalletState( + req: ExpressApiRouteRequest<'express.lightning.getState', 'get'> +): Promise { + const { coin: coinName, walletId } = req.decoded; if (!isLightningCoinName(coinName)) { throw new ApiResponseError(`Invalid coin to get lightning wallet state: ${coinName}`, 400); } - const walletId = req.params.id; - if (typeof walletId !== 'string') { - throw new ApiResponseError(`Invalid wallet id: ${walletId}`, 400); - } const lndSignerClient = await LndSignerClient.create(walletId, req.config); return await lndSignerClient.getWalletState(); diff --git a/modules/express/src/typedRoutes/api/index.ts b/modules/express/src/typedRoutes/api/index.ts index fa5871ff9b..c88d061ed8 100644 --- a/modules/express/src/typedRoutes/api/index.ts +++ b/modules/express/src/typedRoutes/api/index.ts @@ -14,6 +14,7 @@ import { PostSimpleCreate } from './v1/simpleCreate'; import { PutPendingApproval } from './v1/pendingApproval'; import { PostSignTransaction } from './v1/signTransaction'; import { PostKeychainLocal } from './v2/keychainLocal'; +import { GetLightningState } from './v2/lightningState'; import { PostLightningInitWallet } from './v2/lightningInitWallet'; import { PostUnlockLightningWallet } from './v2/unlockWallet'; import { PostVerifyCoinAddress } from './v2/verifyAddress'; @@ -53,6 +54,9 @@ export const ExpressApi = apiSpec({ 'express.keychain.local': { post: PostKeychainLocal, }, + 'express.lightning.getState': { + get: GetLightningState, + }, 'express.lightning.initWallet': { post: PostLightningInitWallet, }, diff --git a/modules/express/src/typedRoutes/api/v2/lightningState.ts b/modules/express/src/typedRoutes/api/v2/lightningState.ts new file mode 100644 index 0000000000..5c38159052 --- /dev/null +++ b/modules/express/src/typedRoutes/api/v2/lightningState.ts @@ -0,0 +1,44 @@ +import * as t from 'io-ts'; +import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; +import { BitgoExpressError } from '../../schemas/error'; +import { WalletState } from '../../../lightning/codecs'; + +/** + * Path parameters for getting lightning node state + */ +export const LightningStateParams = { + /** A lightning coin name (e.g., lnbtc or tlnbtc) */ + coin: t.string, + /** The ID of the lightning self-custody wallet */ + walletId: t.string, +} as const; + +export const LightningStateResponse200 = t.type({ + state: WalletState, +}); + +/** + * Response for getting lightning node state + */ +export const LightningStateResponse = { + /** Current Lightning wallet/node state('NON_EXISTING' | 'LOCKED' | 'UNLOCKED' | 'RPC_ACTIVE' | 'SERVER_ACTIVE' | 'WAITING_TO_START') */ + 200: LightningStateResponse200, + /** BitGo Express error payload when the request is invalid (e.g., invalid coin or wallet not a self-custody lightning wallet). */ + 400: BitgoExpressError, +} as const; + +/** + * Lightning - Get node state + * + * This is only used for self-custody lightning. Get the current state of the lightning node. + * + * @operationId express.lightning.getState + */ +export const GetLightningState = httpRoute({ + method: 'GET', + path: '/api/v2/{coin}/wallet/{walletId}/state', + request: httpRequest({ + params: LightningStateParams, + }), + response: LightningStateResponse, +}); diff --git a/modules/express/test/unit/clientRoutes/lightning/lightningSignerRoutes.ts b/modules/express/test/unit/clientRoutes/lightning/lightningSignerRoutes.ts index 82f693644c..805de7ebdd 100644 --- a/modules/express/test/unit/clientRoutes/lightning/lightningSignerRoutes.ts +++ b/modules/express/test/unit/clientRoutes/lightning/lightningSignerRoutes.ts @@ -16,6 +16,7 @@ import { } from '../../../../src/lightning/lightningSignerRoutes'; import { ExpressApiRouteRequest } from '../../../../src/typedRoutes/api'; import { PostLightningInitWallet } from '../../../../src/typedRoutes/api/v2/lightningInitWallet'; +import { LightningStateResponse } from '../../../../src/typedRoutes/api/v2/lightningState'; describe('Lightning signer routes', () => { let bitgo: TestBitGoAPI; @@ -165,13 +166,21 @@ describe('Lightning signer routes', () => { params: { coin: 'tlnbtc', id: apiData.wallet.id, + walletId: apiData.wallet.id, + }, + decoded: { + coin: 'tlnbtc', + walletId: apiData.wallet.id, }, config: { lightningSignerFileSystemPath: 'lightningSignerFileSystemPath', }, - } as unknown as express.Request; + } as unknown as ExpressApiRouteRequest<'express.lightning.getState', 'get'>; - await handleGetLightningWalletState(req); + const res = await handleGetLightningWalletState(req); + decodeOrElse('LightningStateResponse200', LightningStateResponse[200], res, () => { + throw new Error('Response did not match expected codec'); + }); walletStateNock.done(); readFileStub.calledOnceWith('lightningSignerFileSystemPath').should.be.true(); diff --git a/modules/express/test/unit/typedRoutes/decode.ts b/modules/express/test/unit/typedRoutes/decode.ts index 4385b786b1..2356b36f46 100644 --- a/modules/express/test/unit/typedRoutes/decode.ts +++ b/modules/express/test/unit/typedRoutes/decode.ts @@ -7,6 +7,7 @@ import { VerifyAddressBody } from '../../../src/typedRoutes/api/common/verifyAdd import { VerifyAddressV2Body, VerifyAddressV2Params } from '../../../src/typedRoutes/api/v2/verifyAddress'; import { SimpleCreateRequestBody } from '../../../src/typedRoutes/api/v1/simpleCreate'; import { KeychainLocalRequestParams } from '../../../src/typedRoutes/api/v2/keychainLocal'; +import { LightningStateParams } from '../../../src/typedRoutes/api/v2/lightningState'; import { LightningInitWalletBody, LightningInitWalletParams, @@ -157,6 +158,12 @@ describe('io-ts decode tests', function () { coin: 'tbtc', }); }); + it('express.lightning.getState params valid', function () { + assertDecode(t.type(LightningStateParams), { coin: 'lnbtc', walletId: 'wallet123' }); + }); + it('express.lightning.getState params invalid', function () { + assert.throws(() => assertDecode(t.type(LightningStateParams), { coin: 'lnbtc' })); + }); it('express.lightning.initWallet params', function () { // missing walletId assert.throws(() => assertDecode(t.type(LightningInitWalletParams), { coin: 'ltc' }));