diff --git a/src/__tests__/_helpers.ts b/src/__tests__/_helpers.ts new file mode 100644 index 0000000..54a1593 --- /dev/null +++ b/src/__tests__/_helpers.ts @@ -0,0 +1,191 @@ +import { AssetUnit, convert, usd } from '@kava-labs/crypto-rate-utils' +import 'envkey' +import { ExecutionContext } from 'ava' +import BigNumber from 'bignumber.js' +import { performance } from 'perf_hooks' +import { SwitchApi, SettlementEngineType, ReadyUplinks } from '..' +import { CredentialConfigs } from '../credential' + +// Return configs for connecting to accounts set up in env vars. +const ethConfig = (n: number): CredentialConfigs => { + return { + settlerType: SettlementEngineType.Machinomy, + privateKey: process.env[`ETH_PRIVATE_KEY_CLIENT_${n}`]! + } +} +const btcConfig = (n: number): CredentialConfigs => { + return { + settlerType: SettlementEngineType.Lnd, + hostname: process.env[`LIGHTNING_LND_HOST_CLIENT_${n}`]!, + tlsCert: process.env[`LIGHTNING_TLS_CERT_PATH_CLIENT_${n}`]!, + macaroon: process.env[`LIGHTNING_MACAROON_PATH_CLIENT_${n}`]!, + grpcPort: parseInt(process.env[`LIGHTNING_LND_GRPCPORT_CLIENT_${n}`]!, 10) + } +} +const xrpConfig = (n: number): CredentialConfigs => { + return { + settlerType: SettlementEngineType.XrpPaychan, + secret: process.env[`XRP_SECRET_CLIENT_${n}`]! + } +} + +export const addEth = (n: number) => ({ + add +}: SwitchApi): Promise => add(ethConfig(n)) +export const addBtc = (n: number) => ({ + add +}: SwitchApi): Promise => add(btcConfig(n)) +export const addXrp = (n: number) => ({ + add +}: SwitchApi): Promise => add(xrpConfig(n)) + +// Helper to test deposit and withdraw on uplinks +export const testFunding = ( + createUplink: (api: SwitchApi) => Promise +) => async (t: ExecutionContext) => { + const { state, deposit, withdraw, streamMoney } = t.context + const uplink = await createUplink(t.context) + + const settler = state.settlers[uplink.settlerType] + + // Instead down to the base unit of the ledger if there's more precision than that + const toUplinkUnit = (unit: AssetUnit) => + convert(unit, settler.exchangeUnit(), state.rateBackend).decimalPlaces( + settler.exchangeUnit().exchangeUnit, + BigNumber.ROUND_DOWN + ) + + t.true(uplink.balance$.value.isZero(), 'initial layer 2 balance is 0') + + // TODO Check base layer balances to make sure fees are also correctly reported! + // TODO Check that incoming capacity is opened! + + /** + * TODO Issue with xrp: openAmount has 9 digits of precision, but balance$ only has 6! + * e.g. openAmount === "2.959676012", uplink.balance$ === "2.959676" + */ + + const openAmount = toUplinkUnit(usd(1)) + await t.notThrowsAsync( + deposit({ + uplink, + amount: openAmount, + authorize: () => Promise.resolve() + }), + 'opens channel without throwing an error' + ) + + t.true( + uplink.balance$.value.isEqualTo(openAmount), + 'balance$ correctly reflects the initial channel open' + ) + + const depositAmount = toUplinkUnit(usd(2)) + await t.notThrowsAsync( + deposit({ + uplink, + amount: depositAmount, + authorize: () => Promise.resolve() + }), + 'deposits to channel without throwing an error' + ) + + t.true( + uplink.balance$.value.isEqualTo(openAmount.plus(depositAmount)), + 'balance$ correctly reflects the deposit to the channel' + ) + + // Rebalance so there's some money in both the incoming & outgoing channels + await t.notThrowsAsync( + streamMoney({ + amount: toUplinkUnit(usd(1.1)), + source: uplink, + dest: uplink + }), + 'uplink can stream money to itself' + ) + + await t.notThrowsAsync( + withdraw({ uplink, authorize: () => Promise.resolve() }), + 'withdraws from channel without throwing an error' + ) + + t.true( + uplink.balance$.value.isZero(), + 'balance$ of uplink goes back to zero following a withdraw' + ) +} + +// Helper to test streaming between different uplinks +export const testExchange = ( + createSource: (api: SwitchApi) => Promise, + createDest: (api: SwitchApi) => Promise +) => async (t: ExecutionContext) => { + const { state, deposit, streamMoney } = t.context + + const createFundedUplink = async ( + createUplink: (api: SwitchApi) => Promise + ) => { + const uplink = await createUplink(t.context) + await deposit({ + uplink, + amount: convert( + usd(3), + state.settlers[uplink.settlerType].exchangeUnit(), + state.rateBackend + ), + authorize: () => Promise.resolve() + }) + return uplink + } + + const [sourceUplink, destUplink] = await Promise.all([ + createFundedUplink(createSource), + createFundedUplink(createDest) + ]) + + // Without this pause after creating the uplinks, a stream from lnd to lnd fails. + // TODO fix + await new Promise(r => setTimeout(r, 500)) + + const initialSourceBalance = sourceUplink.balance$.value + const initialDestBalance = destUplink.balance$.value + + const sourceUnit = state.settlers[sourceUplink.settlerType].exchangeUnit + const destUnit = state.settlers[destUplink.settlerType].exchangeUnit + + const amountToSend = convert(usd(2), sourceUnit(), state.rateBackend) + const start = performance.now() + await t.notThrowsAsync( + streamMoney({ + amount: amountToSend, + source: sourceUplink, + dest: destUplink + }) + ) + t.log(`time: ${performance.now() - start} ms`) + + // Wait up to 2 seconds for the final settlements to come in (sigh) + await new Promise(r => setTimeout(r, 2000)) + + const finalSourceBalance = sourceUplink.balance$.value + t.true( + initialSourceBalance.minus(amountToSend).isEqualTo(finalSourceBalance), + 'source balance accurately represents the amount that was sent' + ) + + const estimatedReceiveAmount = convert( + sourceUnit(amountToSend), + destUnit(), + state.rateBackend + ) + const estimatedDestFinalBalance = initialDestBalance.plus( + estimatedReceiveAmount + ) + const finalDestBalance = destUplink.balance$.value + t.true( + finalDestBalance.isGreaterThan(estimatedDestFinalBalance.times(0.99)) && + finalDestBalance.isLessThan(estimatedDestFinalBalance.times(1.01)), + 'destination balance accounts for the amount that was sent, with margin for exchange rate fluctuations' + ) +} diff --git a/src/__tests__/add-remove.test.ts b/src/__tests__/add-remove.test.ts new file mode 100644 index 0000000..26d4ff2 --- /dev/null +++ b/src/__tests__/add-remove.test.ts @@ -0,0 +1,105 @@ +import anyTest, { TestInterface, ExecutionContext } from 'ava' +import 'envkey' +import { + SwitchApi, + connect, + LedgerEnv, + SettlementEngineType, + ReadyUplinks +} from '..' +import { addXrp, addEth, addBtc } from './_helpers' + +const test = anyTest as TestInterface + +// Before & after each test, construct and disconnect the API +test.beforeEach(async t => { + t.context = await connect(process.env.LEDGER_ENV! as LedgerEnv) +}) +test.afterEach(async t => t.context.disconnect()) + +// Test adding and removing uplinks +const testAddRemove = ( + createUplink: (api: SwitchApi) => Promise +) => async (t: ExecutionContext) => { + const uplink = await createUplink(t.context) + t.true(t.context.state.uplinks.includes(uplink)) + + await t.context.remove(uplink) + t.false(t.context.state.uplinks.includes(uplink)) +} +test('add then remove btc', testAddRemove(addBtc(1))) +test('add then remove eth without deposit', testAddRemove(addEth(1))) +test('add then remove xrp without deposit', testAddRemove(addXrp(1))) + +// Test that uplinks with the same credentials cannot be added +test('cannot add duplicate eth uplink', async t => { + await addEth(1)(t.context) + await t.throwsAsync(addEth(1)(t.context)) +}) +test('cannot add duplicate xrp uplink', async t => { + await addXrp(1)(t.context) + await t.throwsAsync(addXrp(1)(t.context)) +}) +test('cannot add duplicate btc uplink', async t => { + await addBtc(1)(t.context) + await t.throwsAsync(addBtc(1)(t.context)) +}) + +// Test credential config input validation +// Private key and credential validation is done by lower level libraries. + +test('add with invalid xrp secret throws', async t => { + await t.throwsAsync( + t.context.add({ + settlerType: SettlementEngineType.XrpPaychan, + secret: 'this is not a valid xrpSecret' // invalid but correct length + }), + 'Non-base58 character' + ) +}) +test('add with un-activated xrp secret throws', async t => { + await t.throwsAsync( + t.context.add({ + settlerType: SettlementEngineType.XrpPaychan, + secret: 'sn5s78zYX1i9mzFmd8jXooDFYgfj2' // un-activated but valid secret + }), + 'actNotFound' + ) +}) +// Test eth private keys. As long as they contain correct characters and are the right length they are a valid key. +test('add with invalid eth secret throws', async t => { + await t.throwsAsync( + t.context.add({ + settlerType: SettlementEngineType.Machinomy, + privateKey: + 'this is not a valid eth secret despite being the correct leength' + }) + ) + // Note: if the secret is correct length but contains invalid characters, an invalid length error is thrown ('private key length is invalid'). +}) +// Test valid lnd uri, but invalid credentials. +test('add with invalid lnd credentials throws', async t => { + await t.throwsAsync( + t.context.add({ + settlerType: SettlementEngineType.Lnd, + hostname: process.env.LIGHTNING_LND_HOST_CLIENT_1!, + grpcPort: parseInt(process.env.LIGHTNING_LND_GRPCPORT_CLIENT_1!, 10), + tlsCert: + 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNpRENDQWkrZ0F3SUJBZ0lRZG81djBRQlhIbmppNGhSYWVlTWpOREFLQmdncWhrak9QUVFEQWpCSE1SOHcKSFFZRFZRUUtFeFpzYm1RZ1lYVjBiMmRsYm1WeVlYUmxaQ0JqWlhKME1TUXdJZ1lEVlFRREV4dEtkWE4wZFhOegpMVTFoWTBKdmIyc3RVSEp2TFRNdWJHOWpZV3d3SGhjTk1UZ3dPREl6TURVMU9ERXdXaGNOTVRreE1ERTRNRFUxCk9ERXdXakJITVI4d0hRWURWUVFLRXhac2JtUWdZWFYwYjJkbGJtVnlZWFJsWkNCalpYSjBNU1F3SWdZRFZRUUQKRXh0S2RYTjBkWE56TFUxaFkwSnZiMnN0VUhKdkxUTXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtpTwpQUU1CQndOQ0FBU0ZoUm0rdy9UMTBQb0t0ZzRsbTloQk5KakpENDczZmt6SHdQVUZ3eTkxdlRyUVNmNzU0M2oyCkpyZ0ZvOG1iVFYwVnRwZ3FrZksxSU1WS01MckYyMXhpbzRIOE1JSDVNQTRHQTFVZER3RUIvd1FFQXdJQ3BEQVAKQmdOVkhSTUJBZjhFQlRBREFRSC9NSUhWQmdOVkhSRUVnYzB3Z2NxQ0cwcDFjM1IxYzNNdFRXRmpRbTl2YXkxUQpjbTh0TXk1c2IyTmhiSUlKYkc5allXeG9iM04wZ2dSMWJtbDRnZ3AxYm1sNGNHRmphMlYwaHdSL0FBQUJoeEFBCkFBQUFBQUFBQUFBQUFBQUFBQUFCaHhEK2dBQUFBQUFBQUFBQUFBQUFBQUFCaHhEK2dBQUFBQUFBQUF3bGM5WmMKazdiRGh3VEFxQUVFaHhEK2dBQUFBQUFBQUJpTnAvLytHeFhHaHhEK2dBQUFBQUFBQUtXSjV0bGlET1JqaHdRSwpEd0FDaHhEK2dBQUFBQUFBQUc2V3ovLyszYXRGaHhEOTJ0RFF5djRUQVFBQUFBQUFBQkFBTUFvR0NDcUdTTTQ5CkJBTUNBMGNBTUVRQ0lBOU85eHRhem1keENLajBNZmJGSFZCcTVJN0pNbk9GUHB3UlBKWFFmcllhQWlCZDVOeUoKUUN3bFN4NUVDblBPSDVzUnB2MjZUOGFVY1hibXlueDlDb0R1ZkE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==', + macaroon: + 'AgEDbG5kArsBAwoQ3/I9f6kgSE6aUPd85lWpOBIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV32ml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFgoHbWVzc2FnZRIEcmVhZBIFd3JpdGUaFwoIb2ZmY2hhaW4SBHJlYWQSBXdyaXRlGhYKB29uY2hhaW4SBHJlYWQSBXdyaXRlGhQKBXBlZXJzEgRyZWFkEgV3cml0ZQAABiAiUTBv3Eh6iDbdjmXCfNxp4HBEcOYNzXhrm+ncLHf5jA==' + }), + 'Failed to connect before the deadline' + ) +}) +test('add with invalid lnd uri throws', async t => { + await t.throwsAsync( + t.context.add({ + settlerType: SettlementEngineType.Lnd, + hostname: 'nonsense', + grpcPort: 2000, + tlsCert: 'nonsense', + macaroon: 'nonsense' + }) + ) +}) diff --git a/src/__tests__/disconnect.test.ts b/src/__tests__/disconnect.test.ts new file mode 100644 index 0000000..5c68e3d --- /dev/null +++ b/src/__tests__/disconnect.test.ts @@ -0,0 +1,48 @@ +import anyTest, { TestInterface, ExecutionContext } from 'ava' +import 'envkey' +import { SwitchApi, connect, LedgerEnv, ReadyUplinks } from '..' +import { addXrp, addEth, addBtc, testExchange } from './_helpers' +import BigNumber from 'bignumber.js' + +const test = anyTest as TestInterface + +// Before & after each test, construct and disconnect the API + +test.beforeEach(async t => { + t.context = await connect(process.env.LEDGER_ENV! as LedgerEnv) +}) + +test('after connect', async t => { + await t.notThrowsAsync(t.context.disconnect()) +}) + +test('after add eth', async t => { + const uplink = await addEth(1)(t.context) + await t.notThrowsAsync(t.context.disconnect()) +}) + +test('after deposit eth', async t => { + const uplink = await addEth(1)(t.context) + const openAmount = new BigNumber(0.01) + await t.context.deposit({ + uplink, + amount: openAmount, + authorize: () => Promise.resolve() + }) + await t.notThrowsAsync(t.context.disconnect()) +}) + +test('after withdraw eth', async t => { + const uplink = await addEth(1)(t.context) + const openAmount = new BigNumber(0.01) + await t.context.deposit({ + uplink, + amount: openAmount, + authorize: () => Promise.resolve() + }) + await t.context.withdraw({ uplink, authorize: () => Promise.resolve() }) + await t.notThrowsAsync(t.context.disconnect()) +}) + +// TODO test the other assets +// TODO maybe refactor the helpers to include a generic deposit method diff --git a/src/__tests__/stream-same-asset.test.ts b/src/__tests__/stream-same-asset.test.ts new file mode 100644 index 0000000..23ef54a --- /dev/null +++ b/src/__tests__/stream-same-asset.test.ts @@ -0,0 +1,18 @@ +import anyTest, { TestInterface } from 'ava' +import 'envkey' +import { SwitchApi, connect, LedgerEnv, ReadyUplinks } from '..' +import { addXrp, addEth, addBtc, testExchange } from './_helpers' + +const test = anyTest as TestInterface + +// Before & after each test, construct and disconnect the API + +test.beforeEach(async t => { + t.context = await connect(process.env.LEDGER_ENV! as LedgerEnv) +}) + +test.afterEach(async t => t.context.disconnect()) + +test('xrp -> xrp different credentials', testExchange(addXrp(1), addXrp(2))) +test('eth -> eth different credentials', testExchange(addEth(1), addEth(2))) +test('btc -> btc different credentials', testExchange(addBtc(2), addBtc(1))) diff --git a/src/__tests__/stream.test.ts b/src/__tests__/stream.test.ts index 4972667..079f1bc 100644 --- a/src/__tests__/stream.test.ts +++ b/src/__tests__/stream.test.ts @@ -1,39 +1,9 @@ -import { AssetUnit, convert, usd } from '@kava-labs/crypto-rate-utils' -import anyTest, { ExecutionContext, TestInterface } from 'ava' -import 'envkey' -import { - SwitchApi, - connect, - LedgerEnv, - SettlementEngineType, - ReadyUplinks -} from '..' -import BigNumber from 'bignumber.js' -import { performance } from 'perf_hooks' +import anyTest, { TestInterface } from 'ava' +import { SwitchApi, connect, LedgerEnv } from '..' +import { addEth, addXrp, addBtc, testFunding, testExchange } from './_helpers' const test = anyTest as TestInterface -export const addEth = ({ add }: SwitchApi): Promise => - add({ - settlerType: SettlementEngineType.Machinomy, - privateKey: process.env.ETH_PRIVATE_KEY_CLIENT_1! - }) - -export const addBtc = ({ add }: SwitchApi): Promise => - add({ - settlerType: SettlementEngineType.Lnd, - hostname: process.env.LIGHTNING_LND_HOST_CLIENT_1!, - tlsCert: process.env.LIGHTNING_TLS_CERT_PATH_CLIENT_1!, - macaroon: process.env.LIGHTNING_MACAROON_PATH_CLIENT_1!, - grpcPort: parseInt(process.env.LIGHTNING_LND_GRPCPORT_CLIENT_1!, 10) - }) - -export const addXrp = ({ add }: SwitchApi): Promise => - add({ - settlerType: SettlementEngineType.XrpPaychan, - secret: process.env.XRP_SECRET_CLIENT_1! - }) - // Before & after each test, construct and disconnect the API test.beforeEach(async t => { @@ -42,157 +12,12 @@ test.beforeEach(async t => { test.afterEach(async t => t.context.disconnect()) -const testFunding = ( - createUplink: (api: SwitchApi) => Promise -) => async (t: ExecutionContext) => { - const { state, deposit, withdraw, streamMoney } = t.context - const uplink = await createUplink(t.context) - - const settler = state.settlers[uplink.settlerType] - - // Instead down to the base unit of the ledger if there's more precision than that - const toUplinkUnit = (unit: AssetUnit) => - convert(unit, settler.exchangeUnit(), state.rateBackend).decimalPlaces( - settler.exchangeUnit().exchangeUnit, - BigNumber.ROUND_DOWN - ) - - t.true(uplink.balance$.value.isZero(), 'initial layer 2 balance is 0') - - // TODO Check base layer balances to make sure fees are also correctly reported! - // TODO Check that incoming capacity is opened! - - /** - * TODO Issue with xrp: openAmount has 9 digits of precision, but balance$ only has 6! - * e.g. openAmount === "2.959676012", uplink.balance$ === "2.959676" - */ - - const openAmount = toUplinkUnit(usd(1)) - await t.notThrowsAsync( - deposit({ - uplink, - amount: openAmount, - authorize: () => Promise.resolve() - }), - 'opens channel without throwing an error' - ) - - t.true( - uplink.balance$.value.isEqualTo(openAmount), - 'balance$ correctly reflects the initial channel open' - ) - - const depositAmount = toUplinkUnit(usd(2)) - await t.notThrowsAsync( - deposit({ - uplink, - amount: depositAmount, - authorize: () => Promise.resolve() - }), - 'deposits to channel without throwing an error' - ) - - t.true( - uplink.balance$.value.isEqualTo(openAmount.plus(depositAmount)), - 'balance$ correctly reflects the deposit to the channel' - ) - - // Rebalance so there's some money in both the incoming & outgoing channels - await t.notThrowsAsync( - streamMoney({ - amount: toUplinkUnit(usd(1.1)), - source: uplink, - dest: uplink - }), - 'uplink can stream money to itself' - ) - - await t.notThrowsAsync( - withdraw({ uplink, authorize: () => Promise.resolve() }), - 'withdraws from channel without throwing an error' - ) - - t.true( - uplink.balance$.value.isZero(), - 'balance$ of uplink goes back to zero following a withdraw' - ) -} - -const testExchange = ( - createSource: (api: SwitchApi) => Promise, - createDest: (api: SwitchApi) => Promise -) => async (t: ExecutionContext) => { - const { state, deposit, streamMoney } = t.context - - const createFundedUplink = async ( - createUplink: (api: SwitchApi) => Promise - ) => { - const uplink = await createUplink(t.context) - await deposit({ - uplink, - amount: convert( - usd(3), - state.settlers[uplink.settlerType].exchangeUnit(), - state.rateBackend - ), - authorize: () => Promise.resolve() - }) - return uplink - } - - const [sourceUplink, destUplink] = await Promise.all([ - createFundedUplink(createSource), - createFundedUplink(createDest) - ]) - - const initialSourceBalance = sourceUplink.balance$.value - const initialDestBalance = destUplink.balance$.value - - const sourceUnit = state.settlers[sourceUplink.settlerType].exchangeUnit - const destUnit = state.settlers[destUplink.settlerType].exchangeUnit - - const amountToSend = convert(usd(2), sourceUnit(), state.rateBackend) - const start = performance.now() - await t.notThrowsAsync( - streamMoney({ - amount: amountToSend, - source: sourceUplink, - dest: destUplink - }) - ) - t.log(`time: ${performance.now() - start} ms`) - - // Wait up to 2 seconds for the final settlements to come in (sigh) - await new Promise(r => setTimeout(r, 2000)) - - const finalSourceBalance = sourceUplink.balance$.value - t.true( - initialSourceBalance.minus(amountToSend).isEqualTo(finalSourceBalance), - 'source balance accurately represents the amount that was sent' - ) - - const estimatedReceiveAmount = convert( - sourceUnit(amountToSend), - destUnit(), - state.rateBackend - ) - const estimatedDestFinalBalance = initialDestBalance.plus( - estimatedReceiveAmount - ) - const finalDestBalance = destUplink.balance$.value - t.true( - finalDestBalance.isGreaterThan(estimatedDestFinalBalance.times(0.99)) && - finalDestBalance.isLessThan(estimatedDestFinalBalance.times(1.01)), - 'destination balance accounts for the amount that was sent, with margin for exchange rate fluctuations' - ) -} - -test('eth deposits & withdrawals', testFunding(addEth)) -test('xrp deposits & withdrawals', testFunding(addXrp)) +test('eth deposits & withdrawals', testFunding(addEth(1))) +test('xrp deposits & withdrawals', testFunding(addXrp(1))) -test('xrp -> eth', testExchange(addXrp, addEth)) -test('xrp -> btc', testExchange(addXrp, addBtc)) -test('btc -> eth', testExchange(addBtc, addEth)) -test('btc -> xrp', testExchange(addBtc, addXrp)) -test('eth -> btc', testExchange(addEth, addBtc)) -test('eth -> xrp', testExchange(addEth, addXrp)) +test('xrp -> eth', testExchange(addXrp(1), addEth(1))) +test('xrp -> btc', testExchange(addXrp(1), addBtc(1))) +test('btc -> eth', testExchange(addBtc(1), addEth(1))) +test('btc -> xrp', testExchange(addBtc(1), addXrp(1))) +test('eth -> btc', testExchange(addEth(1), addBtc(1))) +test('eth -> xrp', testExchange(addEth(1), addXrp(1))) diff --git a/src/index.ts b/src/index.ts index 8abb58b..82c5997 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,23 +147,16 @@ export const connect = async (ledgerEnv: LedgerEnv = LedgerEnv.Testnet) => { return } await closeUplink(internalUplink) - state = { - ...state, - uplinks: state.uplinks.filter(el => !isThatUplink(uplink)(el)) - } + state.uplinks = state.uplinks.filter(el => !isThatUplink(uplink)(el)) // Remove the credential const credentialsToClose = state.credentials.filter( isThatCredentialId(internalUplink.credentialId, uplink.settlerType) ) await Promise.all(credentialsToClose.map(closeCredential)) - - state = { - ...state, - credentials: state.credentials.filter( - someCredential => !credentialsToClose.includes(someCredential) - ) - } + state.credentials = state.credentials.filter( + someCredential => !credentialsToClose.includes(someCredential) + ) // TODO Close engine, if there aren't any other credentials that rely on it? } diff --git a/src/uplink.ts b/src/uplink.ts index fbf69c9..43ade94 100644 --- a/src/uplink.ts +++ b/src/uplink.ts @@ -136,8 +136,8 @@ export const createUplink = (state: State) => async ( const alreadyExists = state.uplinks.some( someUplink => someUplink.credentialId === credentialId && - someUplink.settlerType === readyCredential.settlerType && - false // TODO This MUST comapre the connector it's connected to! + someUplink.settlerType === readyCredential.settlerType + // TODO This MUST compare the connector it's connected to! ) if (alreadyExists) { throw new Error('Cannot create duplicate uplink')