Skip to content
2 changes: 1 addition & 1 deletion modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,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)]);
Expand Down Expand Up @@ -1808,5 +1809,4 @@ export function setupLightningSignerNodeRoutes(app: express.Application, config:
prepareBitGo(config),
promiseWrapper(handleUnlockLightningWallet)
);
app.get('/api/v2/:coin/wallet/:id/state', prepareBitGo(config), promiseWrapper(handleGetLightningWalletState));
}
10 changes: 4 additions & 6 deletions modules/express/src/lightning/lightningSignerRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,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<GetWalletStateResponse> {
const coinName = req.params.coin;
export async function handleGetLightningWalletState(
req: import('../typedRoutes/api').ExpressApiRouteRequest<'express.lightning.getState', 'get'>
): Promise<GetWalletStateResponse> {
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();
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 { GetLightningState } from './v2/lightningState';

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.lightning.getState': {
get: GetLightningState,
},
});

export type ExpressApi = typeof ExpressApi;
Expand Down
35 changes: 35 additions & 0 deletions modules/express/src/typedRoutes/api/v2/lightningState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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
* @property {string} coin - A lightning coin name (e.g., lnbtc or tlnbtc)
* @property {string} walletId - The ID of the lightning self-custody wallet
*/
export const LightningStateParams = {
coin: t.string,
walletId: t.string,
};

/**
* 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: {
200: t.type({
state: WalletState,
}),
400: BitgoExpressError,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
handleInitLightningWallet,
handleUnlockLightningWallet,
} from '../../../../src/lightning/lightningSignerRoutes';
import { ExpressApiRouteRequest } from '../../../../src/typedRoutes/api';

describe('Lightning signer routes', () => {
let bitgo: TestBitGoAPI;
Expand Down Expand Up @@ -153,11 +154,16 @@ 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);

Expand Down
7 changes: 7 additions & 0 deletions modules/express/test/unit/typedRoutes/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 { SimpleCreateRequestBody } from '../../../src/typedRoutes/api/v1/simpleCreate';
import { LightningStateParams } from '../../../src/typedRoutes/api/v2/lightningState';

export function assertDecode<T>(codec: t.Type<T, unknown>, input: unknown): T {
const result = codec.decode(input);
Expand Down Expand Up @@ -100,4 +101,10 @@ describe('io-ts decode tests', function () {
passphrase: 'pass',
});
});
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' }));
});
});