diff --git a/modules/abstract-lightning/src/wallet/custodialLightning.ts b/modules/abstract-lightning/src/wallet/custodialLightning.ts new file mode 100644 index 0000000000..a044d0ee98 --- /dev/null +++ b/modules/abstract-lightning/src/wallet/custodialLightning.ts @@ -0,0 +1,13 @@ +import { ILightningWallet, LightningWallet } from './lightning'; +import * as sdkcore from '@bitgo/sdk-core'; + +export type ICustodialLightningWallet = ILightningWallet; + +export class CustodialLightningWallet extends LightningWallet implements ICustodialLightningWallet { + constructor(wallet: sdkcore.IWallet) { + super(wallet); + if (wallet.type() !== 'custodial') { + throw new Error(`Invalid lightning wallet type for custodial lightning: ${wallet.type()}`); + } + } +} diff --git a/modules/abstract-lightning/src/wallet/index.ts b/modules/abstract-lightning/src/wallet/index.ts index 02a2e45150..a8d5e1f83f 100644 --- a/modules/abstract-lightning/src/wallet/index.ts +++ b/modules/abstract-lightning/src/wallet/index.ts @@ -1,2 +1,4 @@ export * from './lightning'; +export * from './custodialLightning'; +export * from './selfCustodialLightning'; export * from './wallet'; diff --git a/modules/abstract-lightning/src/wallet/lightning.ts b/modules/abstract-lightning/src/wallet/lightning.ts index b04fd4aa88..a15e6eaf58 100644 --- a/modules/abstract-lightning/src/wallet/lightning.ts +++ b/modules/abstract-lightning/src/wallet/lightning.ts @@ -25,7 +25,6 @@ import { Transaction, TransactionQuery, PaymentInfo, - BackupResponse, PaymentQuery, } from '../codecs'; import { LightningPaymentIntent, LightningPaymentRequest } from '@bitgo/public-types'; @@ -38,34 +37,145 @@ export type PayInvoiceResponse = { paymentStatus?: LndCreatePaymentResponse; }; -export interface ILightningWallet { - /** - * Get the lightning keychain for the given wallet. - */ - getLightningKeychain(): Promise; +/** + * Get the lightning keychain for the given wallet. + */ +export async function getLightningKeychain(wallet: sdkcore.IWallet): Promise { + const coin = wallet.baseCoin; + if (coin.getFamily() !== 'lnbtc') { + throw new Error(`Invalid coin to get lightning wallet key: ${coin.getFamily()}`); + } + const keyIds = wallet.keyIds(); + if (keyIds.length !== 1) { + throw new Error(`Invalid number of key in lightning wallet: ${keyIds.length}`); + } + const keychain = await coin.keychains().get({ id: keyIds[0] }); + return sdkcore.decodeOrElse(LightningKeychain.name, LightningKeychain, keychain, (_) => { + throw new Error(`Invalid user key`); + }); +} - /** - * Get the lightning auth keychains for the given wallet. - */ - getLightningAuthKeychains(): Promise<{ userAuthKey: LightningAuthKeychain; nodeAuthKey: LightningAuthKeychain }>; +/** + * Get the lightning auth keychains for the given wallet. + */ +export async function getLightningAuthKeychains(wallet: sdkcore.IWallet): Promise<{ + userAuthKey: LightningAuthKeychain; + nodeAuthKey: LightningAuthKeychain; +}> { + const coin = wallet.baseCoin; + if (coin.getFamily() !== 'lnbtc') { + throw new Error(`Invalid coin to get lightning wallet auth keys: ${coin.getFamily()}`); + } + const authKeyIds = wallet.coinSpecific()?.keys; + if (authKeyIds?.length !== 2) { + throw new Error(`Invalid number of auth keys in lightning wallet: ${authKeyIds?.length}`); + } + const keychains = await Promise.all(authKeyIds.map((id) => coin.keychains().get({ id }))); + const authKeychains = keychains.map((keychain) => { + return sdkcore.decodeOrElse(LightningAuthKeychain.name, LightningAuthKeychain, keychain, (_) => { + // DON'T throw errors from decodeOrElse. It could leak sensitive information. + throw new Error(`Invalid lightning auth key: ${keychain?.id}`); + }); + }); + const [userAuthKey, nodeAuthKey] = (['userAuth', 'nodeAuth'] as const).map((purpose) => { + const keychain = authKeychains.find( + (k) => unwrapLightningCoinSpecific(k.coinSpecific, coin.getChain()).purpose === purpose + ); + if (!keychain) { + throw new Error(`Missing ${purpose} key`); + } + return keychain; + }); - /** - * Updates the coin-specific configuration for a Lightning Wallet. - * - * @param {UpdateLightningWalletClientRequest} params - The parameters containing the updated wallet-specific details. - * - `encryptedSignerMacaroon` (optional): This macaroon is used by the watch-only node to ask the signer node to sign transactions. - * Encrypted with ECDH secret key from private key of wallet's user auth key and public key of lightning service. - * - `encryptedSignerAdminMacaroon` (optional): Generated when initializing the wallet of the signer node. - * Encrypted with client's wallet passphrase. - * - `signerHost` (optional): The host address of the Lightning signer node. - * - `encryptedSignerTlsKey` (optional): The wallet passphrase encrypted TLS key of the signer. - * - `passphrase` (required): The wallet passphrase. - * - `signerTlsCert` (optional): The TLS certificate of the signer. - * - `watchOnlyAccounts` (optional): These are the accounts used to initialize the watch-only wallet. - * @returns {Promise} A promise resolving to the updated wallet response or throwing an error if the update fails. - */ - updateWalletCoinSpecific(params: UpdateLightningWalletClientRequest): Promise; + return { userAuthKey, nodeAuthKey }; +} + +function encryptWalletUpdateRequest( + wallet: sdkcore.IWallet, + params: UpdateLightningWalletClientRequest, + userAuthKey: LightningAuthKeychain +): UpdateLightningWalletEncryptedRequest { + const coinName = wallet.coin() as 'tlnbtc' | 'lnbtc'; + + const requestWithEncryption: Partial = { + ...params, + }; + + const userAuthXprv = wallet.bitgo.decrypt({ + password: params.passphrase, + input: userAuthKey.encryptedPrv, + }); + if (params.signerTlsKey) { + requestWithEncryption.encryptedSignerTlsKey = wallet.bitgo.encrypt({ + password: params.passphrase, + input: params.signerTlsKey, + }); + } + + if (params.signerAdminMacaroon) { + requestWithEncryption.encryptedSignerAdminMacaroon = wallet.bitgo.encrypt({ + password: params.passphrase, + input: params.signerAdminMacaroon, + }); + } + + if (params.signerMacaroon) { + requestWithEncryption.encryptedSignerMacaroon = wallet.bitgo.encrypt({ + password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'), + input: params.signerMacaroon, + }); + } + + return t.exact(UpdateLightningWalletEncryptedRequest).encode(requestWithEncryption); +} + +/** + * Updates the coin-specific configuration for a Lightning Wallet. + * + * @param {Wallet} wallet - Wallet. + * @param {UpdateLightningWalletClientRequest} params - The parameters containing the updated wallet-specific details. + * - `encryptedSignerMacaroon` (optional): This macaroon is used by the watch-only node to ask the signer node to sign transactions. + * Encrypted with ECDH secret key from private key of wallet's user auth key and public key of lightning service. + * - `encryptedSignerAdminMacaroon` (optional): Generated when initializing the wallet of the signer node. + * Encrypted with client's wallet passphrase. + * - `signerHost` (optional): The host address of the Lightning signer node. + * - `encryptedSignerTlsKey` (optional): The wallet passphrase encrypted TLS key of the signer. + * - `passphrase` (required): The wallet passphrase. + * - `signerTlsCert` (optional): The TLS certificate of the signer. + * - `watchOnlyAccounts` (optional): These are the accounts used to initialize the watch-only wallet. + * @returns {Promise} A promise resolving to the updated wallet response or throwing an error if the update fails. + */ +export async function updateWalletCoinSpecific( + wallet: sdkcore.IWallet, + params: UpdateLightningWalletClientRequest +): Promise { + sdkcore.decodeOrElse( + UpdateLightningWalletClientRequest.name, + UpdateLightningWalletClientRequest, + params, + (errors) => { + // DON'T throw errors from decodeOrElse. It could leak sensitive information. + throw new Error(`Invalid params for lightning specific update wallet`); + } + ); + + const { userAuthKey } = await getLightningAuthKeychains(wallet); + const updateRequestWithEncryption = encryptWalletUpdateRequest(wallet, params, userAuthKey); + const signature = createMessageSignature( + updateRequestWithEncryption, + wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKey.encryptedPrv }) + ); + const coinSpecific = { + [wallet.coin()]: { + signedRequest: updateRequestWithEncryption, + signature, + }, + }; + return await wallet.bitgo.put(wallet.url()).send({ coinSpecific }).result(); +} + +export interface ILightningWallet { /** * Creates a lightning invoice * @param {object} params Invoice parameters @@ -138,130 +248,19 @@ export interface ILightningWallet { * @returns {Promise} List of transactions */ listTransactions(params: TransactionQuery): Promise; - - /** - * Get the channel backup for the given wallet. - * @returns {Promise} A promise resolving to the channel backup - */ - getChannelBackup(): Promise; } -export class SelfCustodialLightningWallet implements ILightningWallet { +export class LightningWallet implements ILightningWallet { public wallet: sdkcore.IWallet; constructor(wallet: sdkcore.IWallet) { const coin = wallet.baseCoin; if (coin.getFamily() !== 'lnbtc') { - throw new Error(`Invalid coin to update lightning wallet: ${coin.getFamily()}`); + throw new Error(`Invalid coin for lightning wallet: ${coin.getFamily()}`); } this.wallet = wallet; } - private encryptWalletUpdateRequest( - params: UpdateLightningWalletClientRequest, - userAuthKey: LightningAuthKeychain - ): UpdateLightningWalletEncryptedRequest { - const coinName = this.wallet.coin() as 'tlnbtc' | 'lnbtc'; - - const requestWithEncryption: Partial = { - ...params, - }; - - const userAuthXprv = this.wallet.bitgo.decrypt({ - password: params.passphrase, - input: userAuthKey.encryptedPrv, - }); - - if (params.signerTlsKey) { - requestWithEncryption.encryptedSignerTlsKey = this.wallet.bitgo.encrypt({ - password: params.passphrase, - input: params.signerTlsKey, - }); - } - - if (params.signerAdminMacaroon) { - requestWithEncryption.encryptedSignerAdminMacaroon = this.wallet.bitgo.encrypt({ - password: params.passphrase, - input: params.signerAdminMacaroon, - }); - } - - if (params.signerMacaroon) { - requestWithEncryption.encryptedSignerMacaroon = this.wallet.bitgo.encrypt({ - password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'), - input: params.signerMacaroon, - }); - } - - return t.exact(UpdateLightningWalletEncryptedRequest).encode(requestWithEncryption); - } - - async getLightningKeychain(): Promise { - const keyIds = this.wallet.keyIds(); - if (keyIds.length !== 1) { - throw new Error(`Invalid number of key in lightning wallet: ${keyIds.length}`); - } - const keychain = await this.wallet.baseCoin.keychains().get({ id: keyIds[0] }); - return sdkcore.decodeOrElse(LightningKeychain.name, LightningKeychain, keychain, (_) => { - throw new Error(`Invalid user key`); - }); - } - - async getLightningAuthKeychains(): Promise<{ - userAuthKey: LightningAuthKeychain; - nodeAuthKey: LightningAuthKeychain; - }> { - const authKeyIds = this.wallet.coinSpecific()?.keys; - if (authKeyIds?.length !== 2) { - throw new Error(`Invalid number of auth keys in lightning wallet: ${authKeyIds?.length}`); - } - const coin = this.wallet.baseCoin; - const keychains = await Promise.all(authKeyIds.map((id) => coin.keychains().get({ id }))); - const authKeychains = keychains.map((keychain) => { - return sdkcore.decodeOrElse(LightningAuthKeychain.name, LightningAuthKeychain, keychain, (_) => { - // DON'T throw errors from decodeOrElse. It could leak sensitive information. - throw new Error(`Invalid lightning auth key: ${keychain?.id}`); - }); - }); - const [userAuthKey, nodeAuthKey] = (['userAuth', 'nodeAuth'] as const).map((purpose) => { - const keychain = authKeychains.find( - (k) => unwrapLightningCoinSpecific(k.coinSpecific, coin.getChain()).purpose === purpose - ); - if (!keychain) { - throw new Error(`Missing ${purpose} key`); - } - return keychain; - }); - - return { userAuthKey, nodeAuthKey }; - } - - async updateWalletCoinSpecific(params: UpdateLightningWalletClientRequest): Promise { - sdkcore.decodeOrElse( - UpdateLightningWalletClientRequest.name, - UpdateLightningWalletClientRequest, - params, - (errors) => { - // DON'T throw errors from decodeOrElse. It could leak sensitive information. - throw new Error(`Invalid params for lightning specific update wallet`); - } - ); - - const { userAuthKey } = await this.getLightningAuthKeychains(); - const updateRequestWithEncryption = this.encryptWalletUpdateRequest(params, userAuthKey); - const signature = createMessageSignature( - updateRequestWithEncryption, - this.wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKey.encryptedPrv }) - ); - const coinSpecific = { - [this.wallet.coin()]: { - signedRequest: updateRequestWithEncryption, - signature, - }, - }; - return await this.wallet.bitgo.put(this.wallet.url()).send({ coinSpecific }).result(); - } - async createInvoice(params: CreateInvoiceBody): Promise { const createInvoiceResponse = await this.wallet.bitgo .post(this.wallet.baseCoin.url(`/wallet/${this.wallet.id()}/lightning/invoice`)) @@ -297,7 +296,7 @@ export class SelfCustodialLightningWallet implements ILightningWallet { const reqId = new RequestTracer(); this.wallet.bitgo.setRequestTracer(reqId); - const { userAuthKey } = await this.getLightningAuthKeychains(); + const { userAuthKey } = await getLightningAuthKeychains(this.wallet); const signature = createMessageSignature( t.exact(LightningPaymentRequest).encode(params), this.wallet.bitgo.decrypt({ password: params.passphrase, input: userAuthKey.encryptedPrv }) @@ -387,13 +386,4 @@ export class SelfCustodialLightningWallet implements ILightningWallet { throw new Error(`Invalid transaction list response: ${error}`); }); } - - async getChannelBackup(): Promise { - const backupResponse = await this.wallet.bitgo - .get(this.wallet.baseCoin.url(`/wallet/${this.wallet.id()}/lightning/backup`)) - .result(); - return sdkcore.decodeOrElse(BackupResponse.name, BackupResponse, backupResponse, (error) => { - throw new Error(`Invalid backup response: ${error}`); - }); - } } diff --git a/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts b/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts new file mode 100644 index 0000000000..0b691cbb08 --- /dev/null +++ b/modules/abstract-lightning/src/wallet/selfCustodialLightning.ts @@ -0,0 +1,29 @@ +import * as sdkcore from '@bitgo/sdk-core'; +import { BackupResponse } from '../codecs'; +import { ILightningWallet, LightningWallet } from './lightning'; + +export interface ISelfCustodialLightningWallet extends ILightningWallet { + /** + * Get the channel backup for the given wallet. + * @returns {Promise} A promise resolving to the channel backup + */ + getChannelBackup(): Promise; +} + +export class SelfCustodialLightningWallet extends LightningWallet implements ISelfCustodialLightningWallet { + constructor(wallet: sdkcore.IWallet) { + super(wallet); + if (wallet.type() !== 'hot') { + throw new Error(`Invalid lightning wallet type for self custodial lightning: ${wallet.type()}`); + } + } + + async getChannelBackup(): Promise { + const backupResponse = await this.wallet.bitgo + .get(this.wallet.baseCoin.url(`/wallet/${this.wallet.id()}/lightning/backup`)) + .result(); + return sdkcore.decodeOrElse(BackupResponse.name, BackupResponse, backupResponse, (error) => { + throw new Error(`Invalid backup response: ${error}`); + }); + } +} diff --git a/modules/abstract-lightning/src/wallet/wallet.ts b/modules/abstract-lightning/src/wallet/wallet.ts index 95114b7bef..40e0f6373f 100644 --- a/modules/abstract-lightning/src/wallet/wallet.ts +++ b/modules/abstract-lightning/src/wallet/wallet.ts @@ -1,14 +1,22 @@ import * as sdkcore from '@bitgo/sdk-core'; -import { isLightningCoinName } from '../lightning'; -import { ILightningWallet, SelfCustodialLightningWallet } from './lightning'; +import { SelfCustodialLightningWallet } from './selfCustodialLightning'; +import { CustodialLightningWallet } from './custodialLightning'; +import { ILightningWallet } from './lightning'; /** - * Return a lightwallet instance if the coin supports it + * Returns custodial or self custodial lightning wallet depends on wallet type. */ - export function getLightningWallet(wallet: sdkcore.IWallet): ILightningWallet { - if (!isLightningCoinName(wallet.baseCoin.getChain())) { - throw new Error(`Lightning not supported for ${wallet.coin()}`); + if (wallet.baseCoin.getFamily() !== 'lnbtc') { + throw new Error(`invalid coin for lightning wallet: ${wallet.baseCoin.getFamily()}`); + } + + switch (wallet.type()) { + case 'custodial': + return new CustodialLightningWallet(wallet); + case 'hot': + return new SelfCustodialLightningWallet(wallet); + default: + throw new Error(`invalid wallet type ${wallet.type()} for for lightning coin`); } - return new SelfCustodialLightningWallet(wallet); } diff --git a/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts b/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts index 59e90009ac..4431552343 100644 --- a/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts +++ b/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts @@ -9,9 +9,12 @@ import { InvoiceInfo, InvoiceQuery, LndCreatePaymentResponse, - SelfCustodialLightningWallet, + LightningWallet, SubmitPaymentParams, UpdateLightningWalletClientRequest, + getLightningKeychain, + getLightningAuthKeychains, + updateWalletCoinSpecific, } from '@bitgo/abstract-lightning'; import { BitGo, common, GenerateLightningWalletOptions, Wallet, Wallets } from '../../../../src'; @@ -217,11 +220,11 @@ describe('Lightning wallets', function () { }); describe('invoices', function () { - let wallet: SelfCustodialLightningWallet; + let wallet: LightningWallet; beforeEach(function () { wallet = getLightningWallet( new Wallet(bitgo, basecoin, { id: 'walletId', coin: 'tlnbtc', coinSpecific: { keys: ['def', 'ghi'] } }) - ) as SelfCustodialLightningWallet; + ) as LightningWallet; }); it('should list invoices', async function () { @@ -470,19 +473,19 @@ describe('Lightning wallets', function () { }; it('should get lightning key', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, walletData)); + const wallet = new Wallet(bitgo, basecoin, walletData); const keyNock = nock(bgUrl) .get('/api/v2/' + coinName + '/key/abc') .reply(200, userKeyData); - const key = await wallet.getLightningKeychain(); + const key = await getLightningKeychain(wallet); assert.deepStrictEqual(key, userKeyData); keyNock.done(); }); it('should get lightning auth keys', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, walletData)); + const wallet = new Wallet(bitgo, basecoin, walletData); const userAuthKeyNock = nock(bgUrl) .get('/api/v2/' + coinName + '/key/def') @@ -491,7 +494,7 @@ describe('Lightning wallets', function () { .get('/api/v2/' + coinName + '/key/ghi') .reply(200, nodeAuthKeyData); - const { userAuthKey, nodeAuthKey } = await wallet.getLightningAuthKeychains(); + const { userAuthKey, nodeAuthKey } = await getLightningAuthKeychains(wallet); assert.deepStrictEqual(userAuthKey, userAuthKeyData); assert.deepStrictEqual(nodeAuthKey, nodeAuthKeyData); userAuthKeyNock.done(); @@ -499,35 +502,33 @@ describe('Lightning wallets', function () { }); it('should fail to get lightning key for invalid number of keys', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, { ...walletData, keys: [] })); + const wallet = new Wallet(bitgo, basecoin, { ...walletData, keys: [] }); await assert.rejects( - async () => await wallet.getLightningKeychain(), + async () => await getLightningKeychain(wallet), /Error: Invalid number of key in lightning wallet: 0/ ); }); it('should fail to get lightning auth keys for invalid number of keys', async function () { - const wallet = getLightningWallet( - new Wallet(bitgo, basecoin, { ...walletData, coinSpecific: { keys: ['def'] } }) - ); + const wallet = new Wallet(bitgo, basecoin, { ...walletData, coinSpecific: { keys: ['def'] } }); await assert.rejects( - async () => await wallet.getLightningAuthKeychains(), + async () => await getLightningAuthKeychains(wallet), /Error: Invalid number of auth keys in lightning wallet: 1/ ); }); it('should fail to get lightning key for invalid response', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, walletData)); + const wallet = new Wallet(bitgo, basecoin, walletData); nock(bgUrl) .get('/api/v2/' + coinName + '/key/abc') .reply(200, { ...userKeyData, source: 'backup' }); - await assert.rejects(async () => await wallet.getLightningKeychain(), /Error: Invalid user key/); + await assert.rejects(async () => await getLightningKeychain(wallet), /Error: Invalid user key/); }); it('should fail to get lightning auth keys for invalid response', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, walletData)); + const wallet = new Wallet(bitgo, basecoin, walletData); nock(bgUrl) .get('/api/v2/' + coinName + '/key/def') @@ -538,7 +539,7 @@ describe('Lightning wallets', function () { .reply(200, nodeAuthKeyData); await assert.rejects( - async () => await wallet.getLightningAuthKeychains(), + async () => await getLightningAuthKeychains(wallet), /Error: Invalid lightning auth key: def/ ); }); @@ -571,7 +572,7 @@ describe('Lightning wallets', function () { ], }; it('should update wallet', async function () { - const wallet = getLightningWallet(new Wallet(bitgo, basecoin, walletData)); + const wallet = new Wallet(bitgo, basecoin, walletData); const userAuthKeyNock = nock(bgUrl) .get('/api/v2/' + coinName + '/key/def') @@ -596,7 +597,7 @@ describe('Lightning wallets', function () { passphrase: 'password123', }; - await assert.doesNotReject(async () => await wallet.updateWalletCoinSpecific(params)); + await assert.doesNotReject(async () => await updateWalletCoinSpecific(wallet, params)); assert(userAuthKeyNock.isDone()); assert(nodeAuthKeyNock.isDone()); assert(wpWalletUpdateNock.isDone()); diff --git a/modules/express/src/lightning/lightningBackupRoutes.ts b/modules/express/src/lightning/lightningBackupRoutes.ts index 8e597855ef..885d3f8adb 100644 --- a/modules/express/src/lightning/lightningBackupRoutes.ts +++ b/modules/express/src/lightning/lightningBackupRoutes.ts @@ -1,5 +1,5 @@ import * as express from 'express'; -import { BackupResponse, getLightningWallet } from '@bitgo/abstract-lightning'; +import { BackupResponse, getLightningWallet, SelfCustodialLightningWallet } from '@bitgo/abstract-lightning'; /** * Handle getting channel backup for a Lightning wallet @@ -11,6 +11,8 @@ export async function handleGetChannelBackup(req: express.Request): Promise { diff --git a/modules/express/test/unit/lightning/lightningWalletRoutes.test.ts b/modules/express/test/unit/lightning/lightningWalletRoutes.test.ts index 9c7dbdfda3..7003a6afa5 100644 --- a/modules/express/test/unit/lightning/lightningWalletRoutes.test.ts +++ b/modules/express/test/unit/lightning/lightningWalletRoutes.test.ts @@ -134,14 +134,11 @@ describe('Lightning Wallet Routes', () => { }; const updateStub = sinon.stub().resolves(expectedResponse); - const mockLightningWallet = { - updateWalletCoinSpecific: updateStub, - }; const proxyquire = require('proxyquire'); const lightningRoutes = proxyquire('../../../src/lightning/lightningWalletRoutes', { '@bitgo/abstract-lightning': { - getLightningWallet: () => mockLightningWallet, + updateWalletCoinSpecific: updateStub, }, }); @@ -155,10 +152,10 @@ describe('Lightning Wallet Routes', () => { should(result).deepEqual(expectedResponse); should(updateStub).be.calledOnce(); - const [firstArg] = updateStub.getCall(0).args; - should(firstArg).have.property('signerMacaroon', 'encrypted-macaroon-data'); - should(firstArg).have.property('signerHost', 'signer.example.com'); - should(firstArg).have.property('passphrase', 'wallet-password-123'); + const secondArg = updateStub.getCall(0).args[1]; + should(secondArg).have.property('signerMacaroon', 'encrypted-macaroon-data'); + should(secondArg).have.property('signerHost', 'signer.example.com'); + should(secondArg).have.property('passphrase', 'wallet-password-123'); }); it('should throw error when passphrase is missing', async () => {