diff --git a/src/app/wallets/add-invoice-for-wallet.ts b/src/app/wallets/add-invoice-for-wallet.ts index 59890cd95..9a301d04b 100644 --- a/src/app/wallets/add-invoice-for-wallet.ts +++ b/src/app/wallets/add-invoice-for-wallet.ts @@ -3,6 +3,7 @@ import { toSats } from "@domain/bitcoin" import { checkedToWalletId } from "@domain/wallets" import { RateLimitConfig } from "@domain/rate-limit" import { checkedToMinutes } from "@domain/primitives" +import { UnsupportedCurrencyError } from "@domain/errors" import { RateLimiterExceededError } from "@domain/rate-limit/errors" import { DEFAULT_EXPIRATIONS } from "@domain/bitcoin/lightning/invoice-expiration" import { WalletInvoiceBuilder } from "@domain/wallet-invoices/wallet-invoice-builder" @@ -16,13 +17,14 @@ import { WalletsRepository, } from "@services/mongoose" -import { validateIsBtcWallet, validateIsUsdWallet } from "./validate" import Ibex from "@services/ibex/client" import { IbexError, UnexpectedIbexResponse } from "@services/ibex/errors" import { decodeInvoice } from "@domain/bitcoin/lightning/ln-invoice" -import { checkedToUsdPaymentAmount, USDAmount, UsdPaymentAmount, ValidationError } from "@domain/shared" +import { USDAmount } from "@domain/shared" import { AddInvoiceResponse201 } from "ibex-client" +import { validateIsBtcWallet, validateIsUsdWallet } from "./validate" + const defaultBtcExpiration = DEFAULT_EXPIRATIONS["BTC"].delayMinutes const defaultUsdExpiration = DEFAULT_EXPIRATIONS["USD"].delayMinutes @@ -34,7 +36,7 @@ const addInvoiceForSelf = async ({ }: AddInvoiceForSelfArgs): Promise => { const wallet = await WalletsRepository().findById(walletId) if (wallet instanceof Error) return wallet - + const account = await AccountsRepository().findById(wallet.accountId) if (account instanceof Error) return account @@ -44,32 +46,27 @@ const addInvoiceForSelf = async ({ const limitOk = await checkSelfWalletIdRateLimits(wallet.accountId) if (limitOk instanceof Error) return limitOk - const checkedAmount = amount ? USDAmount.cents(amount.toString()) : undefined + const checkedAmount = amount ? USDAmount.cents(amount.toString()) : undefined if (checkedAmount instanceof Error) return checkedAmount const resp = await Ibex.addInvoice({ - amount: checkedAmount, + amount: checkedAmount, accountId: walletId, memo, - expiration: expiresIn * 60 as Seconds, + expiration: (expiresIn * 60) as Seconds, }) if (resp instanceof IbexError) return resp return toDomainInvoice(resp) } -// Flash fork: remove because not BTC Wallets -// export const addInvoiceForSelfForBtcWallet = async ( -// args: AddInvoiceForSelfForBtcWalletArgs, -// ): Promise => { -// const walletId = checkedToWalletId(args.walletId) -// if (walletId instanceof Error) return walletId - -// const expiresIn = checkedToMinutes(args.expiresIn || defaultBtcExpiration) -// if (expiresIn instanceof Error) return expiresIn - -// const validated = await validateIsBtcWallet(walletId) -// if (validated instanceof Error) return validated -// return addInvoiceForSelf({ ...args, walletId, expiresIn }) -// } +// Flash fork: BTC invoices are not supported, but the legacy Galoy tests still +// reference this API. Keep the export typed so the test suite can typecheck. +export const addInvoiceForSelfForBtcWallet = async ( + args: AddInvoiceForSelfForBtcWalletArgs, +): Promise => { + return new UnsupportedCurrencyError( + `BTC invoices are not supported for wallet ${args.walletId}`, + ) +} export const addInvoiceForSelfForUsdWallet = async ( args: AddInvoiceForSelfForUsdWalletArgs, @@ -122,12 +119,11 @@ const addInvoiceForRecipient = async ({ recipientWalletId, amount, memo = "", - descriptionHash, expiresIn, }: AddInvoiceForRecipientArgs): Promise => { const wallet = await WalletsRepository().findById(recipientWalletId) if (wallet instanceof Error) return wallet - + const account = await AccountsRepository().findById(wallet.accountId) if (account instanceof Error) return account @@ -143,26 +139,20 @@ const addInvoiceForRecipient = async ({ amount: checkedAmount, accountId: recipientWalletId, memo, - expiration: expiresIn ? expiresIn * 60 as Seconds : undefined, + expiration: expiresIn ? ((expiresIn * 60) as Seconds) : undefined, }) if (resp instanceof IbexError) return resp return toDomainInvoice(resp) } -// export const addInvoiceForRecipientForBtcWallet = async ( -// args: AddInvoiceForRecipientForBtcWalletArgs, -// ): Promise => { -// const recipientWalletId = checkedToWalletId(args.recipientWalletId) -// if (recipientWalletId instanceof Error) return recipientWalletId - -// const expiresIn = checkedToMinutes(args.expiresIn || defaultBtcExpiration) -// if (expiresIn instanceof Error) return expiresIn - -// const validated = await validateIsBtcWallet(recipientWalletId) -// if (validated instanceof Error) return validated -// return addInvoiceForRecipient({ ...args, recipientWalletId, expiresIn }) -// } +export const addInvoiceForRecipientForBtcWallet = async ( + args: AddInvoiceForRecipientForBtcWalletArgs, +): Promise => { + return new UnsupportedCurrencyError( + `BTC invoices are not supported for wallet ${args.recipientWalletId}`, + ) +} export const addInvoiceForRecipientForUsdWallet = async ( args: AddInvoiceForRecipientForUsdWalletArgs, @@ -176,7 +166,6 @@ export const addInvoiceForRecipientForUsdWallet = async ( const validated = await validateIsUsdWallet(recipientWalletId) if (validated instanceof Error) return validated - return addInvoiceForRecipient({ ...args, recipientWalletId, expiresIn }) } @@ -273,12 +262,12 @@ const checkRecipientWalletIdRateLimits = async ( }) // Takes a successful Ibex Response and returns a domain 'LnInvoice' or error -const toDomainInvoice = (ibex: AddInvoiceResponse201): (LnInvoice | ApplicationError) => { +const toDomainInvoice = (ibex: AddInvoiceResponse201): LnInvoice | ApplicationError => { const invoiceString: string | undefined = ibex.invoice?.bolt11 if (!invoiceString) return new UnexpectedIbexResponse("Could not find invoice.") - + const decodedInvoice = decodeInvoice(invoiceString) - if (decodedInvoice instanceof Error) return decodedInvoice + if (decodedInvoice instanceof Error) return decodedInvoice return { destination: decodedInvoice.destination, @@ -295,4 +284,4 @@ const toDomainInvoice = (ibex: AddInvoiceResponse201): (LnInvoice | ApplicationE expiresAt: decodedInvoice.expiresAt, isExpired: decodedInvoice.isExpired, } -} \ No newline at end of file +} diff --git a/src/app/wallets/index.types.d.ts b/src/app/wallets/index.types.d.ts index 80ad81ddb..92fa412f6 100644 --- a/src/app/wallets/index.types.d.ts +++ b/src/app/wallets/index.types.d.ts @@ -5,12 +5,12 @@ type AddInvoiceForSelfArgs = { expiresIn: Minutes } -// type AddInvoiceForSelfForBtcWalletArgs = { -// walletId: string -// amount: number -// memo?: string -// expiresIn?: number -// } +type AddInvoiceForSelfForBtcWalletArgs = { + walletId: string + amount: BtcPaymentAmount + memo?: string + expiresIn?: number +} type AddInvoiceForSelfForUsdWalletArgs = { walletId: string @@ -35,7 +35,7 @@ type AddInvoiceForRecipientArgs = { type AddInvoiceForRecipientForBtcWalletArgs = { recipientWalletId: string - amount: number + amount: BtcPaymentAmount memo?: string descriptionHash?: string expiresIn?: number @@ -72,7 +72,7 @@ type GetOnChainFeeWithoutCurrencyArgs = { walletId: WalletId account: Account amount: number - address: OnChainAddress + address: string | OnChainAddress speed: PayoutSpeed } diff --git a/src/app/wallets/send-on-chain.ts b/src/app/wallets/send-on-chain.ts index 61bd040b8..28deaa0e7 100644 --- a/src/app/wallets/send-on-chain.ts +++ b/src/app/wallets/send-on-chain.ts @@ -6,10 +6,7 @@ import { UnsupportedCurrencyError } from "@domain/errors" import { ErrorLevel, USDAmount } from "@domain/shared" import { OnchainUsdPaymentValidator } from "@domain/wallets" -import { - AccountsRepository, - WalletsRepository, -} from "@services/mongoose" +import { AccountsRepository, WalletsRepository } from "@services/mongoose" import { recordExceptionInCurrentSpan } from "@services/tracing" import Ibex from "@services/ibex/client" @@ -22,37 +19,40 @@ type PayOnChainByWalletIdWithoutCurrencyArgs = { address: string speed: PayoutSpeed memo: string | null + amount?: number | FractionalCentAmount | USDAmount } type PayOnChainByUsdArgs = PayOnChainByWalletIdWithoutCurrencyArgs & { amount: USDAmount } +type PayOnChainByWalletIdForUsdWalletArgs = PayOnChainByWalletIdWithoutCurrencyArgs & { + amount: number | FractionalCentAmount | USDAmount +} + /* - * The following code has been changed for Flash as follows: - * - * 1. Intraledger payments - - * Ibex does not allow us to check if address is Ibex owned, - * so we are currently unable to distinguish intraledger vs external - * To do so, we would need to track the address ourselves in mongo - * 2. Rate conversion (BTC-USD) - Ibex handles - * 3. Volume limit checks - either need to reference an internal ledger or, - * get recent trx volume from Ibex - * - * To reintroduce, see the Galoy codebase: - */ -export const payOnChainByWalletId = async ({ + * The following code has been changed for Flash as follows: + * + * 1. Intraledger payments - + * Ibex does not allow us to check if address is Ibex owned, + * so we are currently unable to distinguish intraledger vs external + * To do so, we would need to track the address ourselves in mongo + * 2. Rate conversion (BTC-USD) - Ibex handles + * 3. Volume limit checks - either need to reference an internal ledger or, + * get recent trx volume from Ibex + * + * To reintroduce, see the Galoy codebase: + */ +export const payOnChainByWalletId = async ({ senderAccount, senderWalletId, amount, address, - speed, - memo, }: PayOnChainByUsdArgs): Promise => { // For testing purposes, would be nice to extract these functions const latestAccountState = await AccountsRepository().findById(senderAccount.id) if (latestAccountState instanceof Error) return latestAccountState - + const senderWallet = await WalletsRepository().findById(senderWalletId as WalletId) if (senderWallet instanceof Error) return senderWallet @@ -77,13 +77,13 @@ export const payOnChainByWalletId = async ({ const resp = await Ibex.sendOnchain(args) if (resp instanceof IbexError) return resp - + let status = IbexAdaptor.toPaymentSendStatus(resp.status) if (status instanceof UnexpectedIbexResponse) { recordExceptionInCurrentSpan({ error: status, level: ErrorLevel.Warn, - }) + }) status = PaymentSendStatus.Pending } return { @@ -96,11 +96,21 @@ export const payOnChainByWalletId = async ({ export const payOnChainByWalletIdForBtcWallet = async ( args: PayOnChainByWalletIdWithoutCurrencyArgs, ): Promise => { - return new UnsupportedCurrencyError("BTC amount is not supported") + return new UnsupportedCurrencyError( + `BTC amount is not supported for wallet ${args.senderWalletId}`, + ) +} + +export const payOnChainByWalletIdForUsdWallet = async ( + args: PayOnChainByWalletIdForUsdWalletArgs, +): Promise => { + return payOnChainByWalletId(args as PayOnChainByUsdArgs) } export const payOnChainByWalletIdForUsdWalletAndBtcAmount = async ( args: PayOnChainByWalletIdWithoutCurrencyArgs, ): Promise => { - return new UnsupportedCurrencyError("BTC amount is not supported") + return new UnsupportedCurrencyError( + `BTC amount is not supported for wallet ${args.senderWalletId}`, + ) } diff --git a/src/domain/wallets/index.types.d.ts b/src/domain/wallets/index.types.d.ts index 4b4788cf4..e3db04f36 100644 --- a/src/domain/wallets/index.types.d.ts +++ b/src/domain/wallets/index.types.d.ts @@ -74,7 +74,7 @@ type BaseWalletTransaction = { readonly settlementAmount: Satoshis | UsdCents readonly settlementFee: Satoshis | UsdCents readonly settlementCurrency: WalletCurrency - + readonly settlementDisplayAmount: DisplayCurrencyMajorAmount readonly settlementDisplayFee: DisplayCurrencyMajorAmount readonly settlementDisplayPrice: WalletMinorUnitDisplayPrice< @@ -131,12 +131,12 @@ type WalletLnSettledTransaction = BaseWalletTransaction & { } type UnknownTypeTransaction = BaseWalletTransaction & { - readonly initiationVia: { - readonly type: 'unknown' - } - readonly settlementVia: { - readonly type: 'unknown' - } + readonly initiationVia: { + readonly type: "unknown" + } + readonly settlementVia: { + readonly type: "unknown" + } } type WalletOnChainTransaction = @@ -235,14 +235,3 @@ type OnChainFeeCalculator = { } intraLedgerFees(): PaymentAmountInAllCurrencies } - -type PaymentInputValidatorConfig = ( - walletId: WalletId, -) => Promise - - -type PaymentInputValidator = { - validatePaymentInput: ( - args: ValidatePaymentInputArgs, - ) => Promise | ValidationError | RepositoryError> -} diff --git a/src/domain/wallets/payment-input-validator.ts b/src/domain/wallets/payment-input-validator.ts index ed35a161e..eefe1b093 100644 --- a/src/domain/wallets/payment-input-validator.ts +++ b/src/domain/wallets/payment-input-validator.ts @@ -1,10 +1,4 @@ -import { - BtcAmount, - USDAmount, - ValidationError, - isUsdWallet, - validator, -} from "@domain/shared" +import { USDAmount, ValidationError, isUsdWallet, validator } from "@domain/shared" import { isActiveAccount, walletBelongsToAccount } from "@domain/accounts" import { SendOnchainArgs } from "@services/ibex/types" @@ -17,21 +11,21 @@ import { SendOnchainArgs } from "@services/ibex/types" const checkOnchainMin = async (o: { amount: USDAmount }) => { // TODO: Currently relying on Ibex to enforce dust limits // const { dustThreshold } = getOnChainWalletConfig() - // const minBtc = BtcAmount.sats(dustThreshold.toString()) + // const minBtc = BtcAmount.sats(dustThreshold.toString()) // const btcPrice = await PriceService().getUsdCentRealTimePrice(_) // if (btcPrice instanceof PriceServiceError) return new ValidationError(btcPrice) // const minUsd = minBtc.convertAtRate(MoneyAmount.from("50000", WalletCurrency.Usd)) const minUsd = USDAmount.ZERO - return o.amount.isGreaterThan(minUsd) - ? true + return o.amount.isGreaterThan(minUsd) + ? true : new ValidationError(`Amount must be greater than ${minUsd.asDollars()}`) } -type SendOnchainArgsWithContext = SendOnchainArgs & { wallet: Wallet, account: Account } +type SendOnchainArgsWithContext = SendOnchainArgs & { wallet: Wallet; account: Account } export const OnchainUsdPaymentValidator = validator([ isUsdWallet, isActiveAccount, walletBelongsToAccount, checkOnchainMin, -]) \ No newline at end of file +]) diff --git a/src/services/kratos/tests-but-not-prod.ts b/src/services/kratos/tests-but-not-prod.ts deleted file mode 100644 index 034055985..000000000 --- a/src/services/kratos/tests-but-not-prod.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { IdentityState } from "@ory/client" - -import { baseLogger } from "@services/logger" - -import { UnknownKratosError } from "./errors" -import { kratosAdmin } from "./private" - -export const activateUser = async (kratosUserId: UserId): Promise => { - let identity: KratosIdentity - try { - const res = await kratosAdmin.getIdentity({ id: kratosUserId }) - identity = res.data - } catch (err) { - return new UnknownKratosError(err) - } - - try { - await kratosAdmin.updateIdentity({ - id: kratosUserId, - updateIdentityBody: { - ...identity, - state: IdentityState.Active, - }, - }) - } catch (err) { - return new UnknownKratosError(err) - } -} - -// this function from kratos is not implemented -export const deactivateUser = async ( - kratosUserId: UserId, -): Promise => { - let identity: KratosIdentity - try { - const res = await kratosAdmin.getIdentity({ id: kratosUserId }) - identity = res.data - } catch (err) { - return new UnknownKratosError(err) - } - - try { - const res = await kratosAdmin.updateIdentity({ - id: kratosUserId, - updateIdentityBody: { - ...identity, - state: IdentityState.Inactive, - }, - }) - - baseLogger.info(res, "deactivateUser result") - } catch (err) { - return new UnknownKratosError(err) - } -} - -export const revokeSessions = async ( - kratosUserId: UserId, -): Promise => { - try { - await kratosAdmin.deleteIdentitySessions({ id: kratosUserId }) - } catch (err) { - return new UnknownKratosError(err) - } -} - -export const listIdentitySchemas = async (): Promise< - IdentitySchemaContainer[] | KratosError -> => { - try { - const res = await kratosAdmin.listIdentitySchemas() - return res.data - } catch (err) { - return new UnknownKratosError(err) - } -} diff --git a/test/flash/integration/jest.setup.ts b/test/flash/integration/jest.setup.ts index a0cdbc16b..893729fd4 100644 --- a/test/flash/integration/jest.setup.ts +++ b/test/flash/integration/jest.setup.ts @@ -1,6 +1,13 @@ -import { disconnectAll } from "@services/redis" +import Ibex from "@services/ibex/client" import { setupMongoConnection } from "@services/mongodb" -import { createMandatoryUsers, createRandomUserAndWallets, createUser, getUser, createUserAndWallet, TestUser, getUsdWalletDescriptorByPhone, getAccountByPhone } from "test/galoy/helpers" +import { disconnectAll } from "@services/redis" + +import { + createMandatoryUsers, + createUser, + getUsdWalletDescriptorByPhone, + getAccountByPhone, +} from "test/galoy/helpers" let mongoose export let flash // : TestUser @@ -8,23 +15,21 @@ export let alice // : TestUser export let bob //: TestUser // Mock prices -jest.mock( - "@app/prices/get-current-price", - () => require("test/flash/mocks/get-current-price"), +jest.mock("@app/prices/get-current-price", () => + require("test/flash/mocks/get-current-price"), ) -import Ibex from "@services/ibex/client" +import * as IbexMocks from "test/flash/mocks/ibex" jest.mock( "@services/ibex/client", // () => require("test/flash/mocks/ibex"), ) -let mockedIbex: jest.Mock +export let mockedIbex: jest.Mock beforeAll(async () => { - - mockedIbex = Ibex as jest.Mock + mockedIbex = Ibex as unknown as jest.Mock mockedIbex.mockReturnValue({ - createAccount: jest.fn().mockResolvedValue(createAccount.response), + createAccount: jest.fn().mockResolvedValue(IbexMocks.account.response), // addInvoice: jest.fn().mockResolvedValue(addInvoice.response), // payInvoiceV2: jest.fn().mockResolvedValue(payInvoiceV2.response) @@ -32,7 +37,7 @@ beforeAll(async () => { mongoose = await setupMongoConnection(true) const admins = await createMandatoryUsers() - const owner = admins.find(a => a.role === "bankowner") + const owner = admins.find((a) => a.role === "bankowner") if (!owner) throw new Error("Initialization failed: Bank owner not found.") flash = { account: await getAccountByPhone(owner.phone), @@ -40,7 +45,6 @@ beforeAll(async () => { } alice = await createUser() bob = await createUser() - }) // Would be nice to clean-up Ibex accounts, but Ibex API does not have delete diff --git a/test/flash/integration/offers/execute-offer.spec.ts b/test/flash/integration/offers/execute-offer.spec.ts index 6d09a675c..f723a96ef 100644 --- a/test/flash/integration/offers/execute-offer.spec.ts +++ b/test/flash/integration/offers/execute-offer.spec.ts @@ -1,15 +1,11 @@ import OffersManager from "@app/offers/OffersManager" -import { alice } from "../jest.setup" -import OffersRepository from "@app/offers/storage/Redis" -import { RepositoryError } from "@domain/errors" + // import { mockedIbex } from "../jest.setup" -import * as Mocks from "test/flash/mocks/ibex" -import Ibex from "@services/ibex/client" +import { USDAmount } from "@domain/shared" -const send = { - amount: 100n, - currency: "USD" -} as Amount<"USD"> +import { alice } from "../jest.setup" + +const send = USDAmount.cents("100") as USDAmount // jest.mock( // "@services/ibex/client", @@ -18,11 +14,9 @@ const send = { // let mockedIbex: jest.Mock beforeAll(async () => { // Mocking the http call would be more useful, but adds complexity to tests - // mockedIbex = Ibex as jest.Mock // move to beforeAll - + // mockedIbex = Ibex as unknown as jest.Mock // move to beforeAll // await Ibex().getAccountDetails({ accountId: walletId }) // mockedIbex.mockReset() - // jest.spyOn(mockedIbex, 'getAccountDetails').mockImplementation(() => { // }); }) @@ -37,19 +31,18 @@ beforeEach(async () => { }) afterEach(async () => { - jest.clearAllMocks() + jest.clearAllMocks() }) describe("Offers", () => { it("successfully makes and executes an offer", async () => { - const manager = new OffersManager() - const offer = await manager.makeCashoutOffer(alice.usdWalletD.id, send) + const offer = await OffersManager.createCashoutOffer(alice.usdWalletD.id, send) if (offer instanceof Error) throw offer - + const { id } = offer - const status = await manager.executeOffer(id) - + const status = await OffersManager.executeCashout(id, alice.usdWalletD.id) + // make assertions against ledger - console.log(`status = ${status}`) + expect(status).toBeDefined() }) -}) \ No newline at end of file +}) diff --git a/test/flash/integration/offers/make-cashout-offer.spec.ts b/test/flash/integration/offers/make-cashout-offer.spec.ts index d9b5dd3e4..ca59ffe19 100644 --- a/test/flash/integration/offers/make-cashout-offer.spec.ts +++ b/test/flash/integration/offers/make-cashout-offer.spec.ts @@ -1,15 +1,14 @@ import OffersManager from "@app/offers/OffersManager" -import { alice } from "../jest.setup" -import OffersRepository from "@app/offers/storage/Redis" -import { RepositoryError } from "@domain/errors" + // import { mockedIbex } from "../jest.setup" -import * as Mocks from "test/flash/mocks/ibex" import Ibex from "@services/ibex/client" +import { USDAmount } from "@domain/shared" -const send = { - amount: 101n, - currency: "USD" -} as Amount<"USD"> +import { alice } from "../jest.setup" + +import * as Mocks from "test/flash/mocks/ibex" + +const send = USDAmount.cents("101") as USDAmount jest.mock( "@services/ibex/client", @@ -18,7 +17,7 @@ jest.mock( let mockedIbex: jest.Mock beforeAll(async () => { // Mocking the http call would be more useful, but adds complexity to tests - mockedIbex = Ibex as jest.Mock // move to beforeAll + mockedIbex = Ibex as unknown as jest.Mock // move to beforeAll // await Ibex().getAccountDetails({ accountId: walletId }) // mockedIbex.mockReset() @@ -29,7 +28,7 @@ beforeAll(async () => { beforeEach(async () => { const getAccountDetailsMock = jest.fn().mockResolvedValue( - Mocks.account.response // override the balance + Mocks.account.response, // override the balance ) mockedIbex.mockReturnValue({ getAccountDetails: getAccountDetailsMock, @@ -37,20 +36,20 @@ beforeEach(async () => { }) afterEach(async () => { - jest.clearAllMocks() + jest.clearAllMocks() }) describe("Offers", () => { it("successfully makes and persists an offer using default config", async () => { - const offer = await (new OffersManager().makeCashoutOffer(alice.usdWalletD.id, send)) - + const offer = await OffersManager.createCashoutOffer(alice.usdWalletD.id, send) + if (offer instanceof Error) throw offer - expect(offer.ibexTransfer.amount).toEqual(send.amount) - expect(offer.flashFee.amount).toEqual(2n) - expect(offer.flashFee.currency).toEqual("USD") - expect(offer.usdLiability.amount).toEqual(99n) - expect(offer.usdLiability.currency).toEqual("USD") - expect(offer.jmdLiability.amount).toEqual(157n) - expect(offer.jmdLiability.currency).toEqual("JMD") + expect(offer.details.ibexTrx.usd.asCents()).toEqual(send.asCents()) + expect(offer.details.flash.fee.asCents()).toEqual("2") + expect(offer.details.flash.fee.currencyCode).toEqual("USD") + expect(offer.details.flash.liability.usd.asCents()).toEqual("99") + expect(offer.details.flash.liability.usd.currencyCode).toEqual("USD") + expect(offer.details.flash.liability.jmd.asCents()).toEqual("157") + expect(offer.details.flash.liability.jmd.currencyCode).toEqual("JMD") }) -}) \ No newline at end of file +}) diff --git a/test/flash/unit/domain/ledger/activity-checker.spec.ts b/test/flash/unit/domain/ledger/activity-checker.spec.ts index dd1ba6870..5375a33fa 100644 --- a/test/flash/unit/domain/ledger/activity-checker.spec.ts +++ b/test/flash/unit/domain/ledger/activity-checker.spec.ts @@ -18,6 +18,7 @@ beforeAll(() => { accountId: "a1" as AccountId, onChainAddressIdentifiers: [], onChainAddresses: () => [], + lnurlp: "" as Lnurl, } usdWallet = { @@ -27,6 +28,7 @@ beforeAll(() => { accountId: "a1" as AccountId, onChainAddressIdentifiers: [], onChainAddresses: () => [], + lnurlp: "" as Lnurl, } }) diff --git a/test/flash/unit/domain/ledger/imbalance-calculator.spec.ts b/test/flash/unit/domain/ledger/imbalance-calculator.spec.ts index 1813a69e3..08b5e358b 100644 --- a/test/flash/unit/domain/ledger/imbalance-calculator.spec.ts +++ b/test/flash/unit/domain/ledger/imbalance-calculator.spec.ts @@ -11,6 +11,7 @@ const btcWallet: Wallet = { accountId: "a1" as AccountId, onChainAddressIdentifiers: [], onChainAddresses: () => [], + lnurlp: "" as Lnurl, } const VolumeAfterLightningReceiptFn = () => diff --git a/test/galoy/bats/admin-gql/account-details-by-account-id.gql b/test/galoy/bats/admin-gql/account-details-by-account-id.gql deleted file mode 100644 index a05d9a349..000000000 --- a/test/galoy/bats/admin-gql/account-details-by-account-id.gql +++ /dev/null @@ -1,6 +0,0 @@ -query accountDetailsByAccountId($accountId: ID!) { - accountDetailsByAccountId(accountId: $accountId) { - id - uuid - } -} diff --git a/test/galoy/bats/admin-gql/account-details-by-user-phone.gql b/test/galoy/bats/admin-gql/account-details-by-user-phone.gql deleted file mode 100644 index e6a632e1a..000000000 --- a/test/galoy/bats/admin-gql/account-details-by-user-phone.gql +++ /dev/null @@ -1,6 +0,0 @@ -query accountDetailsByUserPhone($phone: Phone!) { - accountDetailsByUserPhone(phone: $phone) { - id - uuid - } -} diff --git a/test/galoy/bats/admin-gql/account-details-by-username.gql b/test/galoy/bats/admin-gql/account-details-by-username.gql deleted file mode 100644 index 313a82a8d..000000000 --- a/test/galoy/bats/admin-gql/account-details-by-username.gql +++ /dev/null @@ -1,5 +0,0 @@ -query accountDetailsByUsername($username: Username!) { - accountDetailsByUsername(username: $username) { - id - } -} diff --git a/test/galoy/bats/admin-gql/account-status-level-update.gql b/test/galoy/bats/admin-gql/account-status-level-update.gql deleted file mode 100644 index c0838f2a5..000000000 --- a/test/galoy/bats/admin-gql/account-status-level-update.gql +++ /dev/null @@ -1,32 +0,0 @@ -mutation accountUpdateLevel($input: AccountUpdateLevelInput!) { - accountUpdateLevel(input: $input) { - errors { - message - } - accountDetails { - id - username - level - status - title - owner { - id - language - phone - createdAt - } - coordinates { - latitude - longitude - } - wallets { - id - walletCurrency - accountId - balance - pendingIncomingBalance - } - createdAt - } - } -} diff --git a/test/galoy/bats/admin-gql/account-update-level.gql b/test/galoy/bats/admin-gql/account-update-level.gql deleted file mode 100644 index 592ba6162..000000000 --- a/test/galoy/bats/admin-gql/account-update-level.gql +++ /dev/null @@ -1,33 +0,0 @@ -mutation accountUpdateLevel($input: AccountUpdateLevelInput!) { - accountUpdateLevel(input: $input) { - errors { - message - } - accountDetails { - id - username - level - status - title - owner { - id - language - phone - createdAt - } - coordinates { - latitude - longitude - } - wallets { - id - walletCurrency - accountId - balance - pendingIncomingBalance - } - createdAt - erpParty - } - } -} diff --git a/test/galoy/bats/admin-gql/account-update-status.gql b/test/galoy/bats/admin-gql/account-update-status.gql deleted file mode 100644 index f5feaf336..000000000 --- a/test/galoy/bats/admin-gql/account-update-status.gql +++ /dev/null @@ -1,32 +0,0 @@ -mutation accountUpdateStatus($input: AccountUpdateStatusInput!) { - accountUpdateStatus(input: $input) { - errors { - message - } - accountDetails { - id - username - level - status - title - owner { - id - language - phone - createdAt - } - coordinates { - latitude - longitude - } - wallets { - id - walletCurrency - accountId - balance - pendingIncomingBalance - } - createdAt - } - } -} diff --git a/test/galoy/bats/admin-gql/business-update-map-info.gql b/test/galoy/bats/admin-gql/business-update-map-info.gql deleted file mode 100644 index bd287aee6..000000000 --- a/test/galoy/bats/admin-gql/business-update-map-info.gql +++ /dev/null @@ -1,32 +0,0 @@ -mutation businessUpdateMapInfo($input: BusinessUpdateMapInfoInput!) { - businessUpdateMapInfo(input: $input) { - errors { - message - } - accountDetails { - id - username - level - status - title - owner { - id - language - phone - createdAt - } - coordinates { - latitude - longitude - } - wallets { - id - walletCurrency - accountId - balance - pendingIncomingBalance - } - createdAt - } - } -} diff --git a/test/galoy/bats/admin-gql/user-update-phone.gql b/test/galoy/bats/admin-gql/user-update-phone.gql deleted file mode 100644 index c3b5e0a29..000000000 --- a/test/galoy/bats/admin-gql/user-update-phone.gql +++ /dev/null @@ -1,13 +0,0 @@ -mutation UserUpdatePhone($input: UserUpdatePhoneInput!) { - userUpdatePhone(input: $input) { - errors { - __typename - message - code - path - } - accountDetails { - id - } - } -} diff --git a/test/galoy/bats/admin.bats b/test/galoy/bats/admin.bats deleted file mode 100644 index 8b6792ce4..000000000 --- a/test/galoy/bats/admin.bats +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -username="user1" - -setup_file() { - start_server - - login_user \ - "$ADMIN_TOKEN_NAME" \ - "$ADMIN_PHONE" \ - "$CODE" -} - -teardown_file() { - stop_server -} - -ADMIN_TOKEN_NAME="editor" -ADMIN_PHONE="+16505554336" - -TESTER_TOKEN_NAME="tester" -TESTER_PHONE="+19876543210" - -@test "admin: perform admin queries/mutations" { - admin_token="$ADMIN_TOKEN_NAME" - - login_user \ - "$TESTER_TOKEN_NAME" \ - "$TESTER_PHONE" \ - "$CODE" - - variables=$( - jq -n \ - --arg username "$username" \ - '{input: {username: $username}}' - ) - exec_graphql "$TESTER_TOKEN_NAME" 'user-update-username' "$variables" - - variables=$( - jq -n \ - --arg phone "$TESTER_PHONE" \ - '{phone: $phone}' - ) - exec_admin_graphql "$admin_token" 'account-details-by-user-phone' "$variables" - id="$(graphql_output '.data.accountDetailsByUserPhone.id')" - [[ "$id" != "null" ]] || exit 1 - uuid="$(graphql_output '.data.accountDetailsByUserPhone.uuid')" - [[ "$uuid" != "null" ]] || exit 1 - - new_phone="$(random_phone)" - variables=$( - jq -n \ - --arg phone "$new_phone" \ - --arg accountUuid "$uuid" \ - '{input: {phone: $phone, accountUuid: $accountUuid}}' - ) - - exec_admin_graphql $admin_token 'user-update-phone' "$variables" - num_errors="$(graphql_output '.data.userUpdatePhone.errors | length')" - [[ "$num_errors" == "0" ]] || exit 1 - - variables=$( - jq -n \ - --arg phone "$new_phone" \ - '{phone: $phone}' - ) - - exec_admin_graphql "$admin_token" 'account-details-by-user-phone' "$variables" - refetched_id="$(graphql_output '.data.accountDetailsByUserPhone.id')" - [[ "$refetched_id" == "$id" ]] || exit 1 - - variables=$( - jq -n \ - --arg username "$username" \ - '{username: $username}' - ) - - exec_admin_graphql "$admin_token" 'account-details-by-username' "$variables" - refetched_id="$(graphql_output '.data.accountDetailsByUsername.id')" - [[ "$refetched_id" == "$id" ]] || exit 1 - - variables=$( - jq -n \ - --arg level "TWO" \ - --arg uid "$id" \ - '{input: {level: $level, uid: $uid}}' - ) - - exec_admin_graphql "$admin_token" 'account-status-level-update' "$variables" - refetched_id="$(graphql_output '.data.accountUpdateLevel.accountDetails.id')" - [[ "$refetched_id" == "$id" ]] || exit 1 - level="$(graphql_output '.data.accountUpdateLevel.accountDetails.level')" - [[ "$level" == "TWO" ]] || exit 1 - - variables=$( - jq -n \ - --arg account_status "LOCKED" \ - --arg uid "$id" \ - --arg comment "Test lock of the account" \ - '{input: {status: $account_status, uid: $uid, comment: $comment}}' - ) - - exec_admin_graphql "$admin_token" 'account-update-status' "$variables" - refetched_id="$(graphql_output '.data.accountUpdateStatus.accountDetails.id')" - [[ "$refetched_id" == "$id" ]] || exit 1 - account_status="$(graphql_output '.data.accountUpdateStatus.accountDetails.status')" - [[ "$account_status" == "LOCKED" ]] || exit 1 - - variables=$( - jq -n \ - --arg accountId "$uuid" \ - '{accountId: $accountId}' - ) - - exec_admin_graphql "$admin_token" 'account-details-by-account-id' "$variables" - returnedId="$(graphql_output '.data.accountDetailsByAccountId.uuid')" - [[ "$returnedId" == "$uuid" ]] || exit 1 - - # TODO: add check by email - - # TODO: business update map info -} diff --git a/test/galoy/bats/auth.bats b/test/galoy/bats/auth.bats deleted file mode 100644 index 3959a7d45..000000000 --- a/test/galoy/bats/auth.bats +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - start_server -} - -teardown_file() { - stop_server -} - -randomPhone() { - local phone_number="" - phone_number=$((1 + RANDOM % 9)) - - for _ in {1..9}; do - phone_number+=${RANDOM:0:1} - done - echo "+1${phone_number}" -} - - -randomEmail() { - local random_string - # Generate a random hex string of length 40 (equivalent to 20 bytes) - random_string=$(openssl rand -hex 20) - echo "${random_string}@galoy.io" -} - -TOKEN_NAME="charlie" -PHONE=$(randomPhone) - -getEmailCode() { - local email="$1" - local query="SELECT body FROM courier_messages WHERE recipient='${email}' ORDER BY created_at DESC LIMIT 1;" - - local result=$(psql $KRATOS_PG_CON -t -c "${query}") - - # If no result is found, exit with an error - if [[ -z "$result" ]]; then - echo "No message for email ${email}" >&2 - exit 1 - fi - - # Extract the code from the body - local code=$(echo "$result" | grep -Eo '[0-9]{6}' | head -n1) - - echo "$code" -} - -getEmailCount() { - local email="$1" - local table="courier_messages" - local query="SELECT COUNT(*) FROM $table WHERE recipient = '$email';" - - # Make the query - local count=$(psql $KRATOS_PG_CON -t -c "${query}") - - echo $count -} - -generateTotpCode() { - local secret=$1 - node test/bats/helpers/generate-totp.js "$secret" -} - -@test "auth: create user" { - login_user \ - "$TOKEN_NAME" \ - "$PHONE" \ - "$CODE" - - exec_graphql 'charlie' 'identity' - [[ "$(graphql_output '.data.me.phone')" = "$PHONE" ]] || exit 1 -} - -@test "auth: logout user" { - exec_graphql 'charlie' 'identity' - id="$(graphql_output '.data.me.id')" - - sessions_before_logout=$(curl -s $KRATOS_ADMIN_API/admin/identities/$id/sessions | jq '[.[] | select(.active == true)] | length') - [[ "$sessions_before_logout" -eq 1 ]] || exit 1 - - exec_graphql 'charlie' 'logout' - [[ "$(graphql_output '.data.userLogout.success')" = "true" ]] || exit 1 - - sessions_after_logout=$(curl -s $KRATOS_ADMIN_API/admin/identities/$id/sessions | jq '[.[] | select(.active == true)] | length') - [[ "$sessions_after_logout" -eq 0 ]] || exit 1 -} - -@test "auth: add email" { - login_user \ - "$TOKEN_NAME" \ - "$PHONE" \ - "$CODE" - - email=$(randomEmail) - cache_value "email" "$email" - - variables="{\"input\": {\"email\": \"$email\"}}" - - exec_graphql 'charlie' 'user-email-registration-initiate' "$variables" - emailRegistrationId="$(graphql_output '.data.userEmailRegistrationInitiate.emailRegistrationId')" - - code=$(getEmailCode "$email") - echo "The code is: $code" - - exec_graphql 'charlie' 'identity' - [[ "$(graphql_output '.data.me.email.address')" = "$email" ]] || exit 1 - [[ "$(graphql_output '.data.me.email.verified')" = "false" ]] || exit 1 - - exec_graphql 'charlie' 'user-email-registration-initiate' "$variables" - error_message="$(graphql_output '.data.userEmailRegistrationInitiate.errors[0].message')" - expected_message="An email is already attached to this account. It's only possible to attach one email per account" - [[ "$error_message" == "$expected_message" ]] || exit 1 - - variables="{\"input\": {\"code\": \"$code\", \"emailRegistrationId\": \"$emailRegistrationId\"}}" - exec_graphql 'charlie' 'user-email-registration-validate' "$variables" - [[ "$(graphql_output '.data.userEmailRegistrationValidate.me.email.address')" == "$email" ]] || exit 1 - [[ "$(graphql_output '.data.userEmailRegistrationValidate.me.email.verified')" == "true" ]] || exit 1 -} - -@test "auth: log in with email" { - email=$(read_value "email") - - # code request - curl_request "http://${GALOY_ENDPOINT}/auth/email/code" "{ \"email\": \"$email\" }" - emailLoginId=$(curl_output '.result') - [ -n "$emailLoginId" ] || exit 1 - - code=$(getEmailCode "$email") - [ -n "$code" ] || exit 1 - - # validate code - curl_request "http://${GALOY_ENDPOINT}/auth/email/login" "{ \"code\": \"$code\", \"emailLoginId\": \"$emailLoginId\" }" - authToken=$(curl_output '.result.authToken') - [ -n "$authToken" ] || exit 1 - - authTokenLength=$(echo -n "$authToken" | wc -c) - [ "$authTokenLength" -eq 39 ] || exit 1 - - # TODO: check the response when the login request has expired -} - -@test "auth: remove email" { - email=$(read_value "email") - countInit=$(getEmailCount "$email") - echo "count for $email is: $countInit" - [ "$countInit" -eq 2 ] || exit 1 - - exec_graphql 'charlie' 'user-email-delete' - [[ "$(graphql_output '.data.userEmailDelete.me.email.address')" == "null" ]] || exit 1 - [[ "$(graphql_output '.data.userEmailDelete.me.email.verified')" == "false" ]] || exit 1 - - curl_request "http://${GALOY_ENDPOINT}/auth/email/code" "{ \"email\": \"${email}\" }" - flowId=$(curl_output '.result') - [[ "$flowId" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] || exit 1 - - exec_graphql 'charlie' 'identity' - [[ "$(graphql_output '.data.me.email.address')" == "null" ]] || exit 1 - [[ "$(graphql_output '.data.me.totpEnabled')" == "false" ]] || exit 1 - - count=$(getEmailCount "$email") - [[ "$count" -eq "$countInit" ]] || exit 1 - - # TODO: email to the sender highlighting the email was removed -} - -@test "auth: remove phone login" { - email=$(read_value "email") - - variables="{\"input\": {\"email\": \"$email\"}}" - exec_graphql 'charlie' 'user-email-registration-initiate' "$variables" - emailRegistrationId="$(graphql_output '.data.userEmailRegistrationInitiate.emailRegistrationId')" - - code=$(getEmailCode "$email") - echo "The code is: $code" - - variables="{\"input\": {\"code\": \"$code\", \"emailRegistrationId\": \"$emailRegistrationId\"}}" - exec_graphql 'charlie' 'user-email-registration-validate' "$variables" - [[ "$(graphql_output '.data.userEmailRegistrationValidate.me.email.address')" == "$email" ]] || exit 1 - [[ "$(graphql_output '.data.userEmailRegistrationValidate.me.email.verified')" == "true" ]] || exit 1 - - # Remove phone. - exec_graphql "charlie" "user-phone-delete" - [[ "$(graphql_output '.data.userPhoneDelete.me.phone')" == "null" ]] || exit 1 -} - -@test "auth: adding totp" { - authToken=$(read_value "$TOKEN_NAME") - - # Initiate TOTP Registration - variables="{\"input\": {\"authToken\": \"$authToken\"}}" - exec_graphql 'charlie' 'user-totp-registration-initiate' "$variables" - - totpRegistrationId="$(graphql_output '.data.userTotpRegistrationInitiate.totpRegistrationId')" - [[ "$totpRegistrationId" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] || exit 1 - - totpSecret="$(graphql_output '.data.userTotpRegistrationInitiate.totpSecret')" - [ -n "$totpSecret" ] || exit 1 - - # Validate TOTP Registration - totpCode=$(generateTotpCode "$totpSecret") - variables="{\"input\": {\"totpCode\": \"$totpCode\", \"totpRegistrationId\": \"$totpRegistrationId\", \"authToken\": \"$authToken\"}}" - exec_graphql 'charlie' 'user-totp-registration-validate' "$variables" - - # Checking the response structure - totpEnabled="$(graphql_output '.data.userTotpRegistrationValidate.me.totpEnabled')" - [ "$totpEnabled" == "true" ] || exit 1 -} - -@test "auth: log in with email with totp activated" { - email=$(read_value "email") - - # code request - variables="{\"email\": \"$email\"}" - curl_request "http://${GALOY_ENDPOINT}/auth/email/code" "$variables" - emailLoginId="$(curl_output '.result')" - [ "$emailLoginId" != "null" ] - - code=$(getEmailCode "$email") - [ "$code" != "" ] - - # validating email with code - variables="{\"code\": \"$code\", \"emailLoginId\": \"$emailLoginId\"}" - curl_request "http://${GALOY_ENDPOINT}/auth/email/login" "$variables" - authToken="$(curl_output '.result.authToken')" - totpRequired="$(curl_output '.result.totpRequired')" - [ "$authToken" != "" ] - [ "$totpRequired" == "true" ] - - totpCode=$(generateTotpCode "$totpSecret") - variables="{\"totpCode\": \"$totpCode\", \"authToken\": \"$authToken\"}" - curl_request "http://${GALOY_ENDPOINT}/auth/totp/validate" "$variables" - - exec_graphql 'charlie' 'identity' - [[ "$(graphql_output '.data.me.totpEnabled')" = "true" ]] || exit 1 -} - -@test "auth: removing totp" { - authToken=$(read_value "$TOKEN_NAME") - variables="{\"input\": {\"authToken\": \"$authToken\"}}" - - exec_graphql 'charlie' 'user-totp-delete' "$variables" - [[ "$(graphql_output '.data.userTotpDelete.me.totpEnabled')" = "false" ]] || exit 1 -} - -@test "auth: add new phone mutation" { - # First mutation: UserPhoneRegistrationInitiate - variables="{\"input\": {\"phone\": \"$PHONE\"}}" - exec_graphql "charlie" "user-phone-registration-initiate" "$variables" - [[ "$(graphql_output '.data.userPhoneRegistrationInitiate.success')" = "true" ]] || exit 1 - - # Second mutation: UserPhoneRegistrationValidate - variables="{\"input\": {\"phone\": \"$PHONE\", \"code\": \"$CODE\"}}" - exec_graphql "charlie" "user-phone-registration-validate" "$variables" - [[ "$(graphql_output '.data.userPhoneRegistrationValidate.me.phone')" = "$PHONE" ]] || exit 1 -} diff --git a/test/galoy/bats/bitcoind_signer_descriptors.json b/test/galoy/bats/bitcoind_signer_descriptors.json deleted file mode 100644 index fc0a17989..000000000 --- a/test/galoy/bats/bitcoind_signer_descriptors.json +++ /dev/null @@ -1 +0,0 @@ -[{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/0/*)#88k4937c","timestamp":0},{"active":true,"desc":"wpkh([6f2fa1b2/84'/0'/0']tprv8gXB88g1VCScmqPp8WcetpJPRxix24fRJJ6FniYCcCUEFMREDrCfwd34zWXPiY5MW2xp8e1Z6EeBrh74zMSgfQQmTorWtE1zyBtv7yxdcoa/1/*)#knn5cywq","internal":true,"timestamp":0}] diff --git a/test/galoy/bats/callback.bats b/test/galoy/bats/callback.bats deleted file mode 100644 index b054f493e..000000000 --- a/test/galoy/bats/callback.bats +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/ln" - -setup_file() { - clear_cache - - bitcoind_init - start_trigger - start_ws_server - start_server - start_exporter - start_callback - - lnds_init -} - -teardown_file() { - stop_trigger - stop_server - stop_ws_server - stop_exporter - stop_subscriber - stop_callback -} - -setup() { - reset_redis -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi - -} - -@test "callback: setup callback endpoints" { - token_name="$ALICE_TOKEN_NAME" - - login_user "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - - exec_graphql "$token_name" 'callback-endpoints-list' - result0="$(graphql_output '.data.me.defaultAccount.callbackEndpoints')" - [[ "$result0" == "[]" ]] || exit 1 - - variables=$( - jq -n \ - --arg url "$SVIX_CALLBACK_URL" \ - '{input: {url: $url}}' - ) - - exec_graphql "$token_name" 'callback-endpoint-add' "$variables" - result1="$(graphql_output '.data.callbackEndpointAdd.id')" - [[ "$result1" != "null" ]] || exit 1 - - exec_graphql "$token_name" 'callback-endpoints-list' - result2="$(graphql_output '.data.me.defaultAccount.callbackEndpoints[0].id')" - [[ "$result2" != "null" ]] || exit 1 - [[ "$result2" == "$result1" ]] || exit 1 - - variables=$( - jq -n \ - --arg id "$result2" \ - '{input: {id: $id}}' - ) - - exec_graphql "$token_name" 'callback-endpoint-delete' "$variables" - result3="$(graphql_output '.data.callbackEndpointDelete.success')" - [[ "$result3" == "true" ]] || exit 1 - - exec_graphql "$token_name" 'callback-endpoints-list' - result4="$(graphql_output '.data.me.defaultAccount.callbackEndpoints')" - [[ "$result4" == "[]" ]] || exit 1 -} diff --git a/test/galoy/bats/cron.bats b/test/galoy/bats/cron.bats deleted file mode 100644 index 49d3a1c9c..000000000 --- a/test/galoy/bats/cron.bats +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/ln" - -setup_file() { - clear_cache - - bitcoind_init - start_trigger - start_server - start_exporter - - lnds_init - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server - stop_exporter -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -no_pending_lnd1_channels() { - pending_channel="$(lnd_cli pendingchannels | jq -r '.pending_open_channels[0]')" - if [[ "$pending_channel" != "null" ]]; then - bitcoin_cli -generate 6 - exit 1 - fi -} - -wait_for_bria_hot_balance_at_least() { - amount=$1 - - bria_settled_hot_balance=$( - bria_cli wallet-balance -w "dev-wallet" \ - | jq -r '.effectiveSettled' - ) - - [[ "$amount" -lt "$bria_settled_hot_balance" ]] || return 1 -} - -@test "cron: rebalance hot to cold storage" { - login_user "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Create address - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - address="$(graphql_output '.data.onChainAddressCreate.address')" - - bitcoin_cli sendtoaddress "$address" "10" - bitcoin_cli -generate 2 - - retry 15 1 wait_for_bria_hot_balance_at_least 1000000000 - - local key1="tpubDEaDfeS1EXpqLVASNCW7qAHW1TFPBpk2Z39gUXjFnsfctomZ7N8iDpy6RuGwqdXAAZ5sr5kQZrxyuEn15tqPJjM4mcPSuXzV27AWRD3p9Q4" - local key2="tpubDEPCxBfMFRNdfJaUeoTmepLJ6ZQmeTiU1Sko2sdx1R3tmPpZemRUjdAHqtmLfaVrBg1NBx2Yx3cVrsZ2FTyBuhiH9mPSL5ozkaTh1iZUTZx" - - bria_cli import-xpub -x "${key1}" -n cold-key1 -d m/48h/1h/0h/2h || true - bria_cli import-xpub -x "${key2}" -n cold-key2 -d m/48h/1h/0h/2h || true - bria_cli create-wallet -n cold sorted-multisig -x cold-key1 cold-key2 -t 1 || true - - cold_balance=$(bria_cli wallet-balance -w cold | jq -r '.effectivePendingIncome') - - [[ "${cold_balance}" = "0" ]] || exit 1 - - run_cron - - bria_cli watch-events -o - bitcoin_cli -generate 1 - for i in {1..30}; do - cold_balance=$(bria_cli wallet-balance -w cold | jq -r '.effectivePendingIncome') - [[ "${cold_balance}" != "0" ]] && break; - sleep 1 - done - cold_balance=$(bria_cli wallet-balance -w cold | jq -r '.effectivePendingIncome') - [[ "${cold_balance}" != "0" ]] || exit 1; - - bitcoin_cli -generate 1 - - for i in {1..20}; do - cold_balance=$(bria_cli wallet-balance -w cold | jq -r '.effectivePendingIncome') - [[ "${cold_balance}" = "0" ]] && break; - sleep 1 - done - [[ "${cold_balance}" = "0" ]] || exit 1; -} - -@test "cron: rebalance internal channels" { - # NOTE: Not an idempotent test because we haven't implemented accounting for - # closing channels initiated from internal lnds as yet. - - # Get onchain funds into lnd1 - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg amount "600000" \ - '{input: {walletId: $wallet_id, amount: $amount}}' - ) - exec_graphql "$token_name" 'ln-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" - - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - close_partner_initiated_channels_with_external || true - retry 10 1 mempool_not_empty - bitcoin_cli -generate 3 - - # Setup lnd1 -> lnd2 channel - local local_amount="500000" - lnd2_local_pubkey="$(lnd2_cli getinfo | jq -r '.identity_pubkey')" - lnd_cli connect "${lnd2_local_pubkey}@${COMPOSE_PROJECT_NAME}-lnd2-1:9735" || true - opened=$( - lnd_cli openchannel \ - --node_key "$lnd2_local_pubkey" \ - --local_amt "$local_amount" - ) - funding_txid="$(echo $opened | jq -r '.funding_txid')" - - retry 10 1 mempool_not_empty - retry 10 1 no_pending_lnd1_channels - - # Rebalance and check balances - channel_balances=$(lnd_cli channelbalance) - original_local_balance="$(echo $channel_balances | jq -r '.local_balance.sat')" - original_remote_balance="$(echo $channel_balances | jq -r '.remote_balance.sat')" - - run_cron - - channel_balances=$(lnd_cli channelbalance) - rebalanced_local_balance="$(echo $channel_balances | jq -r '.local_balance.sat')" - rebalanced_remote_balance="$(echo $channel_balances | jq -r '.remote_balance.sat')" - - [[ "$rebalanced_local_balance" -lt "$original_local_balance" ]] || exit 1 - [[ "$rebalanced_remote_balance" -gt "$original_remote_balance" ]] || exit 1 -} diff --git a/test/galoy/bats/device-account.bats b/test/galoy/bats/device-account.bats deleted file mode 100644 index c0eb752b2..000000000 --- a/test/galoy/bats/device-account.bats +++ /dev/null @@ -1,90 +0,0 @@ -load "helpers/setup-and-teardown" - -setup_file() { - start_server -} - -teardown_file() { - stop_server -} - -DEVICE_NAME="device-user" -DEVICE_PHONE="+16505554353" - -url="http://${GALOY_ENDPOINT}/auth/create/device-account" - -# dev/ory/gen-test-jwt.ts -jwt="eyJhbGciOiJSUzI1NiIsImtpZCI6IjFiOTdiMjIxLWNhMDgtNGViMi05ZDA5LWE1NzcwZmNjZWIzNyJ9.eyJzdWIiOiIxOjcyMjc5Mjk3MzY2OmFuZHJvaWQ6VEVTVEUyRUFDQ09VTlQ1YWE3NWFmNyIsImF1ZCI6WyJwcm9qZWN0cy83MjI3OTI5NzM2NiIsInByb2plY3RzL2dhbG95YXBwIl0sInByb3ZpZGVyIjoiZGVidWciLCJpc3MiOiJodHRwczovL2ZpcmViYXNlYXBwY2hlY2suZ29vZ2xlYXBpcy5jb20vNzIyNzkyOTczNjYifQ.onGs8nlWA1e1vkEwJhjDtNwCk1jLNezQign7HyCNBOuAxtr7kt0Id6eZtbROuDlVlS4KwO7xMrn3xxsQHZYftu_ihO61OKBw8IEIlLn548May3HGSMletWTANxMLnhwJIjph8ACpRTockFida3XIr2cgIHwPqNRigFh0Ib9HTG5cuzRpQUEkpgiXZ2dJ0hJppX5OX6Q2ywN5LD4mqqqbXV3VNqtGd9oCUI-t7Kfry4UpNBhkhkPzMc5pt_NRsIHFqGtyH1SRX7NJd8BZuPnVfS6zmoPHaOxOixEO4zhFgh_DRePg6_yT4ejRF29mx1gBhfKSz81R5_BVtjgD-LMUdg" - -@test "device-account: create" { - token_name="$DEVICE_NAME" - - username="$(random_uuid)" - password="$(random_uuid)" - - if [[ "$(uname)" == "Linux" ]]; then - basic_token="$(echo -n $username:$password | base64 -w 0)" - else - basic_token="$(echo -n $username:$password | base64)" - fi - - auth_header="Authorization: Basic $basic_token" - - appcheck_header="Appcheck: $jwt" - - # Create account - curl_request "$url" "" "$auth_header" "$appcheck_header" - auth_token="$(echo $output | jq -r '.result')" - [[ "$auth_token" != "null" ]] || exit 1 - cache_value "$token_name" "$auth_token" - - # Account is valid - exec_graphql "$token_name" 'account-details' - account_id="$(graphql_output '.data.me.defaultAccount.id')" - [[ "$account_id" != "null" ]] || exit 1 - account_level="$(graphql_output '.data.me.defaultAccount.level')" - [[ "$account_level" == "ZERO" ]] || exit 1 - - # Api is re-entrant - curl_request "$url" "" "$auth_header" "$appcheck_header" - refetched_token="$(echo $output | jq -r '.result')" - [[ "$refetched_token" != "$auth_token" ]] || exit 1 - cache_value "$token_name" "$refetched_token" - - exec_graphql "$token_name" 'account-details' - refetched_account_id="$(graphql_output '.data.me.defaultAccount.id')" - [[ "$refetched_account_id" == "$account_id" ]] || exit 1 -} - -@test "device-account: upgrade" { - token_name="$DEVICE_NAME" - phone="$DEVICE_PHONE" - code="$CODE" - - variables=$( - jq -n \ - --arg phone "$phone" \ - --arg code "$code" \ - '{input: {phone: $phone, code: $code}}' - ) - - exec_graphql "$token_name" 'user-login-upgrade' "$variables" - upgrade_success="$(graphql_output '.data.userLoginUpgrade.success')" - [[ "$upgrade_success" == "true" ]] || exit 1 - - # Existing phone accounts return an authToken - upgrade_auth_token="$(graphql_output '.data.userLoginUpgrade.authToken')" - [[ "$upgrade_auth_token" == "null" ]] || exit 1 - - exec_graphql "$token_name" 'account-details' - account_level="$(graphql_output '.data.me.defaultAccount.level')" - [[ "$account_level" == "ONE" ]] || exit 1 -} - -@test "device-account: delete upgraded account" { - token_name="$DEVICE_NAME" - - exec_graphql "$token_name" 'account-delete' - delete_success="$(graphql_output '.data.accountDelete.success')" - [[ "$delete_success" == "true" ]] || exit 1 -} diff --git a/test/galoy/bats/gql/account-default-wallet.gql b/test/galoy/bats/gql/account-default-wallet.gql deleted file mode 100644 index 7eed0d852..000000000 --- a/test/galoy/bats/gql/account-default-wallet.gql +++ /dev/null @@ -1,6 +0,0 @@ -query accountDefaultWallet($username: Username!, $walletCurrency: WalletCurrency) { - accountDefaultWallet(username: $username, walletCurrency: $walletCurrency) { - id - walletCurrency - } -} diff --git a/test/galoy/bats/gql/account-delete.gql b/test/galoy/bats/gql/account-delete.gql deleted file mode 100644 index b0b8bac64..000000000 --- a/test/galoy/bats/gql/account-delete.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation accountDelete { - accountDelete { - errors { - message - code - } - success - } -} diff --git a/test/galoy/bats/gql/account-details.gql b/test/galoy/bats/gql/account-details.gql deleted file mode 100644 index adc71fd24..000000000 --- a/test/galoy/bats/gql/account-details.gql +++ /dev/null @@ -1,8 +0,0 @@ -query me { - me { - defaultAccount { - id - level - } - } -} diff --git a/test/galoy/bats/gql/account-disable-notification-category.gql b/test/galoy/bats/gql/account-disable-notification-category.gql deleted file mode 100644 index 771a6c7e9..000000000 --- a/test/galoy/bats/gql/account-disable-notification-category.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation accountDisableNotificationCategory($input: AccountDisableNotificationCategoryInput!) { - accountDisableNotificationCategory(input: $input) { - errors { - message - } - account { - notificationSettings { - push { - enabled - disabledCategories - } - } - } - } -} diff --git a/test/galoy/bats/gql/account-disable-notification-channel.gql b/test/galoy/bats/gql/account-disable-notification-channel.gql deleted file mode 100644 index 34e9a5795..000000000 --- a/test/galoy/bats/gql/account-disable-notification-channel.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation accountDisableNotificationChannel($input: AccountDisableNotificationChannelInput!) { - accountDisableNotificationChannel(input: $input) { - errors { - message - } - account { - notificationSettings { - push { - enabled - disabledCategories - } - } - } - } -} diff --git a/test/galoy/bats/gql/account-enable-notification-category.gql b/test/galoy/bats/gql/account-enable-notification-category.gql deleted file mode 100644 index 5b5b752fe..000000000 --- a/test/galoy/bats/gql/account-enable-notification-category.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation accountEnableNotificationCategory($input: AccountEnableNotificationCategoryInput!) { - accountEnableNotificationCategory(input: $input) { - errors { - message - } - account { - notificationSettings { - push { - enabled - disabledCategories - } - } - } - } -} diff --git a/test/galoy/bats/gql/account-enable-notification-channel.gql b/test/galoy/bats/gql/account-enable-notification-channel.gql deleted file mode 100644 index a3593270d..000000000 --- a/test/galoy/bats/gql/account-enable-notification-channel.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation accountEnableNotificationChannel($input: AccountEnableNotificationChannelInput!) { - accountEnableNotificationChannel(input: $input) { - errors { - message - } - account { - notificationSettings { - push { - enabled - disabledCategories - } - } - } - } -} diff --git a/test/galoy/bats/gql/account-update-default-wallet-id.gql b/test/galoy/bats/gql/account-update-default-wallet-id.gql deleted file mode 100644 index b175a7726..000000000 --- a/test/galoy/bats/gql/account-update-default-wallet-id.gql +++ /dev/null @@ -1,7 +0,0 @@ -mutation accountUpdateDefaultWalletId($input: AccountUpdateDefaultWalletIdInput!) { - accountUpdateDefaultWalletId(input: $input) { - account { - defaultWalletId - } - } -} diff --git a/test/galoy/bats/gql/callback-endpoint-add.gql b/test/galoy/bats/gql/callback-endpoint-add.gql deleted file mode 100644 index 0b5f7ee9b..000000000 --- a/test/galoy/bats/gql/callback-endpoint-add.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation callbackEndpointAdd($input: CallbackEndpointAddInput!) { - callbackEndpointAdd(input: $input) { - errors { - message - } - id - } -} diff --git a/test/galoy/bats/gql/callback-endpoint-delete.gql b/test/galoy/bats/gql/callback-endpoint-delete.gql deleted file mode 100644 index 82a981239..000000000 --- a/test/galoy/bats/gql/callback-endpoint-delete.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation callbackEndpointDelete($input: CallbackEndpointDeleteInput!) { - callbackEndpointDelete(input: $input) { - errors { - message - } - success - } -} diff --git a/test/galoy/bats/gql/callback-endpoints-list.gql b/test/galoy/bats/gql/callback-endpoints-list.gql deleted file mode 100644 index 66aca3135..000000000 --- a/test/galoy/bats/gql/callback-endpoints-list.gql +++ /dev/null @@ -1,10 +0,0 @@ -query callbackEndpointsList { - me { - defaultAccount { - callbackEndpoints { - id - url - } - } - } -} diff --git a/test/galoy/bats/gql/contacts.gql b/test/galoy/bats/gql/contacts.gql deleted file mode 100644 index 285ab41a4..000000000 --- a/test/galoy/bats/gql/contacts.gql +++ /dev/null @@ -1,7 +0,0 @@ -query me { - me { - contacts { - username - } - } -} diff --git a/test/galoy/bats/gql/globals.gql b/test/galoy/bats/gql/globals.gql deleted file mode 100644 index 150920389..000000000 --- a/test/galoy/bats/gql/globals.gql +++ /dev/null @@ -1,6 +0,0 @@ -query Globals { - globals { - network - nodesIds - } -} diff --git a/test/galoy/bats/gql/identity.gql b/test/galoy/bats/gql/identity.gql deleted file mode 100644 index 04643e0c9..000000000 --- a/test/galoy/bats/gql/identity.gql +++ /dev/null @@ -1,11 +0,0 @@ -query identity { - me { - id - phone - email { - address - verified - } - totpEnabled - } -} diff --git a/test/galoy/bats/gql/intraledger-payment-send.gql b/test/galoy/bats/gql/intraledger-payment-send.gql deleted file mode 100644 index c0e648671..000000000 --- a/test/galoy/bats/gql/intraledger-payment-send.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation intraLedgerPaymentSend($input: IntraLedgerPaymentSendInput!) { - intraLedgerPaymentSend(input: $input) { - status - errors { - message - path - } - } -} diff --git a/test/galoy/bats/gql/intraledger-usd-payment-send.gql b/test/galoy/bats/gql/intraledger-usd-payment-send.gql deleted file mode 100644 index 5e72e9d70..000000000 --- a/test/galoy/bats/gql/intraledger-usd-payment-send.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation intraLedgerUsdPaymentSend($input: IntraLedgerUsdPaymentSendInput!) { - intraLedgerUsdPaymentSend(input: $input) { - status - errors { - message - path - } - } -} diff --git a/test/galoy/bats/gql/ln-invoice-create-on-behalf-of-recipient.gql b/test/galoy/bats/gql/ln-invoice-create-on-behalf-of-recipient.gql deleted file mode 100644 index 85978f60e..000000000 --- a/test/galoy/bats/gql/ln-invoice-create-on-behalf-of-recipient.gql +++ /dev/null @@ -1,13 +0,0 @@ -mutation lnInvoiceCreateOnBehalfOfRecipient( - $input: LnInvoiceCreateOnBehalfOfRecipientInput! -) { - lnInvoiceCreateOnBehalfOfRecipient(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-invoice-create.gql b/test/galoy/bats/gql/ln-invoice-create.gql deleted file mode 100644 index 3e3a27d0a..000000000 --- a/test/galoy/bats/gql/ln-invoice-create.gql +++ /dev/null @@ -1,11 +0,0 @@ -mutation lnInvoiceCreateInput($input: LnInvoiceCreateInput!) { - lnInvoiceCreate(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-invoice-fee-probe.gql b/test/galoy/bats/gql/ln-invoice-fee-probe.gql deleted file mode 100644 index 3064f585d..000000000 --- a/test/galoy/bats/gql/ln-invoice-fee-probe.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation LnInvoiceFeeProbe($input: LnInvoiceFeeProbeInput!) { - lnInvoiceFeeProbe(input: $input) { - errors { - message - } - amount - } -} diff --git a/test/galoy/bats/gql/ln-invoice-payment-send.gql b/test/galoy/bats/gql/ln-invoice-payment-send.gql deleted file mode 100644 index cb66944ef..000000000 --- a/test/galoy/bats/gql/ln-invoice-payment-send.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnInvoicePaymentSend($input: LnInvoicePaymentInput!) { - lnInvoicePaymentSend(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/ln-invoice-payment-status-sub.gql b/test/galoy/bats/gql/ln-invoice-payment-status-sub.gql deleted file mode 100644 index 6e26bd073..000000000 --- a/test/galoy/bats/gql/ln-invoice-payment-status-sub.gql +++ /dev/null @@ -1,8 +0,0 @@ -subscription lnInvoicePaymentStatusSubscription($input: LnInvoicePaymentStatusInput!) { - lnInvoicePaymentStatus(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/ln-invoice-payment-status.gql b/test/galoy/bats/gql/ln-invoice-payment-status.gql deleted file mode 100644 index af9e729f3..000000000 --- a/test/galoy/bats/gql/ln-invoice-payment-status.gql +++ /dev/null @@ -1,5 +0,0 @@ -query LnInvoicePaymentStatusQuery($input: LnInvoicePaymentStatusInput!) { - lnInvoicePaymentStatus(input: $input) { - status - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-invoice-create-on-behalf-of-recipient.gql b/test/galoy/bats/gql/ln-no-amount-invoice-create-on-behalf-of-recipient.gql deleted file mode 100644 index 5521fc6d9..000000000 --- a/test/galoy/bats/gql/ln-no-amount-invoice-create-on-behalf-of-recipient.gql +++ /dev/null @@ -1,13 +0,0 @@ -mutation lnNoAmountInvoiceCreateOnBehalfOfRecipient( - $input: LnNoAmountInvoiceCreateOnBehalfOfRecipientInput! -) { - lnNoAmountInvoiceCreateOnBehalfOfRecipient(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-invoice-create.gql b/test/galoy/bats/gql/ln-no-amount-invoice-create.gql deleted file mode 100644 index ac0366739..000000000 --- a/test/galoy/bats/gql/ln-no-amount-invoice-create.gql +++ /dev/null @@ -1,11 +0,0 @@ -mutation lnNoAmountInvoiceCreate($input: LnNoAmountInvoiceCreateInput!) { - lnNoAmountInvoiceCreate(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-invoice-fee-probe.gql b/test/galoy/bats/gql/ln-no-amount-invoice-fee-probe.gql deleted file mode 100644 index cbd12026a..000000000 --- a/test/galoy/bats/gql/ln-no-amount-invoice-fee-probe.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnNoAmountInvoiceFeeProbe($input: LnNoAmountInvoiceFeeProbeInput!) { - lnNoAmountInvoiceFeeProbe(input: $input) { - errors { - message - } - amount - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-invoice-payment-send.gql b/test/galoy/bats/gql/ln-no-amount-invoice-payment-send.gql deleted file mode 100644 index 7131b589b..000000000 --- a/test/galoy/bats/gql/ln-no-amount-invoice-payment-send.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnNoAmountInvoicePaymentSend($input: LnNoAmountInvoicePaymentInput!) { - lnNoAmountInvoicePaymentSend(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-usd-invoice-fee-probe.gql b/test/galoy/bats/gql/ln-no-amount-usd-invoice-fee-probe.gql deleted file mode 100644 index 00bde20e6..000000000 --- a/test/galoy/bats/gql/ln-no-amount-usd-invoice-fee-probe.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnNoAmountUsdInvoiceFeeProbe($input: LnNoAmountUsdInvoiceFeeProbeInput!) { - lnNoAmountUsdInvoiceFeeProbe(input: $input) { - errors { - message - } - amount - } -} diff --git a/test/galoy/bats/gql/ln-no-amount-usd-invoice-payment-send.gql b/test/galoy/bats/gql/ln-no-amount-usd-invoice-payment-send.gql deleted file mode 100644 index a34ccee88..000000000 --- a/test/galoy/bats/gql/ln-no-amount-usd-invoice-payment-send.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnNoAmountUsdInvoicePaymentSend($input: LnNoAmountUsdInvoicePaymentInput!) { - lnNoAmountUsdInvoicePaymentSend(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/ln-usd-invoice-create-on-behalf-of-recipient.gql b/test/galoy/bats/gql/ln-usd-invoice-create-on-behalf-of-recipient.gql deleted file mode 100644 index 80c07e35b..000000000 --- a/test/galoy/bats/gql/ln-usd-invoice-create-on-behalf-of-recipient.gql +++ /dev/null @@ -1,13 +0,0 @@ -mutation lnUsdInvoiceCreateOnBehalfOfRecipient( - $input: LnUsdInvoiceCreateOnBehalfOfRecipientInput! -) { - lnUsdInvoiceCreateOnBehalfOfRecipient(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-usd-invoice-create.gql b/test/galoy/bats/gql/ln-usd-invoice-create.gql deleted file mode 100644 index 270e9ef00..000000000 --- a/test/galoy/bats/gql/ln-usd-invoice-create.gql +++ /dev/null @@ -1,11 +0,0 @@ -mutation lnUsdInvoiceCreate($input: LnUsdInvoiceCreateInput!) { - lnUsdInvoiceCreate(input: $input) { - invoice { - paymentRequest - paymentHash - } - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/ln-usd-invoice-fee-probe.gql b/test/galoy/bats/gql/ln-usd-invoice-fee-probe.gql deleted file mode 100644 index 1508e144a..000000000 --- a/test/galoy/bats/gql/ln-usd-invoice-fee-probe.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation lnUsdInvoiceFeeProbe($input: LnUsdInvoiceFeeProbeInput!) { - lnUsdInvoiceFeeProbe(input: $input) { - errors { - message - } - amount - } -} diff --git a/test/galoy/bats/gql/logout.gql b/test/galoy/bats/gql/logout.gql deleted file mode 100644 index e94bcc3d8..000000000 --- a/test/galoy/bats/gql/logout.gql +++ /dev/null @@ -1,6 +0,0 @@ -mutation userLogout { - userLogout { - success - __typename - } -} diff --git a/test/galoy/bats/gql/my-updates-sub.gql b/test/galoy/bats/gql/my-updates-sub.gql deleted file mode 100644 index 94297df87..000000000 --- a/test/galoy/bats/gql/my-updates-sub.gql +++ /dev/null @@ -1,57 +0,0 @@ -subscription myUpdates { - myUpdates { - errors { - message - } - me { - id - defaultAccount { - id - wallets { - id - walletCurrency - balance - } - } - } - update { - type: __typename - ... on Price { - base - offset - currencyUnit - formattedAmount - } - ... on RealtimePrice { - id - timestamp - denominatorCurrency - btcSatPrice { - base - offset - currencyUnit - } - usdCentPrice { - base - offset - currencyUnit - } - } - ... on LnUpdate { - paymentHash - status - } - ... on OnChainUpdate { - txNotificationType - txHash - amount - usdPerSat - } - ... on IntraLedgerUpdate { - txNotificationType - amount - usdPerSat - } - } - } -} diff --git a/test/galoy/bats/gql/on-chain-address-create.gql b/test/galoy/bats/gql/on-chain-address-create.gql deleted file mode 100644 index b8992d276..000000000 --- a/test/galoy/bats/gql/on-chain-address-create.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation onChainAddressCreate($input: OnChainAddressCreateInput!) { - onChainAddressCreate(input: $input) { - address - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/on-chain-address-current.gql b/test/galoy/bats/gql/on-chain-address-current.gql deleted file mode 100644 index f6d82f37b..000000000 --- a/test/galoy/bats/gql/on-chain-address-current.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation onChainAddressCurrent($input: OnChainAddressCurrentInput!) { - onChainAddressCurrent(input: $input) { - address - errors { - message - } - } -} diff --git a/test/galoy/bats/gql/on-chain-payment-send-all.gql b/test/galoy/bats/gql/on-chain-payment-send-all.gql deleted file mode 100644 index 197e9732a..000000000 --- a/test/galoy/bats/gql/on-chain-payment-send-all.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation onChainPaymentSendAll($input: OnChainPaymentSendAllInput!) { - onChainPaymentSendAll(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/on-chain-payment-send.gql b/test/galoy/bats/gql/on-chain-payment-send.gql deleted file mode 100644 index 097cb6fa4..000000000 --- a/test/galoy/bats/gql/on-chain-payment-send.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation onChainPaymentSend($input: OnChainPaymentSendInput!) { - onChainPaymentSend(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/on-chain-usd-payment-send-as-btc-denominated.gql b/test/galoy/bats/gql/on-chain-usd-payment-send-as-btc-denominated.gql deleted file mode 100644 index e25c6ec28..000000000 --- a/test/galoy/bats/gql/on-chain-usd-payment-send-as-btc-denominated.gql +++ /dev/null @@ -1,10 +0,0 @@ -mutation onChainUsdPaymentSendAsBtcDenominated( - $input: OnChainUsdPaymentSendAsBtcDenominatedInput! -) { - onChainUsdPaymentSendAsBtcDenominated(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/on-chain-usd-payment-send.gql b/test/galoy/bats/gql/on-chain-usd-payment-send.gql deleted file mode 100644 index 47003b24b..000000000 --- a/test/galoy/bats/gql/on-chain-usd-payment-send.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation onChainUsdPaymentSend($input: OnChainUsdPaymentSendInput!) { - onChainUsdPaymentSend(input: $input) { - errors { - message - } - status - } -} diff --git a/test/galoy/bats/gql/price-sub.gql b/test/galoy/bats/gql/price-sub.gql deleted file mode 100644 index a00f77db4..000000000 --- a/test/galoy/bats/gql/price-sub.gql +++ /dev/null @@ -1,14 +0,0 @@ -subscription { - price( - input: { amount: 100 amountCurrencyUnit: BTCSAT priceCurrencyUnit: USDCENT } - ) { - errors { - message - } - price { - base - offset - currencyUnit - } - } -} diff --git a/test/galoy/bats/gql/real-time-price-sub.gql b/test/galoy/bats/gql/real-time-price-sub.gql deleted file mode 100644 index 00c13d1b2..000000000 --- a/test/galoy/bats/gql/real-time-price-sub.gql +++ /dev/null @@ -1,22 +0,0 @@ -subscription realtimePrice($currency: DisplayCurrency!) { - realtimePrice(input: { currency: $currency }) { - errors { - message - } - realtimePrice { - id - timestamp - denominatorCurrency - btcSatPrice { - base - offset - currencyUnit - } - usdCentPrice { - base - offset - currencyUnit - } - } - } -} diff --git a/test/galoy/bats/gql/transactions-by-address.gql b/test/galoy/bats/gql/transactions-by-address.gql deleted file mode 100644 index 00ff80989..000000000 --- a/test/galoy/bats/gql/transactions-by-address.gql +++ /dev/null @@ -1,68 +0,0 @@ -query transactionsByAddress($first: Int, $after: String, $address: OnChainAddress!) { - me { - defaultAccount { - displayCurrency - wallets { - __typename - id - walletCurrency - transactionsByAddress(first: $first, after: $after, address: $address) { - ...TransactionList - } - } - } - } -} - -fragment TransactionList on TransactionConnection { - pageInfo { - hasNextPage - } - edges { - cursor - node { - __typename - id - status - direction - memo - createdAt - settlementAmount - settlementFee - settlementDisplayAmount - settlementDisplayFee - settlementDisplayCurrency - settlementCurrency - settlementPrice { - base - offset - } - initiationVia { - __typename - ... on InitiationViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on InitiationViaLn { - paymentHash - } - ... on InitiationViaOnChain { - address - } - } - settlementVia { - __typename - ... on SettlementViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on SettlementViaLn { - preImage - } - ... on SettlementViaOnChain { - transactionHash - } - } - } - } -} diff --git a/test/galoy/bats/gql/transactions-by-wallet.gql b/test/galoy/bats/gql/transactions-by-wallet.gql deleted file mode 100644 index 150460d39..000000000 --- a/test/galoy/bats/gql/transactions-by-wallet.gql +++ /dev/null @@ -1,78 +0,0 @@ -query transactionsByWallet($first: Int, $after: String) { - me { - defaultAccount { - displayCurrency - wallets { - ... on BTCWallet { - __typename - id - walletCurrency - transactions(first: $first, after: $after) { - ...TransactionList - } - } - ... on UsdWallet { - __typename - id - walletCurrency - transactions(first: $first, after: $after) { - ...TransactionList - } - } - } - } - } -} - -fragment TransactionList on TransactionConnection { - pageInfo { - hasNextPage - } - edges { - cursor - node { - __typename - id - status - direction - memo - createdAt - settlementAmount - settlementFee - settlementDisplayAmount - settlementDisplayFee - settlementDisplayCurrency - settlementCurrency - settlementPrice { - base - offset - } - initiationVia { - __typename - ... on InitiationViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on InitiationViaLn { - paymentHash - } - ... on InitiationViaOnChain { - address - } - } - settlementVia { - __typename - ... on SettlementViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on SettlementViaLn { - preImage - } - ... on SettlementViaOnChain { - transactionHash - } - } - } - } -} diff --git a/test/galoy/bats/gql/transactions.gql b/test/galoy/bats/gql/transactions.gql deleted file mode 100644 index a8d773dd8..000000000 --- a/test/galoy/bats/gql/transactions.gql +++ /dev/null @@ -1,63 +0,0 @@ -query transactions($walletIds: [WalletId], $first: Int, $after: String) { - me { - defaultAccount { - defaultWalletId - transactions(walletIds: $walletIds, first: $first, after: $after) { - ...TransactionList - } - } - } -} - -fragment TransactionList on TransactionConnection { - pageInfo { - hasNextPage - } - edges { - cursor - node { - __typename - id - status - direction - memo - createdAt - settlementAmount - settlementFee - settlementDisplayAmount - settlementDisplayFee - settlementDisplayCurrency - settlementCurrency - settlementPrice { - base - offset - } - initiationVia { - __typename - ... on InitiationViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on InitiationViaLn { - paymentHash - } - ... on InitiationViaOnChain { - address - } - } - settlementVia { - __typename - ... on SettlementViaIntraLedger { - counterPartyWalletId - counterPartyUsername - } - ... on SettlementViaLn { - preImage - } - ... on SettlementViaOnChain { - transactionHash - } - } - } - } -} diff --git a/test/galoy/bats/gql/user-email-delete.gql b/test/galoy/bats/gql/user-email-delete.gql deleted file mode 100644 index a83e165a5..000000000 --- a/test/galoy/bats/gql/user-email-delete.gql +++ /dev/null @@ -1,14 +0,0 @@ -mutation userEmailDelete { - userEmailDelete { - errors { - message - } - me { - id - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-email-registration-initiate.gql b/test/galoy/bats/gql/user-email-registration-initiate.gql deleted file mode 100644 index e71d774a3..000000000 --- a/test/galoy/bats/gql/user-email-registration-initiate.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation userEmailRegistrationInitiate($input: UserEmailRegistrationInitiateInput!) { - userEmailRegistrationInitiate(input: $input) { - errors { - message - } - emailRegistrationId - me { - id - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-email-registration-validate.gql b/test/galoy/bats/gql/user-email-registration-validate.gql deleted file mode 100644 index 14fc670c2..000000000 --- a/test/galoy/bats/gql/user-email-registration-validate.gql +++ /dev/null @@ -1,14 +0,0 @@ -mutation userEmailRegistrationValidate($input: UserEmailRegistrationValidateInput!) { - userEmailRegistrationValidate(input: $input) { - errors { - message - } - me { - id - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-login-upgrade.gql b/test/galoy/bats/gql/user-login-upgrade.gql deleted file mode 100644 index 3c8045f87..000000000 --- a/test/galoy/bats/gql/user-login-upgrade.gql +++ /dev/null @@ -1,10 +0,0 @@ -mutation userLoginUpgrade($input: UserLoginUpgradeInput!) { - userLoginUpgrade(input: $input) { - errors { - message - code - } - success - authToken - } -} diff --git a/test/galoy/bats/gql/user-login.gql b/test/galoy/bats/gql/user-login.gql deleted file mode 100644 index 44b1b124c..000000000 --- a/test/galoy/bats/gql/user-login.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation userLogin($input: UserLoginInput!) { - userLogin(input: $input) { - errors { - code - message - } - authToken - } -} diff --git a/test/galoy/bats/gql/user-phone-delete.gql b/test/galoy/bats/gql/user-phone-delete.gql deleted file mode 100644 index be0befde8..000000000 --- a/test/galoy/bats/gql/user-phone-delete.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation userPhoneDelete { - userPhoneDelete { - errors { - message - } - me { - id - phone - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-phone-registration-initiate.gql b/test/galoy/bats/gql/user-phone-registration-initiate.gql deleted file mode 100644 index db8ad8cd2..000000000 --- a/test/galoy/bats/gql/user-phone-registration-initiate.gql +++ /dev/null @@ -1,8 +0,0 @@ -mutation userPhoneRegistrationInitiate($input: UserPhoneRegistrationInitiateInput!) { - userPhoneRegistrationInitiate(input: $input) { - errors { - message - } - success - } -} diff --git a/test/galoy/bats/gql/user-phone-registration-validate.gql b/test/galoy/bats/gql/user-phone-registration-validate.gql deleted file mode 100644 index 43030c998..000000000 --- a/test/galoy/bats/gql/user-phone-registration-validate.gql +++ /dev/null @@ -1,11 +0,0 @@ -mutation userPhoneRegistrationValidate($input: UserPhoneRegistrationValidateInput!) { - userPhoneRegistrationValidate(input: $input) { - errors { - message - } - me { - id - phone - } - } -} diff --git a/test/galoy/bats/gql/user-totp-delete.gql b/test/galoy/bats/gql/user-totp-delete.gql deleted file mode 100644 index dde4e47e5..000000000 --- a/test/galoy/bats/gql/user-totp-delete.gql +++ /dev/null @@ -1,14 +0,0 @@ -mutation userTotpDelete($input: UserTotpDeleteInput!) { - userTotpDelete(input: $input) { - errors { - message - } - me { - totpEnabled - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-totp-registration-initiate.gql b/test/galoy/bats/gql/user-totp-registration-initiate.gql deleted file mode 100644 index 606f802b7..000000000 --- a/test/galoy/bats/gql/user-totp-registration-initiate.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation userTotpRegistrationInitiate($input: UserTotpRegistrationInitiateInput!) { - userTotpRegistrationInitiate(input: $input) { - errors { - message - } - totpRegistrationId - totpSecret - } -} diff --git a/test/galoy/bats/gql/user-totp-registration-validate.gql b/test/galoy/bats/gql/user-totp-registration-validate.gql deleted file mode 100644 index 2f3c54b25..000000000 --- a/test/galoy/bats/gql/user-totp-registration-validate.gql +++ /dev/null @@ -1,14 +0,0 @@ -mutation userTotpRegistrationValidate($input: UserTotpRegistrationValidateInput!) { - userTotpRegistrationValidate(input: $input) { - errors { - message - } - me { - totpEnabled - email { - address - verified - } - } - } -} diff --git a/test/galoy/bats/gql/user-update-username.gql b/test/galoy/bats/gql/user-update-username.gql deleted file mode 100644 index a971942f9..000000000 --- a/test/galoy/bats/gql/user-update-username.gql +++ /dev/null @@ -1,14 +0,0 @@ -mutation userUpdateUsername($input: UserUpdateUsernameInput!) { - userUpdateUsername(input: $input) { - errors { - message - __typename - } - user { - id - username - __typename - } - __typename - } -} diff --git a/test/galoy/bats/gql/wallets-for-account.gql b/test/galoy/bats/gql/wallets-for-account.gql deleted file mode 100644 index 467922664..000000000 --- a/test/galoy/bats/gql/wallets-for-account.gql +++ /dev/null @@ -1,13 +0,0 @@ -query me { - me { - defaultAccount { - id - wallets { - id - walletCurrency - balance - pendingIncomingBalance - } - } - } -} diff --git a/test/galoy/bats/helpers/_common.bash b/test/galoy/bats/helpers/_common.bash deleted file mode 100644 index 0e4b280d3..000000000 --- a/test/galoy/bats/helpers/_common.bash +++ /dev/null @@ -1,295 +0,0 @@ -REPO_ROOT=$(git rev-parse --show-toplevel) -COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-${REPO_ROOT##*/}}" - -CACHE_DIR=${BATS_TMPDIR:-tmp/bats}/galoy-bats-cache -mkdir -p $CACHE_DIR - -GALOY_ENDPOINT=${GALOY_ENDPOINT:-localhost:4002} - -ALICE_TOKEN_NAME="alice" -ALICE_PHONE="+16505554328" - -BOB_TOKEN_NAME="bob" -BOB_PHONE="+16505554350" - -CODE="000000" - -if ! type fail &>/dev/null; then - fail() { - echo "$1" - exit 1 - } -fi - - -bitcoin_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-bitcoind-1" bitcoin-cli $@ -} - -bria_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-bria-1" bria $@ -} - -cache_value() { - echo $2 >${CACHE_DIR}/$1 -} - -read_value() { - cat ${CACHE_DIR}/$1 -} - -is_number() { - if ! [[ $1 =~ ^-?[0-9]+$ ]]; then - echo "Error: Input is not a number" - exit 1 - fi -} - -abs() { - is_number $1 || return 1 - - if [[ $1 -lt 0 ]]; then - echo "$((- $1))" - else - echo "$1" - fi -} - -# Run the given command in the background. Useful for starting a -# node and then moving on with commands that exercise it for the -# test. -# -# Ensures that BATS' handling of file handles is taken into account; -# see -# https://github.com/bats-core/bats-core#printing-to-the-terminal -# https://github.com/sstephenson/bats/issues/80#issuecomment-174101686 -# for details. -background() { - "$@" 3>- & - echo $! -} - -# Taken from https://github.com/docker/swarm/blob/master/test/integration/helpers.bash -# Retry a command $1 times until it succeeds. Wait $2 seconds between retries. -retry() { - local attempts=$1 - shift - local delay=$1 - shift - local i - - for ((i = 0; i < attempts; i++)); do - if [[ "${BATS_TEST_DIRNAME}" = "" ]]; then - "$@" - else - run "$@" - fi - - if [[ "$status" -eq 0 ]]; then - return 0 - fi - sleep "$delay" - done - - echo "Command \"$*\" failed $attempts times. Output: $output" - false -} - -gql_query() { - cat "$(gql_file $1)" | tr '\n' ' ' | sed 's/"/\\"/g' -} - -gql_file() { - echo "${BATS_TEST_DIRNAME:-${REPO_ROOT}/test/galoy/bats}/gql/$1.gql" -} - -gql_admin_query() { - cat "$(gql_admin_file $1)" | tr '\n' ' ' | sed 's/"/\\"/g' -} - -gql_admin_file() { - echo "${BATS_TEST_DIRNAME:-${REPO_ROOT}/test/bats}/admin-gql/$1.gql" -} - -new_idempotency_key() { - random_uuid -} - -exec_graphql() { - local token_name=$1 - local query_name=$2 - local variables=${3:-"{}"} - echo "GQL query - user: ${token_name} - query: ${query_name} - vars: ${variables}" - echo "{\"query\": \"$(gql_query $query_name)\", \"variables\": $variables}" - - if [[ ${token_name} == "anon" ]]; then - AUTH_HEADER="" - else - AUTH_HEADER="Authorization: Bearer $(read_value ${token_name})" - fi - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - run_cmd="run" - else - run_cmd="" - fi - - gql_route="graphql" - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - # In BATS: run command captures output into $output - ${run_cmd} curl -s \ - -X POST \ - ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ - -H "Content-Type: application/json" \ - -H "X-Idempotency-Key: $(new_idempotency_key)" \ - -d "{\"query\": \"$(gql_query $query_name)\", \"variables\": $variables}" \ - "${GALOY_ENDPOINT}/${gql_route}" - else - # Outside BATS: manually capture output - output=$(curl -s \ - -X POST \ - ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ - -H "Content-Type: application/json" \ - -H "X-Idempotency-Key: $(new_idempotency_key)" \ - -d "{\"query\": \"$(gql_query $query_name)\", \"variables\": $variables}" \ - "${GALOY_ENDPOINT}/${gql_route}") - fi - - echo "GQL output: '$output'" -} - -exec_admin_graphql() { - local token_name=$1 - local query_name=$2 - local variables=${3:-"{}"} - echo "GQL query - user: ${token_name} - query: ${query_name} - vars: ${variables}" - echo "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" - - if [[ ${token_name} == "anon" ]]; then - AUTH_HEADER="" - else - AUTH_HEADER="Authorization: Bearer $(read_value ${token_name})" - fi - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - run_cmd="run" - else - run_cmd="" - fi - - gql_route="admin/graphql" - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - # In BATS: run command captures output into $output - ${run_cmd} curl -s \ - -X POST \ - ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ - -H "Content-Type: application/json" \ - -d "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" \ - "${GALOY_ENDPOINT}/${gql_route}" - else - # Outside BATS: manually capture output - output=$(curl -s \ - -X POST \ - ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ - -H "Content-Type: application/json" \ - -d "{\"query\": \"$(gql_admin_query $query_name)\", \"variables\": $variables}" \ - "${GALOY_ENDPOINT}/${gql_route}") - fi - - echo "GQL output: '$output'" -} - -graphql_output() { - echo $output | jq -r "$@" -} - -random_uuid() { - if [[ -e /proc/sys/kernel/random/uuid ]]; then - cat /proc/sys/kernel/random/uuid - else - uuidgen - fi -} - -curl_request() { - local url=$1 - shift - local data=${1:-""} - shift - local headers=("$@") - - echo "Curl request - url: ${url} - data: ${data}" - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - run_cmd="run" - else - run_cmd="" - fi - - cmd=(${run_cmd} curl -s -X POST -H "Content-Type: application/json") - - for header in "${headers[@]}"; do - cmd+=(-H "$header") - done - - if [[ -n "$data" ]]; then - cmd+=(-d "${data}") - fi - - cmd+=("${url}") - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - # In BATS: run command captures output into $output - "${cmd[@]}" - else - # Outside BATS: manually capture output - output=$("${cmd[@]}") - fi - - echo "Curl output: '$output'" -} - -curl_output() { - echo $output | jq -r "$@" -} - -is_contact() { - local token_name="$1" - local contact_username="$2" - - exec_graphql "$token_name" 'contacts' - local fetched_username=$( - graphql_output \ - --arg contact_username "$contact_username" \ - '.data.me.contacts[] | select(.username == $contact_username) .username' - ) - [[ "$fetched_username" == "$contact_username" ]] || return 1 -} - -create_device_account() { - local token_name="$1" - local url="http://${GALOY_ENDPOINT}/auth/create/device-account" - - # dev/ory/gen-test-jwt.ts - local jwt="eyJhbGciOiJSUzI1NiIsImtpZCI6IjFiOTdiMjIxLWNhMDgtNGViMi05ZDA5LWE1NzcwZmNjZWIzNyJ9.eyJzdWIiOiIxOjgwNjY0NjE0MDQzNTphbmRyb2lkOmE4YTBjY2ZlODhiZWUxNWIwNmY5ZTYiLCJhdWQiOlsicHJvamVjdHMvODA2NjQ2MTQwNDM1IiwicHJvamVjdHMvYXZpZC1jZWlsaW5nLTM5MDQxOCJdLCJwcm92aWRlciI6ImRlYnVnIiwiaXNzIjoiaHR0cHM6Ly9maXJlYmFzZWFwcGNoZWNrLmdvb2dsZWFwaXMuY29tLzgwNjY0NjE0MDQzNSIsImV4cCI6MjYzOTAwMDA2OX0.cgE2pX3srSzlPreJpBDLaFmPQn9CyKoxW1f-hFgVbGZ7xwWysogsNTrV0eIkvgDnZWjbjexOxf4HhuK2MSBmnRYTWgk6LC7LNoq_KPNAvxkMNj1HGSYh34q2uYafcc1LZCREDvPFTw-JN6FJOAzk7TbWwi8A8-Z8ed5W1kqzkWu_D79nZNWZuN6tUpoeyj1c77Cb7wn5UBlSBhoNrfxXOQKTsKTmuFpcR2P3zv_R9D-yedizqLpG75XJkJd6_4zuhhrW05nMgOHULQ2bTt3PTbi6dy64ObLwMOT5vevqqbKc303-rk02sDGCdRc251nL5sIvTIcajXUXs-Ruy3Op4g" # the JWT token - - local username="$(random_uuid)" - local password="$(random_uuid)" - - if [[ "$(uname)" == "Linux" ]]; then - local basic_token="$(echo -n $username:$password | base64 -w 0)" - else - local basic_token="$(echo -n $username:$password | base64)" - fi - - local auth_header="Authorization: Basic $basic_token" - local appcheck_header="Appcheck: $jwt" - - # Create account - curl_request "$url" "" "$auth_header" "$appcheck_header" - local auth_token="$(echo $output | jq -r '.result')" - [[ "$auth_token" != "null" ]] || return 1 - cache_value "$token_name" "$auth_token" - } diff --git a/test/galoy/bats/helpers/callback.js b/test/galoy/bats/helpers/callback.js deleted file mode 100644 index 2ed7673d6..000000000 --- a/test/galoy/bats/helpers/callback.js +++ /dev/null @@ -1,20 +0,0 @@ -const http = require("http") - -const PORT = 8080 - -const server = http.createServer((req, res) => { - let body = [] - req - .on("data", (chunk) => { - body.push(chunk) - }) - .on("end", () => { - body = Buffer.concat(body).toString() - console.log(body) - res.end("Received") - }) -}) - -server.listen(PORT, () => { - console.error(`Server listening on port ${PORT}`) -}) diff --git a/test/galoy/bats/helpers/generate-totp.js b/test/galoy/bats/helpers/generate-totp.js deleted file mode 100644 index 03033f38b..000000000 --- a/test/galoy/bats/helpers/generate-totp.js +++ /dev/null @@ -1,4 +0,0 @@ -// eslint-disable-next-line -const { authenticator } = require("otplib") -const secret = process.argv[2] -console.log(authenticator.generate(secret)) diff --git a/test/galoy/bats/helpers/ln.bash b/test/galoy/bats/helpers/ln.bash deleted file mode 100644 index 9f730f4af..000000000 --- a/test/galoy/bats/helpers/ln.bash +++ /dev/null @@ -1,335 +0,0 @@ -BASH_SOURCE=${BASH_SOURCE:-test/bats/helpers/.} -source $(dirname "$BASH_SOURCE")/_common.bash - -LND_FUNDING_TOKEN_NAME="lnd_funding" -LND_FUNDING_PHONE="+16505554351" - -LNDS_REST_LOG=".e2e-lnds-rest.log" - -mempool_not_empty() { - local txid="$(bitcoin_cli getrawmempool | jq -r ".[0]")" - [[ "$txid" != "null" ]] || exit 1 -} - -run_with_lnd() { - local func_name="$1" - shift # This will shift away the function name, so $1 becomes the next argument - - if [[ "$func_name" == "lnd_cli" ]]; then - lnd_cli "$@" - elif [[ "$func_name" == "lnd2_cli" ]]; then - lnd2_cli "$@" - elif [[ "$func_name" == "lnd_outside_cli" ]]; then - lnd_outside_cli "$@" - elif [[ "$func_name" == "lnd_outside_2_cli" ]]; then - lnd_outside_2_cli "$@" - else - echo "Invalid function name passed!" && return 1 - fi -} - -close_partner_initiated_channels_with_external() { - close_channels_with_external() { - lnd_cli_value="$1" - lnd1_pubkey=$(lnd_cli getinfo | jq -r '.identity_pubkey') - lnd2_pubkey=$(lnd2_cli getinfo | jq -r '.identity_pubkey') - - partner_initiated_external_channel_filter=' - .channels[]? - | select(.initiator != true) - | select(.remote_pubkey != $lnd1_pubkey) - | select(.remote_pubkey != $lnd2_pubkey) - | .channel_point - ' - - run_with_lnd "$lnd_cli_value" listchannels \ - | jq -r \ - --arg lnd1_pubkey "$lnd1_pubkey" \ - --arg lnd2_pubkey "$lnd2_pubkey" \ - "$partner_initiated_external_channel_filter" \ - | while read -r channel_point; do - funding_txid="${channel_point%%:*}" - run_with_lnd "$lnd_cli_value" closechannel "$funding_txid" - done - } - - close_channels_with_external lnd_cli - close_channels_with_external lnd2_cli - close_channels_with_external lnd_outside_cli - close_channels_with_external lnd_outside_2_cli -} - -lnds_init() { - # Clean up any existing channels - close_partner_initiated_channels_with_external || true - - # Mine onchain balance - local amount="1" - local address="$(lnd_outside_cli newaddress p2wkh | jq -r '.address')" - local local_amount="10000000" - local push_amount="5000000" - bitcoin_cli sendtoaddress "$address" "$amount" - bitcoin_cli -generate 3 - - no_pending_channels() { - pending_channel="$(lnd_outside_cli pendingchannels | jq -r '.pending_open_channels[0]')" - if [[ "$pending_channel" != "null" ]]; then - bitcoin_cli -generate 3 - exit 1 - fi - } - - synced_to_graph() { - is_synced="$(lnd_outside_cli getinfo | jq -r '.synced_to_graph')" - [[ "$is_synced" == "true" ]] || exit 1 - } - - # Open channel from lndoutside1 -> lnd1 - pubkey="$(lnd_cli getinfo | jq -r '.identity_pubkey')" - endpoint="${COMPOSE_PROJECT_NAME}-lnd1-1:9735" - lnd_outside_cli connect "${pubkey}@${endpoint}" || true - retry 10 1 synced_to_graph - lnd_outside_cli openchannel \ - --node_key "$pubkey" \ - --local_amt "$local_amount" - - retry 10 1 mempool_not_empty - retry 10 1 no_pending_channels - - # Open channel with push from lndoutside1 -> lndoutside2 - pubkey="$(lnd_outside_2_cli getinfo | jq -r '.identity_pubkey')" - endpoint="${COMPOSE_PROJECT_NAME}-lnd-outside-2-1:9735" - lnd_outside_cli connect "${pubkey}@${endpoint}" || true - retry 10 1 synced_to_graph - lnd_outside_cli openchannel \ - --node_key "$pubkey" \ - --local_amt "$local_amount" \ - --push_amt "$push_amount" - - retry 10 1 mempool_not_empty - retry 10 1 no_pending_channels - - # FIXME: we may need some check on the graph or something else - # NB: I get randomly a "no route" error otherwise - sleep 10 - - # Fund lnd1 node with push_amount via funding user - login_user \ - "$LND_FUNDING_TOKEN_NAME" \ - "$LND_FUNDING_PHONE" \ - "$CODE" - - fund_wallet_from_lightning \ - "$LND_FUNDING_TOKEN_NAME" \ - "$LND_FUNDING_TOKEN_NAME.btc_wallet_id" \ - "$push_amount" -} - -rebalance_channel() { - lnd_cli_value="$1" - lnd_partner_cli_value="$2" - target_local_balance="$3" - - local_pubkey="$(run_with_lnd $lnd_cli_value getinfo | jq -r '.identity_pubkey')" - remote_pubkey="$(run_with_lnd $lnd_partner_cli_value getinfo | jq -r '.identity_pubkey')" - - partner_channel_filter=' - [ - .channels[]? - | select(.remote_pubkey == $remote_pubkey) - ] | first - ' - - channel=$( - run_with_lnd "$lnd_cli_value" listchannels \ - | jq -r \ - --arg remote_pubkey "$remote_pubkey" \ - "$partner_channel_filter" - ) - [[ "$channel" != "null" ]] - - actual_local_balance=$(echo $channel | jq -r '.local_balance') - diff="$(( $actual_local_balance - $target_local_balance ))" - if [[ "$diff" -gt 0 ]]; then - run_with_lnd "$lnd_cli_value" sendpayment --dest=$remote_pubkey --amt=$diff --keysend - elif [[ "$diff" -lt 0 ]]; then - run_with_lnd "$lnd_partner_cli_value" sendpayment --dest=$local_pubkey --amt="$(abs $diff)" --keysend - fi -} - -lnd_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-lnd1-1" \ - lncli \ - --macaroonpath /root/.lnd/admin.macaroon \ - --tlscertpath /root/.lnd/tls.cert \ - $@ -} - -lnd2_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-lnd2-1" \ - lncli \ - --macaroonpath /root/.lnd/admin.macaroon \ - --tlscertpath /root/.lnd/tls.cert \ - $@ -} - -lnd_outside_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-lnd-outside-1-1" \ - lncli \ - --macaroonpath /root/.lnd/admin.macaroon \ - --tlscertpath /root/.lnd/tls.cert \ - $@ -} - -lnd_outside_rest() { - local route=$1 - local endpoint="https://localhost:8080/$route" - - local data=$2 - - local macaroon_hex=$( - docker exec "${COMPOSE_PROJECT_NAME}-lnd-outside-1-1" \ - xxd -p -c 10000 /root/.lnd/admin.macaroon - ) - - docker exec "${COMPOSE_PROJECT_NAME}-lnd-outside-1-1" \ - curl -s \ - --cacert /root/.lnd/tls.cert \ - -H "Grpc-Metadata-macaroon: $macaroon_hex" \ - ${data:+ -X POST -d $data} \ - "$endpoint" \ - > "$LNDS_REST_LOG" -} - -lnd_outside_2_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-lnd-outside-2-1" \ - lncli \ - --macaroonpath /root/.lnd/admin.macaroon \ - --tlscertpath /root/.lnd/tls.cert \ - $@ -} - -fund_wallet_from_lightning() { - local token_name=$1 - local wallet_id_name=$2 - local amount=$3 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $wallet_id_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] - - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$amount" - - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" -} - -check_for_ln_initiated_status() { - local expected_status=$1 - local token_name=$2 - local payment_hash=$3 - local first=${4:-"2"} - - variables=$( - jq -n \ - --argjson first "$first" \ - '{"first": $first}' - ) - exec_graphql "$token_name" 'transactions' "$variables" - - status="$(get_from_transaction_by_ln_hash_and_status $payment_hash $expected_status '.status')" - [[ "${status}" == "${expected_status}" ]] || return 1 -} - -check_for_ln_initiated_settled() { - check_for_ln_initiated_status "SUCCESS" "$@" -} - -check_for_ln_initiated_pending() { - check_for_ln_initiated_status "PENDING" "$@" -} - - -check_ln_payment_settled() { - local payment_request=$1 - - variables=$( - jq -n \ - --arg payment_request "$payment_request" \ - '{"input": {"paymentRequest": $payment_request}}' - ) - exec_graphql 'anon' 'ln-invoice-payment-status' "$variables" - payment_status="$(graphql_output '.data.lnInvoicePaymentStatus.status')" - [[ "${payment_status}" = "PAID" ]] -} - -get_from_transaction_by_ln_hash_and_status() { - payment_hash="$1" - expected_status="$2" - property_query="$3" - - jq_query=' - .data.me.defaultAccount.transactions.edges[] - | select(.node.initiationVia.paymentHash == $payment_hash) - | select(.node.status == $expected_status) - .node' - echo $output \ - | jq -r \ - --arg payment_hash "$payment_hash" \ - --arg expected_status "$expected_status" \ - "$jq_query" \ - | jq -r "$property_query" \ - | head -n 1 -} - -check_for_ln_update() { - payment_hash=$1 - - retry 10 1 \ - grep "Data.*LnUpdate.*$payment_hash" .e2e-subscriber.log \ - | awk '{print $2}' \ - | jq -r --arg hash "$payment_hash" 'select(.data.myUpdates.update.paymentHash == $hash)' - - paid_status=$( \ - grep 'Data.*LnUpdate' .e2e-subscriber.log \ - | awk '{print $2}' \ - | jq -r --arg hash "$payment_hash" 'select(.data.myUpdates.update.paymentHash == $hash) .data.myUpdates.update.status' - ) - - [[ "$paid_status" == "PAID" ]] || exit 1 -} - -num_txns_for_hash() { - token_name="$1" - payment_hash="$2" - - first=20 - txn_variables=$( - jq -n \ - --argjson first "$first" \ - '{"first": $first}' - ) - exec_graphql "$token_name" 'transactions' "$txn_variables" > /dev/null - - jq_query=' - [ - .data.me.defaultAccount.transactions.edges[] - | select(.node.initiationVia.paymentHash == $payment_hash) - ] - | length - ' - echo $output \ - | jq -r \ - --arg payment_hash "$payment_hash" \ - "$jq_query" -} diff --git a/test/galoy/bats/helpers/onchain.bash b/test/galoy/bats/helpers/onchain.bash deleted file mode 100644 index 1cacb4e1b..000000000 --- a/test/galoy/bats/helpers/onchain.bash +++ /dev/null @@ -1,61 +0,0 @@ -BASH_SOURCE=${BASH_SOURCE:-test/bats/helpers/.} -source $(dirname "$BASH_SOURCE")/_common.bash - -bitcoind_init() { - bitcoin_cli createwallet "outside" || true - bitcoin_cli -generate 200 > /dev/null 2>&1 - - bitcoin_signer_cli createwallet "dev" || true - bitcoin_signer_cli -rpcwallet=dev importdescriptors "$(cat ${REPO_ROOT}/test/bats/bitcoind_signer_descriptors.json)" -} - -bitcoin_signer_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-bitcoind-signer-1" bitcoin-cli $@ -} - -get_from_transaction_by_address() { - property_query=$2 - - jq_query='.data.me.defaultAccount.transactions.edges[] | select(.node.initiationVia.address == $address) .node' - echo $output \ - | jq -r --arg address "$1" "$jq_query" \ - | jq -r "$property_query" -} - -check_for_broadcast() { - local token_name=$1 - local address=$2 - local first=${3:-"1"} - - variables=$( - jq -n \ - --argjson first "$first" \ - '{"first": $first}' - ) - exec_graphql "$token_name" 'transactions' "$variables" - - tx="$(get_from_transaction_by_address "$address" '.')" - [[ -n "${tx}" && "${tx}" != "null" ]] || exit 1 - txid="$(echo $tx | jq -r '.settlementVia.transactionHash')" - [[ "${txid}" != "null" ]] || exit 1 - status="$(echo $tx | jq -r '.status')" - [[ "${status}" == "PENDING" ]] || exit 1 - - bitcoin_cli gettransaction "$txid" || exit 1 -} - -check_for_onchain_initiated_settled() { - local token_name=$1 - local address=$2 - local first=${3:-"1"} - - variables=$( - jq -n \ - --argjson first "$first" \ - '{"first": $first}' - ) - exec_graphql "$token_name" 'transactions' "$variables" - - settled_status="$(get_from_transaction_by_address $address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 -} diff --git a/test/galoy/bats/helpers/setup-and-teardown.bash b/test/galoy/bats/helpers/setup-and-teardown.bash deleted file mode 100644 index 4157d4dac..000000000 --- a/test/galoy/bats/helpers/setup-and-teardown.bash +++ /dev/null @@ -1,281 +0,0 @@ -BASH_SOURCE=${BASH_SOURCE:-test/bats/helpers/.} -source $(dirname "$BASH_SOURCE")/onchain.bash - -SERVER_PID_FILE=$REPO_ROOT/test/bats/.galoy_server_pid -WS_SERVER_PID_FILE=$REPO_ROOT/test/bats/.galoy_ws_server_pid -TRIGGER_PID_FILE=$REPO_ROOT/test/bats/.galoy_trigger_pid -EXPORTER_PID_FILE=$REPO_ROOT/test/bats/.galoy_exporter_pid -SUBSCRIBER_PID_FILE=$REPO_ROOT/test/bats/.gql_subscriber_pid -CALLBACK_PID_FILE=$REPO_ROOT/test/bats/.callback_pid - -METRICS_ENDPOINT="localhost:3000/metrics" - -redis_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-redis-1" redis-cli $@ -} - -reset_redis() { - redis_cli FLUSHALL -} - -mongo_cli() { - docker exec "${COMPOSE_PROJECT_NAME}-mongodb-1" mongosh --quiet mongodb://localhost:27017/galoy --eval $@ -} - -start_server() { - stop_server > /dev/null 2>&1 || true - - background node lib/servers/graphql-main-server.js > .e2e-server.log - echo $! > $SERVER_PID_FILE - - server_is_up() { - exec_graphql 'anon' 'globals' - network="$(graphql_output '.data.globals.network')" - [[ "${network}" = "regtest" ]] || exit 1 - } - - retry 30 1 server_is_up -} - -subscribe_to() { - stop_subscriber > /dev/null 2>&1 || true - - token_name=$1 - if [[ -n "$token_name" && "$token_name" != 'anon' ]]; then - token="$(read_value $token_name)" - fi - gql_filename=$2 - variables=$3 - - background \ - ${REPO_ROOT}/node_modules/.bin/ts-node "${REPO_ROOT}/test/helpers/servers/gql-subscribe.ts" \ - "ws://${GALOY_ENDPOINT}/graphqlws" \ - "$(gql_file $gql_filename)" \ - "$token" \ - "$variables" \ - > .e2e-subscriber.log - echo $! > $SUBSCRIBER_PID_FILE -} - -add_callback() { - local token_name=$1 - - local variables=$( - jq -n \ - --arg url "$SVIX_CALLBACK_URL" \ - '{input: {url: $url}}' - ) - exec_graphql "$token_name" 'callback-endpoint-add' "$variables" -} - -remove_callbacks() { - local token_name=$1 - - exec_graphql "$token_name" 'callback-endpoints-list' - graphql_output '.data.me.defaultAccount.callbackEndpoints[].id' \ - | while read -r id; do - variables=$( - jq -n \ - --arg id "$id" \ - '{input: {id: $id}}' - ) - exec_graphql "$token_name" 'callback-endpoint-delete' "$variables" - done -} - -start_ws_server() { - stop_ws_server > /dev/null 2>&1 || true - - background node lib/servers/ws-server.js > .e2e-ws-server.log - echo $! > $WS_SERVER_PID_FILE -} - -start_trigger() { - stop_trigger > /dev/null 2>&1 || true - - background node lib/servers/trigger.js > .e2e-trigger.log - echo $! > $TRIGGER_PID_FILE -} - -run_cron() { - node lib/servers/cron.js > .e2e-cron.log -} - -start_exporter() { - stop_exporter > /dev/null 2>&1 || true - - background node lib/servers/exporter.js > .e2e-exporter.log - echo $! > $EXPORTER_PID_FILE -} - -start_callback() { - background node test/bats/helpers/callback.js > .e2e-callback.log - echo $! > $CALLBACK_PID_FILE -} - -stop_server() { - [[ -f "$SERVER_PID_FILE" ]] && kill -9 $(cat $SERVER_PID_FILE) > /dev/null || true -} - -stop_ws_server() { - [[ -f "$WS_SERVER_PID_FILE" ]] && kill -9 $(cat $WS_SERVER_PID_FILE) > /dev/null || true -} - -stop_trigger() { - [[ -f "$TRIGGER_PID_FILE" ]] && kill -9 $(cat $TRIGGER_PID_FILE) > /dev/null || true -} - -stop_exporter() { - [[ -f "$EXPORTER_PID_FILE" ]] && kill -9 $(cat $EXPORTER_PID_FILE) > /dev/null || true -} - -stop_subscriber() { - [[ -f "$SUBSCRIBER_PID_FILE" ]] && kill -9 $(cat $SUBSCRIBER_PID_FILE) > /dev/null || true -} - -stop_callback() { - [[ -f "$CALLBACK_PID_FILE" ]] && kill -9 $(cat $CALLBACK_PID_FILE) > /dev/null || true -} - -clear_cache() { - rm -r ${CACHE_DIR} - mkdir -p ${CACHE_DIR} -} - -balance_for_check() { - reset_redis > /dev/null 2>&1 || true - - get_metric() { - metric_name=$1 - - retry 10 1 curl -s "$METRICS_ENDPOINT" - curl -s "$METRICS_ENDPOINT" \ - | awk "/^$metric_name/ { print \$2 }" - } - - lnd_balance_sync=$(get_metric "galoy_lndBalanceSync") - is_number "$lnd_balance_sync" - abs_lnd_balance_sync=$(abs $lnd_balance_sync) - - assets_eq_liabilities=$(get_metric "galoy_assetsEqLiabilities") - is_number "$assets_eq_liabilities" - abs_assets_eq_liabilities=$(abs $assets_eq_liabilities) - - echo $(( $abs_lnd_balance_sync + $abs_assets_eq_liabilities )) -} - -random_phone() { - printf "+1%010d\n" $(( ($RANDOM * 1000000) + ($RANDOM % 1000000) )) -} - -login_user() { - local token_name=$1 - local phone=$2 - local code=$3 - - local variables=$( - jq -n \ - --arg phone "$phone" \ - --arg code "$code" \ - '{input: {phone: $phone, code: $code}}' - ) - exec_graphql 'anon' 'user-login' "$variables" - auth_token="$(graphql_output '.data.userLogin.authToken')" - [[ -n "${auth_token}" && "${auth_token}" != "null" ]] - cache_value "$token_name" "$auth_token" - - exec_graphql "$token_name" 'wallets-for-account' - - btc_wallet_id="$(graphql_output '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "BTC") .id')" - [[ "${btc_wallet_id}" != "null" ]] - cache_value "$token_name.btc_wallet_id" "$btc_wallet_id" - - usd_wallet_id="$(graphql_output '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "USD") .id')" - [[ "${usd_wallet_id}" != "null" ]] - cache_value "$token_name.usd_wallet_id" "$usd_wallet_id" -} - -fund_wallet_from_onchain() { - local token_name=$1 - local wallet_id_name="$2" - local amount=$3 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $wallet_id_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${address}" != "null" ]] - - bitcoin_cli sendtoaddress "$address" "$amount" - bitcoin_cli -generate 2 - retry 30 1 check_for_onchain_initiated_settled "$token_name" "$address" -} - -fund_wallet_intraledger() { - local from_token_name=$1 - local from_wallet_name=$2 - local wallet_name=$3 - local amount=$4 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $wallet_name)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] -} - -initialize_user_from_onchain() { - local token_name="$1" - local phone="$2" - local code="$3" - - local btc_amount_in_btc=${4:-"0.01"} - local usd_amount_in_sats=${5:-"200000"} - - login_user "$token_name" "$phone" "$code" - - fund_wallet_from_onchain \ - "$token_name" \ - "$token_name.btc_wallet_id" \ - "$btc_amount_in_btc" - - fund_wallet_intraledger \ - "$token_name" \ - "$token_name.btc_wallet_id" \ - "$token_name.usd_wallet_id" \ - "$usd_amount_in_sats" -} - -balance_for_wallet() { - local token_name="$1" - local wallet="$2" - - exec_graphql "$token_name" 'wallets-for-account' > /dev/null - if [[ "$wallet" == "USD" ]]; then - graphql_output '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "USD") .balance' - else - graphql_output '.data.me.defaultAccount.wallets[] | select(.walletCurrency == "BTC") .balance' - fi -} - -user_update_username() { - local token_name="$1" - - local variables=$( - jq -n \ - --arg username "$token_name" \ - '{input: {username: $username}}' - ) - exec_graphql "$token_name" 'user-update-username' "$variables" - num_errors="$(graphql_output '.data.userUpdateUsername.errors | length')" - username="$(graphql_output '.data.userUpdateUsername.user.username')" - [[ "$num_errors" == "0" || "$username" == "$token_name" ]] || exit 1 -} diff --git a/test/galoy/bats/intraledger-send.bats b/test/galoy/bats/intraledger-send.bats deleted file mode 100644 index f43e9ff68..000000000 --- a/test/galoy/bats/intraledger-send.bats +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - clear_cache - reset_redis - - bitcoind_init - start_trigger - start_exporter - start_server - - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - user_update_username "$ALICE_TOKEN_NAME" - login_user "$BOB_TOKEN_NAME" "$BOB_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server - stop_exporter -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -@test "intraledger-send: settle intraledger, from BTC wallet, with contacts check" { - local from_token_name="$ALICE_TOKEN_NAME" - local from_wallet_name="$from_token_name.btc_wallet_id" - - local to_token_name="user_$RANDOM" - to_phone="$(random_phone)" - login_user \ - "$to_token_name" \ - "$to_phone" \ - "$CODE" - user_update_username "$to_token_name" - local wallet_name_btc="$to_token_name.btc_wallet_id" - local wallet_name_usd="$to_token_name.usd_wallet_id" - local amount=1000 - - # Check is not contact before send - run is_contact "$from_token_name" "$to_token_name" - [[ "$status" -ne "0" ]] || exit 1 - run is_contact "$to_token_name" "$from_token_name" - [[ "$status" -ne "0" ]] || exit 1 - - # To btc wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $wallet_name_btc)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] - - # To usd wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $wallet_name_usd)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] - - # Check is contact after sends - run is_contact "$from_token_name" "$to_token_name" - [[ "$status" == "0" ]] || exit 1 - run is_contact "$to_token_name" "$from_token_name" - [[ "$status" == "0" ]] || exit 1 - -} - -@test "intraledger-send: settle intraledger, from USD wallet" { - local from_token_name="$ALICE_TOKEN_NAME" - local from_wallet_name="$from_token_name.usd_wallet_id" - - local to_token_name="$BOB_TOKEN_NAME" - local wallet_name_btc="$to_token_name.btc_wallet_id" - local wallet_name_usd="$to_token_name.usd_wallet_id" - local amount=20 - - # To btc wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $wallet_name_btc)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] - - # To usd wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $wallet_name_usd)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] -} - -@test "intraledger-send: settle trade intra-account, from BTC wallet" { - local from_token_name="$ALICE_TOKEN_NAME" - local from_wallet_name="$from_token_name.btc_wallet_id" - local to_token_name="$from_token_name" - local to_wallet_name="$to_token_name.usd_wallet_id" - - local amount=1000 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $to_wallet_name)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] -} - -@test "intraledger-send: settle trade intra-account, from USD wallet" { - local from_token_name="$ALICE_TOKEN_NAME" - local from_wallet_name="$from_token_name.usd_wallet_id" - local to_token_name="$from_token_name" - local to_wallet_name="$to_token_name.btc_wallet_id" - - local amount=20 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $from_wallet_name)" \ - --arg recipient_wallet_id "$(read_value $to_wallet_name)" \ - --arg amount "$amount" \ - '{input: {walletId: $wallet_id, recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql "$from_token_name" 'intraledger-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.intraLedgerUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] -} diff --git a/test/galoy/bats/ln-receive.bats b/test/galoy/bats/ln-receive.bats deleted file mode 100644 index b5e7f1cd2..000000000 --- a/test/galoy/bats/ln-receive.bats +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/ln" - -setup_file() { - clear_cache - - bitcoind_init - start_trigger - start_ws_server - start_server - start_exporter - start_callback - - lnds_init - - login_user "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - add_callback "$ALICE_TOKEN_NAME" - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - - subscribe_to "$ALICE_TOKEN_NAME" my-updates-sub - sleep 3 -} - -teardown_file() { - remove_callbacks "$ALICE_TOKEN_NAME" - - stop_trigger - stop_server - stop_ws_server - stop_exporter - stop_subscriber - stop_callback -} - -setup() { - reset_redis -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi - -} - -btc_amount=1000 -usd_amount=50 - -@test "ln-receive: settle via ln for BTC wallet, invoice with amount" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Check callback events before - exec_graphql "$token_name" 'account-details' - account_id="$(graphql_output '.data.me.defaultAccount.id')" - [[ "$account_id" != "null" ]] || exit 1 - - num_callback_events_before=$(cat .e2e-callback.log | grep "$account_id" | wc -l) - - # Generate invoice - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg amount "$btc_amount" \ - '{input: {walletId: $wallet_id, amount: $amount}}' - ) - exec_graphql "$token_name" 'ln-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - # Check for subscriber event - check_for_ln_update "$payment_hash" || exit 1 - - # Check for callback - num_callback_events_after=$(cat .e2e-callback.log | grep "$account_id" | wc -l) - [[ "$num_callback_events_after" -gt "$num_callback_events_before" ]] || exit 1 -} - -@test "ln-receive: settle via ln for USD wallet, invoice with amount" { - # Generate invoice - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg amount "$usd_amount" \ - '{input: {walletId: $wallet_id, amount: $amount}}' - ) - exec_graphql "$token_name" 'ln-usd-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnUsdInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - # Check for subscriber event - check_for_ln_update "$payment_hash" || exit 1 -} - -@test "ln-receive: settle via ln for BTC wallet, amountless invoice" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Generate invoice - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - # Check for subscriber event - check_for_ln_update "$payment_hash" || exit 1 -} - -@test "ln-receive: handle less-than-1-sat ln payment for BTC wallet" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Generate amountless invoice - invoice_variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$invoice_variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Check that invoice is retrievable from lnd1 - invoice_from_lnd=$(lnd_cli lookupinvoice "$payment_hash") - [[ -n $invoice_from_lnd ]] || exit 1 - - # Receive less-than-1-sat payment - pay_variables=$( - jq -n \ - --arg payment_request "$payment_request" \ - --arg amt_msat "995" \ - --arg timeout_seconds "5" \ - '{payment_request: $payment_request, amt_msat: $amt_msat, timeout_seconds: $timeout_seconds}'\ - | tr -d '[:space:]') - lnd_outside_rest "v2/router/send" "$pay_variables" - - # Check that payment fails - response=$(tail -n 1 "$LNDS_REST_LOG") - [[ -n $response ]] || exit 1 - pay_status=$(echo $response | jq -r '.result.status') - [[ "$pay_status" == "FAILED" ]] || exit 1 - failure_reason=$(echo $response | jq -r '.result.failure_reason') - [[ "$failure_reason" == "FAILURE_REASON_INCORRECT_PAYMENT_DETAILS" ]] || exit 1 - - # Check that invoice is removed from lnd1 - invoice_from_lnd=$(lnd_cli lookupinvoice "$payment_hash") || true - [[ -z $invoice_from_lnd ]] || exit 1 -} - -@test "ln-receive: settle via ln for USD wallet, amountless invoice" { - # Generate invoice - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - # Check for subscriber event - check_for_ln_update "$payment_hash" || exit 1 -} - -@test "ln-receive: settles invoices created while trigger down" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Stop trigger - stop_trigger - - # Generate invoice - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Start trigger - start_trigger - sleep 5 - - # Pay invoice & check for settled - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" - - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" -} - -@test "ln-receive: settles invoices created & paid while trigger down" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - # Stop trigger - stop_trigger - - # Generate invoice - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - # Pay invoice - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" \ - & - - # Start trigger - start_trigger - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" -} diff --git a/test/galoy/bats/ln-send.bats b/test/galoy/bats/ln-send.bats deleted file mode 100644 index fa6aafe62..000000000 --- a/test/galoy/bats/ln-send.bats +++ /dev/null @@ -1,741 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/ln" - -setup_file() { - clear_cache - - bitcoind_init - start_trigger - start_server - start_exporter - - lnds_init - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - user_update_username "$ALICE_TOKEN_NAME" - initialize_user_from_onchain "$BOB_TOKEN_NAME" "$BOB_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server - stop_exporter -} - -setup() { - reset_redis -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -amount_sent_for_ln_txn_by_hash() { - token_name="$1" - payment_hash="$2" - - first=20 - txn_variables=$( - jq -n \ - --argjson first "$first" \ - '{"first": $first}' - ) - exec_graphql "$token_name" 'transactions' "$txn_variables" > /dev/null - - jq_query=' - [ - .data.me.defaultAccount.transactions.edges[] - | select(.node.initiationVia.paymentHash == $payment_hash) - | select(.node.direction == "SEND") - ] - | first .node.settlementAmount - ' - local amount=$(echo $output \ - | jq -r \ - --arg payment_hash "$payment_hash" \ - "$jq_query" - ) - abs $amount -} - -btc_amount=1000 -usd_amount=50 - -@test "ln-send: lightning settled - lnInvoicePaymentSend from btc" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice --amt $btc_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-invoice-fee-probe' "$variables" - fee_amount="$(graphql_output '.data.lnInvoiceFeeProbe.amount')" - [[ "${fee_amount}" = "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnInvoicePaymentSend from btc, no fee probe" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice --amt $btc_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnInvoicePaymentSend from usd" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice --amt $btc_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-usd-invoice-fee-probe' "$variables" - fee_amount="$(graphql_output '.data.lnUsdInvoiceFeeProbe.amount')" - [[ "${fee_amount}" = "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnInvoicePaymentSend from usd, no fee probe" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice --amt $btc_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnNoAmountInvoicePaymentSend" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $btc_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-invoice-fee-probe' "$variables" - fee_amount="$(graphql_output '.data.lnNoAmountInvoiceFeeProbe.amount')" - [[ "${fee_amount}" = "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-no-amount-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnNoAmountInvoicePaymentSend, no fee probe" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $btc_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "$btc_amount" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnNoAmountUsdInvoicePaymentSend" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - initial_balance="$(balance_for_wallet $token_name 'USD')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $usd_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-usd-invoice-fee-probe' "$variables" - fee_amount="$(graphql_output '.data.lnNoAmountUsdInvoiceFeeProbe.amount')" - [[ "${fee_amount}" = "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-no-amount-usd-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountUsdInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_balance="$(balance_for_wallet $token_name 'USD')" - wallet_diff="$(( $initial_balance - $final_balance ))" - [[ "$wallet_diff" == "$usd_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" -gt "0" ]] || exit 1 -} - -@test "ln-send: lightning settled - lnNoAmountUsdInvoicePaymentSend, no fee probe" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - initial_balance="$(balance_for_wallet $token_name 'USD')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - invoice_response="$(lnd_outside_cli addinvoice)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $usd_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-usd-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountUsdInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - - final_balance="$(balance_for_wallet $token_name 'USD')" - wallet_diff="$(( $initial_balance - $final_balance ))" - [[ "$wallet_diff" == "$usd_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" -gt "0" ]] || exit 1 -} - -@test "ln-send: intraledger settled - lnInvoicePaymentSend from btc to btc, with contacts check" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - recipient_token_name="user_$RANDOM" - recipient_phone="$(random_phone)" - login_user \ - "$recipient_token_name" \ - "$recipient_phone" \ - "$CODE" - user_update_username "$recipient_token_name" - btc_recipient_wallet_name="$recipient_token_name.btc_wallet_id" - - initial_balance="$(balance_for_wallet $token_name 'BTC')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - # Check is not contact before send - run is_contact "$token_name" "$recipient_token_name" - [[ "$status" -ne "0" ]] || exit 1 - run is_contact "$recipient_token_name" "$token_name" - [[ "$status" -ne "0" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - --arg amount "$btc_amount" \ - '{input: {walletId: $wallet_id, amount: $amount}}' - ) - exec_graphql "$recipient_token_name" 'ln-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - check_for_ln_initiated_settled "$recipient_token_name" "$payment_hash" - - final_balance="$(balance_for_wallet $token_name 'BTC')" - wallet_diff="$(( $initial_balance - $final_balance ))" - [[ "$wallet_diff" == "$btc_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "0" ]] || exit 1 - - # Check is contact after send - run is_contact "$token_name" "$recipient_token_name" - [[ "$status" == "0" ]] || exit 1 - run is_contact "$recipient_token_name" "$token_name" - [[ "$status" == "0" ]] || exit 1 -} - -@test "ln-send: intraledger settled - lnInvoicePaymentSend from usd to btc" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - recipient_token_name="$BOB_TOKEN_NAME" - btc_recipient_wallet_name="$recipient_token_name.btc_wallet_id" - - initial_recipient_balance="$(balance_for_wallet $recipient_token_name 'BTC')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - --arg amount "$btc_amount" \ - '{input: {walletId: $wallet_id, amount: $amount}}' - ) - exec_graphql "$recipient_token_name" 'ln-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - - exec_graphql "$token_name" 'ln-usd-invoice-fee-probe' "$variables" - fee_amount="$(graphql_output '.data.lnUsdInvoiceFeeProbe.amount')" - [[ "${fee_amount}" = "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - check_for_ln_initiated_settled "$recipient_token_name" "$payment_hash" - - final_recipient_balance="$(balance_for_wallet $recipient_token_name 'BTC')" - recipient_wallet_diff="$(( $final_recipient_balance - $initial_recipient_balance ))" - [[ "$recipient_wallet_diff" == "$btc_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "0" ]] || exit 1 -} - -@test "ln-send: intraledger settled - lnNoAmountInvoicePaymentSend from btc to usd" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - recipient_token_name="$BOB_TOKEN_NAME" - usd_recipient_wallet_name="$recipient_token_name.usd_wallet_id" - - initial_balance="$(balance_for_wallet $token_name 'BTC')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_recipient_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$recipient_token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $btc_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - check_for_ln_initiated_settled "$recipient_token_name" "$payment_hash" - - final_balance="$(balance_for_wallet $token_name 'BTC')" - wallet_diff="$(( $initial_balance - $final_balance ))" - [[ "$wallet_diff" == "$btc_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "0" ]] || exit 1 -} - -@test "ln-send: intraledger settled - lnNoAmountUsdInvoicePaymentSend from usd to usd" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - recipient_token_name="$BOB_TOKEN_NAME" - usd_recipient_wallet_name="$recipient_token_name.usd_wallet_id" - - initial_balance="$(balance_for_wallet $token_name 'USD')" - initial_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_recipient_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$recipient_token_name" 'ln-no-amount-invoice-create' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreate.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - payment_hash="$(echo $invoice | jq -r '.paymentHash')" - [[ "${payment_hash}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - --arg amount $usd_amount \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request, amount: $amount}}' - ) - - exec_graphql "$token_name" 'ln-no-amount-usd-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnNoAmountUsdInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for settled - retry 15 1 check_for_ln_initiated_settled "$token_name" "$payment_hash" - check_for_ln_initiated_settled "$recipient_token_name" "$payment_hash" - - final_balance="$(balance_for_wallet $token_name 'USD')" - wallet_diff="$(( $initial_balance - $final_balance ))" - [[ "$wallet_diff" == "$usd_amount" ]] || exit 1 - - final_lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance') - lnd1_diff="$(( $initial_lnd1_balance - $final_lnd1_balance ))" - [[ "$lnd1_diff" == "0" ]] || exit 1 -} - -@test "ln-send: ln settled - settle failed and then successful payment" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - threshold_amount=150000 - invoice_response="$(lnd_outside_2_cli addinvoice --amt $threshold_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - payment_hash=$(echo $invoice_response | jq -r '.r_hash') - [[ "${payment_request}" != "null" ]] || exit 1 - - check_num_txns() { - expected_num="$1" - - num_txns="$(num_txns_for_hash "$token_name" "$payment_hash")" - [[ "$num_txns" == "$expected_num" ]] || exit 1 - } - - # Rebalance last hop so payment will fail - rebalance_channel lnd_outside_cli lnd_outside_2_cli "$(( $threshold_amount - 1 ))" - - # Try payment and check for fail - initial_balance="$(balance_for_wallet $token_name 'BTC')" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - error_msg="$(graphql_output '.data.lnInvoicePaymentSend.errors[0].message')" - [[ "${send_status}" = "FAILURE" ]] || exit 1 - [[ "${error_msg}" == "Unable to find a route for payment." ]] || exit 1 - - # Check for txns - retry 15 1 check_num_txns "2" - balance_after_fail="$(balance_for_wallet $token_name 'BTC')" - [[ "$initial_balance" == "$balance_after_fail" ]] || exit 1 - - # Rebalance last hop so same payment will succeed - rebalance_channel lnd_outside_cli lnd_outside_2_cli "$(( $threshold_amount * 2 ))" - lnd_cli resetmc - - # Retry payment and check for success - exec_graphql "$token_name" 'ln-invoice-fee-probe' "$variables" - num_errors="$(graphql_output '.data.lnInvoiceFeeProbe.errors | length')" - fee_amount="$(graphql_output '.data.lnInvoiceFeeProbe.amount')" - [[ "$num_errors" == "0" ]] || exit 1 - [[ "${fee_amount}" -gt "0" ]] || exit 1 - - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # Check for txns - retry 15 1 check_num_txns "3" - balance_after_success="$(balance_for_wallet $token_name 'BTC')" - [[ "$balance_after_success" -lt "$initial_balance" ]] || exit 1 -} - -@test "ln-send: ln settled - settle failed and then pending-to-failed payment" { - skip "missing xxd dep, failing on concourse" - - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - threshold_amount=150000 - secret=$(xxd -l 32 -p /dev/urandom) - payment_hash=$(echo -n $secret | xxd -r -p | sha256sum | cut -d ' ' -f1) - invoice_response="$(lnd_outside_2_cli addholdinvoice $payment_hash --amt $threshold_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - [[ "${payment_request}" != "null" ]] || exit 1 - - check_num_txns() { - expected_num="$1" - - num_txns="$(num_txns_for_hash "$token_name" "$payment_hash")" - [[ "$num_txns" == "$expected_num" ]] || exit 1 - } - - # Rebalance last hop so payment will fail - rebalance_channel lnd_outside_cli lnd_outside_2_cli "$(( $threshold_amount - 1 ))" - - # Try payment and check for fail - initial_balance="$(balance_for_wallet $token_name 'BTC')" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - error_msg="$(graphql_output '.data.lnInvoicePaymentSend.errors[0].message')" - [[ "${send_status}" = "FAILURE" ]] || exit 1 - [[ "${error_msg}" == "Unable to find a route for payment." ]] || exit 1 - - # Check for txns - retry 15 1 check_num_txns "2" - balance_after_fail="$(balance_for_wallet $token_name 'BTC')" - [[ "$initial_balance" == "$balance_after_fail" ]] || exit 1 - - # Rebalance last hop so same payment will succeed - rebalance_channel lnd_outside_cli lnd_outside_2_cli "$(( $threshold_amount * 2 ))" - lnd_cli resetmc - - # Retry payment and check for pending - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "PENDING" ]] || exit 1 - - # Check for txns - retry 15 1 check_num_txns "3" - check_for_ln_initiated_pending "$token_name" "$payment_hash" "10" \ - || exit 1 - balance_while_pending="$(balance_for_wallet $token_name 'BTC')" - [[ "$balance_while_pending" -lt "$initial_balance" ]] || exit 1 - - # Cancel hodl invoice - lnd_outside_2_cli cancelinvoice "$payment_hash" - - retry 15 1 check_num_txns "4" - balance_after_pending_failed="$(balance_for_wallet $token_name 'BTC')" - [[ "$balance_after_pending_failed" == "$initial_balance" ]] || exit 1 - - run check_for_ln_initiated_pending "$token_name" "$payment_hash" "10" - [[ "$status" -ne 0 ]] || exit 1 -} - -@test "ln-send: ln settled - pending-to-failed usd payment" { - skip "missing xxd dep, failing on concourse" - - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - threshold_amount=150000 - secret=$(xxd -l 32 -p /dev/urandom) - payment_hash=$(echo -n $secret | xxd -r -p | sha256sum | cut -d ' ' -f1) - invoice_response="$(lnd_outside_2_cli addholdinvoice $payment_hash --amt $threshold_amount)" - payment_request="$(echo $invoice_response | jq -r '.payment_request')" - [[ "${payment_request}" != "null" ]] || exit 1 - - check_num_txns() { - expected_num="$1" - - num_txns="$(num_txns_for_hash "$token_name" "$payment_hash")" - [[ "$num_txns" == "$expected_num" ]] || exit 1 - } - - initial_btc_balance="$(balance_for_wallet $token_name 'BTC')" - initial_usd_balance="$(balance_for_wallet $token_name 'USD')" - - # Rebalance last hop so payment will succeed - rebalance_channel lnd_outside_cli lnd_outside_2_cli "$(( $threshold_amount * 2 ))" - lnd_cli resetmc - - # Try payment and check for pending - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg payment_request "$payment_request" \ - '{input: {walletId: $wallet_id, paymentRequest: $payment_request}}' - ) - exec_graphql "$token_name" 'ln-invoice-payment-send' "$variables" - send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')" - [[ "${send_status}" = "PENDING" ]] || exit 1 - - # Check for txns - retry 15 1 check_num_txns "1" - check_for_ln_initiated_pending "$token_name" "$payment_hash" "10" \ - || exit 1 - btc_balance_while_pending="$(balance_for_wallet $token_name 'BTC')" - usd_balance_while_pending="$(balance_for_wallet $token_name 'USD')" - [[ "$btc_balance_while_pending" == "$initial_btc_balance" ]] || exit 1 - [[ "$usd_balance_while_pending" -lt "$initial_usd_balance" ]] || exit 1 - - # Cancel hodl invoice - lnd_outside_2_cli cancelinvoice "$payment_hash" - - retry 15 1 check_num_txns "2" - btc_balance_after_pending_failed="$(balance_for_wallet $token_name 'BTC')" - usd_balance_after_pending_failed="$(balance_for_wallet $token_name 'USD')" - [[ "$btc_balance_after_pending_failed" -gt "$btc_balance_while_pending" ]] || exit 1 - [[ "$usd_balance_after_pending_failed" == "$usd_balance_while_pending" ]] || exit 1 - - run check_for_ln_initiated_pending "$token_name" "$payment_hash" "10" - [[ "$status" -ne 0 ]] || exit 1 -} diff --git a/test/galoy/bats/notification-settings.bats b/test/galoy/bats/notification-settings.bats deleted file mode 100644 index 3496ff27e..000000000 --- a/test/galoy/bats/notification-settings.bats +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - clear_cache - reset_redis - - bitcoind_init - start_trigger - start_server - - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server -} - - -@test "notification-settings: disable/enable notification channel" { - token_name="$ALICE_TOKEN_NAME" - - variables=$( - jq -n \ - '{input: { channel: "PUSH" }}') - - exec_graphql "$token_name" 'account-disable-notification-channel' "$variables" - channel_enabled="$(graphql_output '.data.accountDisableNotificationChannel.account.notificationSettings.push.enabled')" - [[ "$channel_enabled" == "false" ]] || exit 1 - - exec_graphql "$token_name" 'account-enable-notification-channel' "$variables" - - channel_enabled="$(graphql_output '.data.accountEnableNotificationChannel.account.notificationSettings.push.enabled')" - [[ "$channel_enabled" == "true" ]] || exit 1 -} - -@test "notification-settings: disable/enable notification category" { - token_name="$ALICE_TOKEN_NAME" - - variables=$( - jq -n \ - '{input: { channel: "PUSH", category: "Circles" }}') - - exec_graphql "$token_name" 'account-disable-notification-category' "$variables" - disabled_category="$(graphql_output '.data.accountDisableNotificationCategory.account.notificationSettings.push.disabledCategories[0]')" - [[ "$disabled_category" == "Circles" ]] || exit 1 - - exec_graphql "$token_name" 'account-enable-notification-category' "$variables" - - disabled_length="$(graphql_output '.data.accountEnableNotificationCategory.account.notificationSettings.push.disabledCategories | length')" - [[ "$disabled_length" == "0" ]] || exit 1 -} diff --git a/test/galoy/bats/oathkeeper.bats b/test/galoy/bats/oathkeeper.bats deleted file mode 100644 index 54ecfab0d..000000000 --- a/test/galoy/bats/oathkeeper.bats +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - start_server -} - -teardown_file() { - stop_server -} - -OATHKEEPER_ENDPOINT=${OATHKEEPER_ENDPOINT:-"http://127.0.0.1:4456/decisions/"} - -check_is_uuid() { - uuid_string=$1 - - uuid_regex='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' - echo $uuid_string | grep -Eq "$uuid_regex" -} - -decode_jwt() { - local jwt="$1" - local part="$2" - - # Split JWT into parts - local header="$(echo "$jwt" | cut -d "." -f 1)" - local payload="$(echo "$jwt" | cut -d "." -f 2)" - local signature="$(echo "$jwt" | cut -d "." -f 3)" - - # Helper to decode Base64Url - decode_base64url() { - local data="$1" - local len=${#data} - - # Add padding - local mod=$((len % 4)) - if [[ $mod -eq 2 ]]; then data="${data}=="; fi - if [[ $mod -eq 3 ]]; then data="${data}="; fi - - # Translate base64url to base64 - local url_decoded="$(echo "$data" | tr '_-' '/+')" - - echo "$url_decoded" | base64 --decode - } - - case "$part" in - header) - decode_base64url "$header" - ;; - payload) - decode_base64url "$payload" - ;; - signature) - # Not decoding signature, just showing it - echo "$signature" - ;; - *) - echo "Invalid part. Choose header, payload, or signature." - ;; - esac -} - -exec_oathkeeper() { - local token_name=$1 - - if [[ ${token_name} == "anon" ]]; then - AUTH_HEADER="" - else - AUTH_HEADER="Authorization: Bearer $(read_value ${token_name})" - fi - - if [[ "${BATS_TEST_DIRNAME}" != "" ]]; then - run_cmd="run" - else - run_cmd="" - fi - - ${run_cmd} curl -s -I \ - -X POST \ - ${AUTH_HEADER:+ -H "$AUTH_HEADER"} \ - -H "Content-Type: application/json" \ - "${OATHKEEPER_ENDPOINT}graphql" - - echo "GQL output: '$output'" -} - -oathkeeper_jwt() { - echo $output \ - | grep -o 'Authorization: Bearer [^ ]*' \ - | awk '{print $3}' -} - -@test "oathkeeper: returns anon if no bearer assets" { - exec_oathkeeper 'anon' - jwt=$(oathkeeper_jwt) - cache_value 'anon.oath' $jwt - - subject=$(decode_jwt "$jwt" 'payload' | jq -r '.sub') - [[ "$subject" == "anon" ]] || exit 1 -} - -@test "oathkeeper: error for an invalid token" { - invalid_jwt="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" - cache_value 'invalid.oath' $jwt - - exec_oathkeeper 'invalid.oath' - echo $output | grep 'HTTP/1.1 401 Unauthorized' || exit 1 -} - -@test "oathkeeper: return userId if bearer assets is present" { - login_user "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - - exec_oathkeeper 'alice' - jwt=$(oathkeeper_jwt) - cache_value 'alice.oath' $jwt - - subject=$(decode_jwt "$jwt" 'payload' | jq -r '.sub') - check_is_uuid "$subject" || exit 1 -} diff --git a/test/galoy/bats/onchain-receive.bats b/test/galoy/bats/onchain-receive.bats deleted file mode 100644 index 55660d559..000000000 --- a/test/galoy/bats/onchain-receive.bats +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/onchain" -load "helpers/ln" - -setup_file() { - clear_cache - reset_redis - - bitcoind_init - start_trigger - start_exporter - start_server - - login_user "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - login_user "$BOB_TOKEN_NAME" "$BOB_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server - stop_exporter -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -create_new_lnd_onchain_address() { - local wallet_name=$1 - local wallet_id=$(read_value $wallet_name) - - insert_lnd1_address() { - local wallet_id=$1 - local address=$2 - pubkey=$(lnd_cli getinfo | jq -r '.identity_pubkey') - - mongo_command=$(echo "db.wallets.updateOne( - { id: \"$wallet_id\" }, - { - \$push: { - onchain: { - address: \"$address\", - pubkey: \"$pubkey\" - } - } - } - );" | tr -d '[:space:]') - - mongo_cli "$mongo_command" - } - - address=$(lnd_cli newaddress p2wkh | jq -r '.address') - insert_lnd1_address "$wallet_id" "$address" > /dev/null - - echo $address -} - -@test "onchain-receive: btc wallet, can create new address if current one is unused" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - - # Create address - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_address_created="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_address_created}" != "null" ]] || exit 1 - - # Fetch current address - exec_graphql "$token_name" 'on-chain-address-current' "$variables" - on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${on_chain_address_current}" != "null" ]] || exit 1 - [[ "${on_chain_address_created}" == "${on_chain_address_current}" ]] || exit 1 - - # Create new address - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - retry_on_chain_address_created="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${retry_on_chain_address_created}" != "null" ]] || exit 1 - [[ "${on_chain_address_created}" != "${retry_on_chain_address_created}" ]] || exit 1 - - # Fetch new current address - exec_graphql "$token_name" 'on-chain-address-current' "$variables" - retry_on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${retry_on_chain_address_current}" != "null" ]] || exit 1 - [[ "${retry_on_chain_address_created}" == "${retry_on_chain_address_current}" ]] || exit 1 -} - -@test "onchain-receive: settle onchain for BTC wallet, query by address" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - amount="0.01" - - # Create address and broadcast transaction 1 - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_address_created_1="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_address_created_1}" != "null" ]] || exit 1 - - bitcoin_cli sendtoaddress "$on_chain_address_created_1" "$amount" - retry 15 1 check_for_broadcast "$token_name" "$on_chain_address_created_1" 1 - - # Create address and broadcast transaction 2 - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_address_created_2="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_address_created_2}" != "null" ]] || exit 1 - - bitcoin_cli sendtoaddress "$on_chain_address_created_2" "$amount" - retry 15 1 check_for_broadcast "$token_name" "$on_chain_address_created_2" 1 - - # Mine transactions - bitcoin_cli -generate 2 - retry 15 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_address_created_1" 2 - retry 3 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_address_created_2" 2 - - # Check transactions for address 1 - address_1_variables=$( - jq -n \ - --argjson first "10" \ - --arg address "$on_chain_address_created_1" \ - '{"first": $first, "address": $address}' - ) - exec_graphql "$token_name" 'transactions-by-address' "$address_1_variables" - txns_for_address_1=$( - graphql_output ' - .data.me.defaultAccount.wallets[] - | select(.__typename == "BTCWallet") - .transactionsByAddress.edges' - ) - txns_for_address_1_length="$(echo $txns_for_address_1 | jq -r 'length')" - [[ "$txns_for_address_1_length" == "1" ]] || exit 1 - address_1_from_txns="$(echo $txns_for_address_1 | jq -r '.[0].node.initiationVia.address')" - [[ "$address_1_from_txns" == "$on_chain_address_created_1" ]] - - # Check transactions for address 2 - address_2_variables=$( - jq -n \ - --argjson first "10" \ - --arg address "$on_chain_address_created_2" \ - '{"first": $first, "address": $address}' - ) - exec_graphql "$token_name" 'transactions-by-address' "$address_2_variables" - txns_for_address_2=$( - graphql_output ' - .data.me.defaultAccount.wallets[] - | select(.__typename == "BTCWallet") - .transactionsByAddress.edges' - ) - txns_for_address_2_length="$(echo $txns_for_address_2 | jq -r 'length')" - [[ "$txns_for_address_2_length" == "1" ]] || exit 1 - address_2_from_txns="$(echo $txns_for_address_2 | jq -r '.[0].node.initiationVia.address')" - [[ "$address_2_from_txns" == "$on_chain_address_created_2" ]] -} - -@test "onchain-receive: usd wallet, can create new address if current one is unused" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - - # Create address - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_address_created="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_address_created}" != "null" ]] || exit 1 - - # Fetch current address - exec_graphql "$token_name" 'on-chain-address-current' "$variables" - on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${on_chain_address_current}" != "null" ]] || exit 1 - [[ "${on_chain_address_created}" == "${on_chain_address_current}" ]] || exit 1 - - # Create new address - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - retry_on_chain_address_created="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${retry_on_chain_address_created}" != "null" ]] || exit 1 - [[ "${on_chain_address_created}" != "${retry_on_chain_address_created}" ]] || exit 1 - - # Fetch new current address - exec_graphql "$token_name" 'on-chain-address-current' "$variables" - retry_on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${retry_on_chain_address_current}" != "null" ]] || exit 1 - [[ "${retry_on_chain_address_created}" == "${retry_on_chain_address_current}" ]] || exit 1 -} - -@test "onchain-receive: settle onchain for USD wallet" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - amount="0.01" - - # Create address - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_address_created="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_address_created}" != "null" ]] || exit 1 - - # Execute onchain send and check for transaction - bitcoin_cli sendtoaddress "$on_chain_address_created" "$amount" - retry 15 1 check_for_broadcast "$token_name" "$on_chain_address_created" 1 - bitcoin_cli -generate 2 - retry 15 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_address_created" 1 -} - -@test "onchain-receive: process received batch transaction" { - alice_token_name="$ALICE_TOKEN_NAME" - alice_btc_wallet_name="$alice_token_name.btc_wallet_id" - bob_token_name="$BOB_TOKEN_NAME" - bob_usd_wallet_name="$bob_token_name.usd_wallet_id" - amount="0.01" - - # Create Alice addresses - alice_variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - - exec_graphql "$alice_token_name" 'on-chain-address-create' "$alice_variables" - alice_address_1="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${alice_address_1}" != "null" ]] || exit 1 - - exec_graphql "$alice_token_name" 'on-chain-address-create' "$alice_variables" - alice_address_2="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${alice_address_2}" != "null" ]] || exit 1 - - # Create Bob addresses - bob_variables=$( - jq -n \ - --arg wallet_id "$(read_value $bob_usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - - exec_graphql "$bob_token_name" 'on-chain-address-create' "$bob_variables" - bob_address_1="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${bob_address_1}" != "null" ]] || exit 1 - - # Create psbt & broadcast transaction - psbt_outputs=$( - jq -c -n \ - --arg alice_address_1 "$alice_address_1" \ - --arg alice_address_2 "$alice_address_2" \ - --arg bob_address_1 "$bob_address_1" \ - --argjson amount "$amount" \ - '{ - ($alice_address_1): $amount, - ($alice_address_2): $amount, - ($bob_address_1): $amount - }' - ) - unsigned_psbt=$(bitcoin_cli walletcreatefundedpsbt '[]' $psbt_outputs | jq -r '.psbt') - signed_psbt=$(bitcoin_cli walletprocesspsbt "$unsigned_psbt" | jq -r '.psbt') - tx_hex=$(bitcoin_cli finalizepsbt "$signed_psbt" | jq -r '.hex') - txid=$(bitcoin_cli sendrawtransaction "$tx_hex") - - retry 15 1 check_for_broadcast "$alice_token_name" "$alice_address_1" 2 - retry 3 1 check_for_broadcast "$alice_token_name" "$alice_address_2" 2 - retry 3 1 check_for_broadcast "$bob_token_name" "$bob_address_1" 1 - - # Check 'pendingIncomingBalance' query - exec_graphql "$alice_token_name" 'wallets-for-account' - alice_btc_pending_incoming=$(graphql_output ' - .data.me.defaultAccount.wallets[] - | select(.walletCurrency == "BTC") - .pendingIncomingBalance - ') - [[ "$alice_btc_pending_incoming" -gt 0 ]] || exit 1 - - exec_graphql "$bob_token_name" 'wallets-for-account' - bob_usd_pending_incoming=$(graphql_output ' - .data.me.defaultAccount.wallets[] - | select(.walletCurrency == "USD") - .pendingIncomingBalance - ') - [[ "$bob_usd_pending_incoming" -gt 0 ]] || exit 1 - - # Mine transactions - bitcoin_cli -generate 2 - retry 15 1 check_for_onchain_initiated_settled "$alice_token_name" "$alice_address_1" 2 - retry 3 1 check_for_onchain_initiated_settled "$alice_token_name" "$alice_address_2" 2 - retry 3 1 check_for_onchain_initiated_settled "$bob_token_name" "$bob_address_1" 1 -} - -@test "onchain-receive: process received batch transaction via legacy lnd" { - alice_token_name="$ALICE_TOKEN_NAME" - alice_btc_wallet_name="$alice_token_name.btc_wallet_id" - alice_usd_wallet_name="$alice_token_name.usd_wallet_id" - - bob_token_name="$BOB_TOKEN_NAME" - bob_btc_wallet_name="$bob_token_name.btc_wallet_id" - - amount="0.01" - - # Get initial balances - lnd1_initial_balance=$(lnd_cli walletbalance | jq -r '.confirmed_balance') - bria_initial_balance=$( bria_cli wallet-balance -w dev-wallet | jq -r '.effectiveSettled') - - # Create Alice addresses - alice_btc_address="$(create_new_lnd_onchain_address $alice_btc_wallet_name)" - current_btc_address_variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$alice_token_name" 'on-chain-address-current' "$current_btc_address_variables" - on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${on_chain_address_current}" != "null" ]] || exit 1 - [[ "${alice_btc_address}" == "${on_chain_address_current}" ]] || exit 1 - - alice_usd_address="$(create_new_lnd_onchain_address $alice_usd_wallet_name)" - current_usd_address_variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$alice_token_name" 'on-chain-address-current' "$current_usd_address_variables" - on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${on_chain_address_current}" != "null" ]] || exit 1 - [[ "${alice_usd_address}" == "${on_chain_address_current}" ]] || exit 1 - - # Create Bob addresses - bob_btc_address="$(create_new_lnd_onchain_address $bob_btc_wallet_name)" - current_btc_address_variables=$( - jq -n \ - --arg wallet_id "$(read_value $bob_btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$bob_token_name" 'on-chain-address-current' "$current_btc_address_variables" - on_chain_address_current="$(graphql_output '.data.onChainAddressCurrent.address')" - [[ "${on_chain_address_current}" != "null" ]] || exit 1 - [[ "${bob_btc_address}" == "${on_chain_address_current}" ]] || exit 1 - - # Create psbt & broadcast transaction - psbt_outputs=$( - jq -c -n \ - --arg alice_btc_address "$alice_btc_address" \ - --arg alice_usd_address "$alice_usd_address" \ - --arg bob_btc_address "$bob_btc_address" \ - --argjson amount "$amount" \ - '{ - ($alice_btc_address): $amount, - ($alice_usd_address): $amount, - ($bob_btc_address): $amount - }' - ) - unsigned_psbt=$(bitcoin_cli walletcreatefundedpsbt '[]' $psbt_outputs | jq -r '.psbt') - signed_psbt=$(bitcoin_cli walletprocesspsbt "$unsigned_psbt" | jq -r '.psbt') - tx_hex=$(bitcoin_cli finalizepsbt "$signed_psbt" | jq -r '.hex') - txid=$(bitcoin_cli sendrawtransaction "$tx_hex") - - retry 15 1 check_for_broadcast "$alice_token_name" "$alice_btc_address" 10 - retry 3 1 check_for_broadcast "$alice_token_name" "$alice_usd_address" 10 - retry 3 1 check_for_broadcast "$bob_token_name" "$bob_btc_address" 10 - - # Mine transactions - # Note: subscription event operates in a delayed way from lnd1 state - bitcoin_cli -generate 2 - sleep 1 - bitcoin_cli -generate 2 - sleep 1 - bitcoin_cli -generate 2 - - retry 15 1 check_for_onchain_initiated_settled "$alice_token_name" "$alice_btc_address" 10 - retry 3 1 check_for_onchain_initiated_settled "$alice_token_name" "$alice_usd_address" 10 - retry 3 1 check_for_onchain_initiated_settled "$bob_token_name" "$bob_btc_address" 10 - - # Check final balances - lnd1_final_balance=$(lnd_cli walletbalance | jq -r '.confirmed_balance') - [[ "$lnd1_final_balance" -gt "$lnd1_initial_balance" ]] || exit 1 - bria_final_balance=$( bria_cli wallet-balance -w dev-wallet | jq -r '.effectiveSettled') - [[ "$bria_final_balance" == "$bria_initial_balance" ]] || exit 1 -} diff --git a/test/galoy/bats/onchain-send.bats b/test/galoy/bats/onchain-send.bats deleted file mode 100644 index 507f2b987..000000000 --- a/test/galoy/bats/onchain-send.bats +++ /dev/null @@ -1,348 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/onchain" - -setup_file() { - clear_cache - reset_redis - - bitcoind_init - start_trigger - start_exporter - start_server - - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - user_update_username "$ALICE_TOKEN_NAME" - initialize_user_from_onchain "$BOB_TOKEN_NAME" "$BOB_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server - stop_exporter -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -@test "onchain-send: settle trade intraccount" { - token_name="$BOB_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - # mutation: onChainPaymentSend, from BTC to USD wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_btc_payment_send_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_btc_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg address "$on_chain_btc_payment_send_address" \ - --arg amount 200 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_btc_payment_send_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - - # mutation: onChainUsdPaymentSend, from USD to BTC wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_usd_payment_send_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_usd_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_address" \ - --arg amount 200 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_usd_payment_send_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainUsdPaymentSendAsBtcDenominated, from USD to BTC wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_usd_payment_send_as_btc_denominated_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_usd_payment_send_as_btc_denominated_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_as_btc_denominated_address" \ - --arg amount 12345 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-usd-payment-send-as-btc-denominated' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSendAsBtcDenominated.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_usd_payment_send_as_btc_denominated_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainPaymentSendAll, from USD to BTC wallet - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'on-chain-address-create' "$variables" - on_chain_payment_send_all_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_payment_send_all_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_payment_send_all_address" \ - '{input: {walletId: $wallet_id, address: $address}}' - ) - exec_graphql "$token_name" 'on-chain-payment-send-all' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSendAll.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_payment_send_all_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 -} - -@test "onchain-send: settle intraledger, with no contacts check" { - alice_token_name="$ALICE_TOKEN_NAME" - alice_btc_wallet_name="$alice_token_name.btc_wallet_id" - alice_usd_wallet_name="$alice_token_name.usd_wallet_id" - - recipient_token_name="user_$RANDOM" - recipient_phone="$(random_phone)" - login_user \ - "$recipient_token_name" \ - "$recipient_phone" \ - "$CODE" - user_update_username "$recipient_token_name" - btc_recipient_wallet_name="$recipient_token_name.btc_wallet_id" - - # Check is not contact before send - run is_contact "$alice_token_name" "$recipient_token_name" - [[ "$status" -ne "0" ]] || exit 1 - run is_contact "$recipient_token_name" "$alice_token_name" - [[ "$status" -ne "0" ]] || exit 1 - - # mutation: onChainPaymentSend, alice btc -> bob btc - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$recipient_token_name" 'on-chain-address-create' "$variables" - on_chain_payment_send_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_btc_wallet_name)" \ - --arg address "$on_chain_payment_send_address" \ - --arg amount 12345 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$alice_token_name" 'on-chain-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$alice_token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_payment_send_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - # Check is not contact after send - run is_contact "$alice_token_name" "$recipient_token_name" - [[ "$status" -ne "0" ]] || exit 1 - run is_contact "$recipient_token_name" "$alice_token_name" - [[ "$status" -ne "0" ]] || exit 1 - - # mutation: onChainUsdPaymentSend, alice usd -> bob btc - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$recipient_token_name" 'on-chain-address-create' "$variables" - on_chain_usd_payment_send_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_usd_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_address" \ - --arg amount 200 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$alice_token_name" 'on-chain-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$alice_token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_usd_payment_send_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainUsdPaymentSendAsBtcDenominated, alice usd -> bob btc - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$recipient_token_name" 'on-chain-address-create' "$variables" - on_chain_usd_payment_send_as_btc_denominated_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_usd_payment_send_as_btc_denominated_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_as_btc_denominated_address" \ - --arg amount 12345 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$alice_token_name" 'on-chain-usd-payment-send-as-btc-denominated' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSendAsBtcDenominated.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$alice_token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_usd_payment_send_as_btc_denominated_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainPaymentSendAll, bob btc -> alice btc - variables=$( - jq -n \ - --arg wallet_id "$(read_value $alice_btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$alice_token_name" 'on-chain-address-create' "$variables" - on_chain_payment_send_all_address="$(graphql_output '.data.onChainAddressCreate.address')" - [[ "${on_chain_payment_send_all_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_recipient_wallet_name)" \ - --arg address "$on_chain_payment_send_all_address" \ - '{input: {walletId: $wallet_id, address: $address}}' - ) - exec_graphql "$recipient_token_name" 'on-chain-payment-send-all' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSendAll.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - exec_graphql "$recipient_token_name" 'transactions' '{"first": 1}' - settled_status="$(get_from_transaction_by_address $on_chain_payment_send_all_address '.status')" - [[ "${settled_status}" = "SUCCESS" ]] || exit 1 -} - -@test "onchain-send: settle onchain" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - # EXECUTE GQL SENDS - # ---------- - - # mutation: onChainPaymentSend - on_chain_payment_send_address=$(bitcoin_cli getnewaddress) - [[ "${on_chain_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg address "$on_chain_payment_send_address" \ - --arg amount 12345 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainUsdPaymentSend - on_chain_usd_payment_send_address=$(bitcoin_cli getnewaddress) - [[ "${on_chain_usd_payment_send_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_address" \ - --arg amount 200 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-usd-payment-send' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSend.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainUsdPaymentSendAsBtcDenominated - on_chain_usd_payment_send_as_btc_denominated_address=$(bitcoin_cli getnewaddress) - [[ "${on_chain_usd_payment_send_as_btc_denominated_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_usd_payment_send_as_btc_denominated_address" \ - --arg amount 12345 \ - '{input: {walletId: $wallet_id, address: $address, amount: $amount}}' - ) - exec_graphql "$token_name" 'on-chain-usd-payment-send-as-btc-denominated' "$variables" - send_status="$(graphql_output '.data.onChainUsdPaymentSendAsBtcDenominated.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # mutation: onChainPaymentSendAll - on_chain_payment_send_all_address=$(bitcoin_cli getnewaddress) - [[ "${on_chain_payment_send_all_address}" != "null" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg address "$on_chain_payment_send_all_address" \ - '{input: {walletId: $wallet_id, address: $address}}' - ) - exec_graphql "$token_name" 'on-chain-payment-send-all' "$variables" - send_status="$(graphql_output '.data.onChainPaymentSendAll.status')" - [[ "${send_status}" = "SUCCESS" ]] || exit 1 - - # CHECK FOR TRANSACTIONS IN DATABASE - # ---------- - - # Check for broadcast of last send - retry 15 1 check_for_broadcast "$token_name" "$on_chain_payment_send_all_address" 4 - retry 3 1 check_for_broadcast "$token_name" "$on_chain_usd_payment_send_as_btc_denominated_address" 4 - retry 3 1 check_for_broadcast "$token_name" "$on_chain_usd_payment_send_address" 4 - retry 3 1 check_for_broadcast "$token_name" "$on_chain_payment_send_address" 4 - - # Mine all - bitcoin_cli -generate 2 - - # Check for settled - retry 15 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_payment_send_all_address" 4 - retry 3 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_usd_payment_send_as_btc_denominated_address" 4 - retry 3 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_usd_payment_send_address" 4 - retry 3 1 check_for_onchain_initiated_settled "$token_name" "$on_chain_payment_send_address" 4 -} diff --git a/test/galoy/bats/public-ln-receive.bats b/test/galoy/bats/public-ln-receive.bats deleted file mode 100644 index 4f9d35ccb..000000000 --- a/test/galoy/bats/public-ln-receive.bats +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" -load "helpers/ln" - -setup_file() { - clear_cache - - bitcoind_init - start_trigger - start_server - start_ws_server - start_exporter - - lnds_init - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" - user_update_username "$ALICE_TOKEN_NAME" -} - -teardown_file() { - stop_trigger - stop_server - stop_ws_server - stop_exporter -} - -setup() { - reset_redis -} - -teardown() { - if [[ "$(balance_for_check)" != 0 ]]; then - fail "Error: balance_for_check failed" - fi -} - -btc_amount=1000 -usd_amount=50 - -@test "public-ln-receive: account details - can fetch with btc default wallet-id from username" { - token_name=$ALICE_TOKEN_NAME - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - # Change default wallet to btc - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'account-update-default-wallet-id' "$variables" - updated_wallet_id="$(graphql_output '.data.accountUpdateDefaultWalletId.account.defaultWalletId')" - [[ "$updated_wallet_id" == "$(read_value $btc_wallet_name)" ]] || exit 1 - - # Fetch btc-wallet-id from username - variables=$( - jq -n \ - --arg username "$token_name" \ - '{username: $username}' - ) - exec_graphql 'anon' 'account-default-wallet' "$variables" - receiver_wallet_id="$(graphql_output '.data.accountDefaultWallet.id')" - [[ "$receiver_wallet_id" == "$(read_value $btc_wallet_name)" ]] || exit 1 - - # Fetch usd-wallet-id from username - variables=$( - jq -n \ - --arg username "$token_name" \ - '{username: $username, walletCurrency: "USD"}' - ) - exec_graphql 'anon' 'account-default-wallet' "$variables" - receiver_wallet_id="$(graphql_output '.data.accountDefaultWallet.id')" - [[ "$receiver_wallet_id" == "$(read_value $usd_wallet_name)" ]] || exit 1 -} - -@test "public-ln-receive: account details - can fetch with usd default wallet-id from username" { - token_name=$ALICE_TOKEN_NAME - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - # Change default wallet to usd - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {walletId: $wallet_id}}' - ) - exec_graphql "$token_name" 'account-update-default-wallet-id' "$variables" - updated_wallet_id="$(graphql_output '.data.accountUpdateDefaultWalletId.account.defaultWalletId')" - [[ "$updated_wallet_id" == "$(read_value $usd_wallet_name)" ]] || exit 1 - - # Fetch usd-wallet-id from username - variables=$( - jq -n \ - --arg username "$token_name" \ - '{username: $username}' - ) - exec_graphql 'anon' 'account-default-wallet' "$variables" - receiver_wallet_id="$(graphql_output '.data.accountDefaultWallet.id')" - [[ "$receiver_wallet_id" == "$(read_value $usd_wallet_name)" ]] || exit 1 - - # Fetch btc-wallet-id from username - variables=$( - jq -n \ - --arg username "$token_name" \ - '{username: $username, walletCurrency: "BTC"}' - ) - exec_graphql 'anon' 'account-default-wallet' "$variables" - receiver_wallet_id="$(graphql_output '.data.accountDefaultWallet.id')" - [[ "$receiver_wallet_id" == "$(read_value $btc_wallet_name)" ]] || exit 1 -} - -@test "public-ln-receive: account details - return error for invalid username" { - exec_graphql 'anon' 'account-default-wallet' '{"username": "incorrectly-formatted"}' - error_msg="$(graphql_output '.errors[0].message')" - [[ "$error_msg" == "Invalid value for Username" ]] || exit 1 - - exec_graphql 'anon' 'account-default-wallet' '{"username": "idontexist"}' - error_msg="$(graphql_output '.errors[0].message')" - [[ "$error_msg" == "Account does not exist for username idontexist" ]] || exit 1 -} - -@test "public-ln-receive: receive via invoice - can receive on btc invoice, with subscription" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg amount "$btc_amount" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables" - invoice="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - - # Setup subscription - variables=$( - jq -n \ - --arg payment_request "$payment_request" \ - '{"input": {"paymentRequest": $payment_request}}' - ) - subscribe_to 'anon' 'ln-invoice-payment-status-sub' "$variables" - sleep 3 - retry 10 1 grep "Data.*lnInvoicePaymentStatus.*PENDING" .e2e-subscriber.log - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - - # Check for settled with subscription - retry 10 1 grep "Data.*lnInvoicePaymentStatus.*PAID" .e2e-subscriber.log - stop_subscriber -} - -@test "public-ln-receive: receive via invoice - can receive on usd invoice" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg amount "$usd_amount" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-usd-invoice-create-on-behalf-of-recipient' "$variables" - invoice="$(graphql_output '.data.lnUsdInvoiceCreateOnBehalfOfRecipient.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - - # Check for settled with query - retry 15 1 check_ln_payment_settled "$payment_request" -} - -@test "public-ln-receive: receive via invoice - can receive on btc amountless invoice" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - '{input: {recipientWalletId: $wallet_id}}' - ) - exec_graphql 'anon' 'ln-no-amount-invoice-create-on-behalf-of-recipient' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreateOnBehalfOfRecipient.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" - - # Check for settled with query - retry 15 1 check_ln_payment_settled "$payment_request" -} - -@test "public-ln-receive: receive via invoice - can receive on usd amountless invoice" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{input: {recipientWalletId: $wallet_id}}' - ) - exec_graphql 'anon' 'ln-no-amount-invoice-create-on-behalf-of-recipient' "$variables" - invoice="$(graphql_output '.data.lnNoAmountInvoiceCreateOnBehalfOfRecipient.invoice')" - - payment_request="$(echo $invoice | jq -r '.paymentRequest')" - [[ "${payment_request}" != "null" ]] || exit 1 - - # Receive payment - lnd_outside_cli payinvoice -f \ - --pay_req "$payment_request" \ - --amt "$btc_amount" - - # Check for settled with query - retry 15 1 check_ln_payment_settled "$payment_request" -} - -@test "public-ln-receive: fail to create invoice - invalid wallet-id" { - variables=$( - jq -n \ - --arg amount "$btc_amount" \ - '{input: {recipientWalletId: "does-not-exist", amount: $amount}}' - ) - exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "Invalid value for WalletId" ]] || exit 1 - exec_graphql 'anon' 'ln-usd-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnUsdInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "Invalid value for WalletId" ]] || exit 1 - - exec_graphql \ - 'anon' \ - 'ln-no-amount-invoice-create-on-behalf-of-recipient' \ - '{"input": {"recipientWalletId": "does-not-exist"}}' - error_msg="$(graphql_output '.data.lnNoAmountInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "Invalid value for WalletId" ]] || exit 1 -} - -@test "public-ln-receive: fail to create invoice - nonexistent wallet-id" { - non_existent_wallet_id="$(random_uuid)" - - variables=$( - jq -n \ - --arg amount "$btc_amount" \ - --arg recipient_wallet_id "$non_existent_wallet_id" \ - '{input: {recipientWalletId: $recipient_wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == *CouldNotFindWalletFromIdError* ]] || exit 1 - exec_graphql 'anon' 'ln-usd-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnUsdInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == *CouldNotFindWalletFromIdError* ]] || exit 1 - - variables=$( - jq -n \ - --arg recipient_wallet_id "$non_existent_wallet_id" \ - '{input: {recipientWalletId: $recipient_wallet_id}}' - ) - exec_graphql \ - 'anon' \ - 'ln-no-amount-invoice-create-on-behalf-of-recipient' \ - "$variables" - error_msg="$(graphql_output '.data.lnNoAmountInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == *CouldNotFindWalletFromIdError* ]] || exit 1 -} - -@test "public-ln-receive: fail to create invoice - negative amount" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg amount "-1000" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "Invalid value for SatAmount" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg amount "-1000" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-usd-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnUsdInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "Invalid value for CentAmount" ]] || exit 1 -} - -@test "public-ln-receive: fail to create invoice - zero amount" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $btc_wallet_name)" \ - --arg amount "0" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "A valid satoshi amount is required" ]] || exit 1 - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - --arg amount "0" \ - '{input: {recipientWalletId: $wallet_id, amount: $amount}}' - ) - exec_graphql 'anon' 'ln-usd-invoice-create-on-behalf-of-recipient' "$variables" - error_msg="$(graphql_output '.data.lnUsdInvoiceCreateOnBehalfOfRecipient.errors[0].message')" - [[ "$error_msg" == "A valid usd amount is required" ]] || exit 1 - -} diff --git a/test/galoy/bats/public.bats b/test/galoy/bats/public.bats deleted file mode 100644 index 343c4aca4..000000000 --- a/test/galoy/bats/public.bats +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - start_ws_server - start_server -} - -teardown_file() { - stop_server - stop_ws_server -} - -btc_amount=1000 -usd_amount=50 - -@test "public: can query globals" { - exec_graphql 'anon' 'globals' - network="$(graphql_output '.data.globals.network')" - [[ "${network}" = "regtest" ]] || exit 1 -} - -@test "public: can apply idempotency key to queries" { - fixed_idempotency_key=$(new_idempotency_key) - original_new_idempotency_key=$(declare -f new_idempotency_key) - new_idempotency_key() { - echo $fixed_idempotency_key - } - - # Successful 1st attempt - exec_graphql 'anon' 'globals' - errors="$(graphql_output '.errors')" - [[ "$errors" == "null" ]] || exit 1 - - # Failed 2nd attempt with same idempotency key - exec_graphql 'anon' 'globals' - error_msg="$(graphql_output '.errors[0].message')" - [[ "$error_msg" == "HTTP fetch failed from 'public': 409: Conflict" ]] || exit 1 - - # Failed attempt with invalid idempotency key - new_idempotency_key() { - echo "invalid-key" - } - exec_graphql 'anon' 'globals' - error_msg="$(graphql_output '.errors[0].message')" - [[ "$error_msg" == "HTTP fetch failed from 'public': 400: Bad Request" ]] || exit 1 - - # Successful 3rd attempt with unique valid idempotency key - eval "$original_new_idempotency_key" - exec_graphql 'anon' 'globals' - [[ "$errors" == "null" ]] || exit 1 -} - -@test "public: can subscribe to price" { - subscribe_to 'anon' price-sub - retry 10 1 grep 'Data.*\bprice\b' .e2e-subscriber.log - - num_errors=$( - grep 'Data.*\bprice\b' .e2e-subscriber.log \ - | awk '{print $2}' \ - | jq -r '.data.price.errors | length' - ) - [[ "$num_errors" == "0" ]] || exit 1 - - stop_subscriber -} - -@test "public: can subscribe to realtime price" { - subscribe_to 'anon' real-time-price-sub '{"currency": "EUR"}' - retry 10 1 grep 'Data.*\brealtimePrice\b.*EUR' .e2e-subscriber.log - - num_errors=$( - grep 'Data.*\brealtimePrice\b.*EUR' .e2e-subscriber.log \ - | awk '{print $2}' \ - | jq -r '.data.brealtimePrice.errors | length' - ) - [[ "$num_errors" == "0" ]] || exit 1 - - stop_subscriber -} diff --git a/test/galoy/bats/transactions.bats b/test/galoy/bats/transactions.bats deleted file mode 100644 index 0c61cfdd7..000000000 --- a/test/galoy/bats/transactions.bats +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bats - -load "helpers/setup-and-teardown" - -setup_file() { - clear_cache - reset_redis - - bitcoind_init - start_trigger - start_server - - initialize_user_from_onchain "$ALICE_TOKEN_NAME" "$ALICE_PHONE" "$CODE" -} - -teardown_file() { - stop_trigger - stop_server -} - -count_transactions_by_currency() { - transactions_array_query=$1 - currency=$2 - - echo $output \ - | jq -r "$transactions_array_query" \ - | jq -r --arg currency "$currency" 'select(.node.settlementCurrency == $currency)' \ - | jq -s "length" -} - -currency_for_wallet() { - idx=$1 - - echo $output \ - | jq -r --argjson idx "$idx" '.data.me.defaultAccount.wallets[$idx].walletCurrency' -} - -@test "transactions: by account" { - token_name="$ALICE_TOKEN_NAME" - account_transactions_query='.data.me.defaultAccount.transactions.edges[]' - - exec_graphql "$token_name" 'transactions' '{"first": 3}' - usd_count="$(count_transactions_by_currency \ - $account_transactions_query \ - 'USD' \ - )" - [[ "$usd_count" -gt "0" ]] || exit 1 - btc_count="$(count_transactions_by_currency \ - $account_transactions_query \ - 'BTC' \ - )" - [[ "$btc_count" -gt "0" ]] || exit 1 -} - -@test "transactions: by account, filtered by wallet" { - token_name="$ALICE_TOKEN_NAME" - usd_wallet_name="$token_name.usd_wallet_id" - account_transactions_query='.data.me.defaultAccount.transactions.edges[]' - - variables=$( - jq -n \ - --arg wallet_id "$(read_value $usd_wallet_name)" \ - '{"first": 3, walletIds: [$wallet_id]}' - ) - exec_graphql "$token_name" 'transactions' "$variables" - usd_count="$(count_transactions_by_currency \ - $account_transactions_query \ - 'USD' \ - )" - [[ "$usd_count" -gt "0" ]] || exit 1 - btc_count="$(count_transactions_by_currency \ - $account_transactions_query \ - 'BTC' \ - )" - [[ "$btc_count" == "0" ]] || exit 1 -} - -@test "transactions: by wallet" { - token_name="$ALICE_TOKEN_NAME" - btc_wallet_name="$token_name.btc_wallet_id" - usd_wallet_name="$token_name.usd_wallet_id" - - exec_graphql "$token_name" 'transactions-by-wallet' '{"first": 3}' - - # Check for BTC wallet - wallet_0_currency="$(currency_for_wallet 0)" - [[ "$wallet_0_currency" == "BTC" ]] || exit 1 - - wallet_0_transactions_query='.data.me.defaultAccount.wallets[0].transactions.edges[]' - - wallet_0_btc_count="$(count_transactions_by_currency \ - $wallet_0_transactions_query \ - 'BTC' \ - )" - [[ "$wallet_0_btc_count" -gt "0" ]] || exit 1 - - # Check for USD wallet - wallet_1_currency="$(currency_for_wallet 1)" - [[ "$wallet_1_currency" == "USD" ]] || exit 1 - - wallet_1_transactions_query='.data.me.defaultAccount.wallets[1].transactions.edges[]' - - wallet_1_usd_count="$(count_transactions_by_currency \ - $wallet_1_transactions_query \ - 'USD' \ - )" - [[ "$wallet_1_usd_count" -gt "0" ]] || exit 1 -} diff --git a/test/galoy/e2e/jest.config.js b/test/galoy/e2e/jest.config.js deleted file mode 100644 index 86cb35efe..000000000 --- a/test/galoy/e2e/jest.config.js +++ /dev/null @@ -1,27 +0,0 @@ -const swcConfig = require("../swc-config.json") - -module.exports = { - moduleFileExtensions: ["js", "json", "ts", "gql", "cjs", "mjs"], - rootDir: "../../", - roots: ["/test/e2e"], - transform: { - "^.+\\.(t|j)sx?$": ["@swc/jest", swcConfig], - "^.+\\.(gql)$": "@graphql-tools/jest-transform", - }, - testRegex: ".*\\.spec\\.ts$", - setupFilesAfterEnv: ["/test/e2e/jest.setup.js"], - testEnvironment: "node", - moduleNameMapper: { - "^@config$": ["src/config/index"], - "^@app$": ["src/app/index"], - "^@utils$": ["src/utils/index"], - - "^@core/(.*)$": ["src/core/$1"], - "^@app/(.*)$": ["src/app/$1"], - "^@domain/(.*)$": ["src/domain/$1"], - "^@services/(.*)$": ["src/services/$1"], - "^@servers/(.*)$": ["src/servers/$1"], - "^@graphql/(.*)$": ["src/graphql/$1"], - "^test/(.*)$": ["test/$1"], - }, -} diff --git a/test/galoy/e2e/jest.setup.js b/test/galoy/e2e/jest.setup.js deleted file mode 100644 index ad99914fa..000000000 --- a/test/galoy/e2e/jest.setup.js +++ /dev/null @@ -1,26 +0,0 @@ -const { disconnectAll } = require("@services/redis") -const { setupMongoConnection } = require("@services/mongodb") - -jest.mock("@services/lnd/auth", () => { - const module = jest.requireActual("@services/lnd/auth") - const lndsConnect = module.lndsConnect.map((p) => ({ ...p, active: true })) - return { ...module, lndsConnect } -}) - -let mongoose - -jest.mock("@services/twilio", () => require("test/mocks/twilio")) - -beforeAll(async () => { - mongoose = await setupMongoConnection(true) -}) - -afterAll(async () => { - // avoids to use --forceExit - disconnectAll() - if (mongoose) { - await mongoose.connection.close() - } -}) - -jest.setTimeout(process.env.JEST_TIMEOUT || 90000) diff --git a/test/galoy/e2e/servers/index.types.d.ts b/test/galoy/e2e/servers/index.types.d.ts deleted file mode 100644 index 6968e44d9..000000000 --- a/test/galoy/e2e/servers/index.types.d.ts +++ /dev/null @@ -1 +0,0 @@ -type PID = number & { readonly brand: unique symbol } diff --git a/test/galoy/e2e/servers/kratos.spec.ts b/test/galoy/e2e/servers/kratos.spec.ts deleted file mode 100644 index 373f83398..000000000 --- a/test/galoy/e2e/servers/kratos.spec.ts +++ /dev/null @@ -1,651 +0,0 @@ -import { - AuthenticationError, - EmailCodeInvalidError, - LikelyNoUserWithThisPhoneExistError, - LikelyUserAlreadyExistError, -} from "@domain/authentication/errors" -import { - AuthWithPhonePasswordlessService, - AuthWithUsernamePasswordDeviceIdService, - IdentityRepository, - extendSession, - getNextPage, - listSessions, - validateKratosToken, - AuthenticationKratosError, - IncompatibleSchemaUpgradeError, - KratosError, - AuthWithEmailPasswordlessService, - kratosValidateTotp, - kratosInitiateTotp, - kratosElevatingSessionWithTotp, - SchemaIdType, - kratosRemoveTotp, -} from "@services/kratos" -import { kratosAdmin, kratosPublic } from "@services/kratos/private" -import { - activateUser, - deactivateUser, - revokeSessions, -} from "@services/kratos/tests-but-not-prod" -import { authenticator } from "otplib" - -import { sleep } from "@utils" - -import { getError, randomEmail, randomPhone } from "test/galoy/helpers" - -import { getEmailCode } from "test/galoy/helpers/kratos" - -beforeAll(async () => { - // await removeIdentities() - // needed for the kratos callback to registration - // serverPid = await startServer("start-main-ci") -}) - -afterAll(async () => { - // await killServer(serverPid) -}) - -describe("phoneNoPassword", () => { - const authService = AuthWithPhonePasswordlessService() - - describe("public selflogin api", () => { - const phone = randomPhone() - let kratosUserId: UserId - - it("create a user", async () => { - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - - expect(res).toHaveProperty("kratosUserId") - kratosUserId = res.kratosUserId - }) - - it("can't create user twice", async () => { - const res = await authService.createIdentityWithSession({ phone }) - - expect(res).toBeInstanceOf(LikelyUserAlreadyExistError) - }) - - it("login user succeed if user exists", async () => { - const res = await authService.loginToken({ phone }) - if (res instanceof Error) throw res - - expect(res.kratosUserId).toBe(kratosUserId) - }) - - it("get user id through getUserIdFromIdentifier(phone)", async () => { - const identities = IdentityRepository() - const userId = await identities.getUserIdFromIdentifier(phone) - - if (userId instanceof Error) throw userId - - expect(userId).toBe(kratosUserId) - }) - - it("new sessions are added when LoginWithPhoneNoPasswordSchema is used", async () => { - const res = await authService.loginToken({ phone }) - if (res instanceof Error) throw res - - expect(res.kratosUserId).toBe(kratosUserId) - const sessions = await listSessions(kratosUserId) - if (sessions instanceof Error) throw sessions - - expect(sessions).toHaveLength(3) - }) - - it("add totp", async () => { - const phone = randomPhone() - let authToken: AuthToken - let userId: UserId - - let totpSecret: string - { - const res0 = await authService.createIdentityWithSession({ phone }) - if (res0 instanceof Error) throw res0 - - authToken = res0.authToken - - const res1 = await kratosInitiateTotp(authToken) - if (res1 instanceof Error) throw res1 - - const { totpSecret: totpSecret_, totpRegistrationId } = res1 - totpSecret = totpSecret_ - const totpCode = authenticator.generate(totpSecret) - - const res = kratosValidateTotp({ totpRegistrationId, totpCode, authToken }) - if (res instanceof Error) throw res - - const res2 = await validateKratosToken(authToken) - if (res2 instanceof Error) throw res2 - expect(res2).toEqual( - expect.objectContaining({ - kratosUserId: expect.any(String), - session: expect.any(Object), - }), - ) - - // wait for the identity to be updated? - // some cache or asynchronous method need to run on the kratos side? - await sleep(100) - const identity = await IdentityRepository().getIdentity(res2.kratosUserId) - if (identity instanceof Error) throw identity - expect(identity.totpEnabled).toBe(true) - - userId = res2.kratosUserId - } - - { - const res = await authService.loginToken({ phone }) - if (res instanceof Error) throw res - expect(res).toEqual( - expect.objectContaining({ - kratosUserId: undefined, - authToken: expect.any(String), - }), - ) - - const totpCode = authenticator.generate(totpSecret) as TotpCode - - const res2 = await kratosElevatingSessionWithTotp({ - authToken: res.authToken, - totpCode, - }) - if (res2 instanceof Error) throw res2 - expect(res2).toBe(true) - } - - await kratosRemoveTotp(authToken) - - // wait for the identity to be updated? - // some cache or asynchronous method need to run on the kratos side? - await sleep(100) - const identity = await IdentityRepository().getIdentity(userId) - if (identity instanceof Error) throw identity - expect(identity.totpEnabled).toBe(false) - }) - - it("login fails is user doesn't exist", async () => { - const phone = randomPhone() - const res = await authService.loginToken({ phone }) - expect(res).toBeInstanceOf(LikelyNoUserWithThisPhoneExistError) - }) - - it("forbidding change of a phone number from publicApi", async () => { - const phone = randomPhone() - - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - - const res1 = await validateKratosToken(res.authToken) - if (res1 instanceof Error) throw res1 - expect(res1.session.identity.phone).toStrictEqual(phone) - - const res2 = await kratosPublic.createNativeSettingsFlow({ - xSessionToken: res.authToken, - }) - - const newPhone = randomPhone() - - const err = await getError(() => - kratosPublic.updateSettingsFlow({ - flow: res2.data.id, - updateSettingsFlowBody: { - method: "profile", - traits: { - phone: newPhone, - }, - }, - xSessionToken: res.authToken, - }), - ) - - expect(err).toBeTruthy() - }) - }) - - describe("admin api", () => { - it("create a user with admin api, and can login with self api", async () => { - const phone = randomPhone() - const kratosUserId = await authService.createIdentityNoSession({ phone }) - if (kratosUserId instanceof Error) throw kratosUserId - - const res2 = await authService.loginToken({ phone }) - if (res2 instanceof Error) throw res2 - - expect(res2.kratosUserId).toBe(kratosUserId) - }) - }) -}) - -describe("token validation", () => { - const authService = AuthWithPhonePasswordlessService() - - it("validate bearer token", async () => { - const phone = randomPhone() - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - - const token = res.authToken - const res2 = await validateKratosToken(token) - if (res2 instanceof Error) throw res2 - expect(res2.kratosUserId).toBe(res.kratosUserId) - }) - - it("return error on invalid token", async () => { - const res = await validateKratosToken("invalid_token" as AuthToken) - expect(res).toBeInstanceOf(AuthenticationKratosError) - }) -}) - -describe("session revokation", () => { - const authService = AuthWithPhonePasswordlessService() - - const phone = randomPhone() - it("revoke user session", async () => { - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - const kratosUserId = res.kratosUserId - - { - const { data } = await kratosAdmin.listIdentitySessions({ id: kratosUserId }) - expect(data.length).toBeGreaterThan(0) - } - - await revokeSessions(kratosUserId) - - { - const { data } = await kratosAdmin.listIdentitySessions({ id: kratosUserId }) - expect(data.length).toEqual(0) - } - }) - - it("return error on revoked session", async () => { - let token: AuthToken - { - const res = await authService.loginToken({ phone }) - if (res instanceof Error) throw res - if (res.kratosUserId === undefined) throw new Error("kratosUserId is undefined") - - token = res.authToken - await revokeSessions(res.kratosUserId) - } - { - const res = await validateKratosToken(token) - expect(res).toBeInstanceOf(AuthenticationKratosError) - } - }) - - it("revoke a user's second session only", async () => { - // Session 1 - const session1 = await authService.loginToken({ phone }) - if (session1 instanceof Error) throw session1 - const session1Token = session1.authToken - - // Session 2 - const session2 = await authService.loginToken({ phone }) - if (session2 instanceof Error) throw session2 - const session2Token = session2.authToken - - // Session Details - // *caveat, you need to have at least 2 active sessions - // for 'listMySessions' to work properly if you only - // have 1 active session the data will come back null - const session1Details = await kratosPublic.listMySessions({ - xSessionToken: session1Token, - }) - const session1Id = session1Details.data[0].id - const session2Details = await kratosPublic.listMySessions({ - xSessionToken: session2Token, - }) - const session2Id = session2Details.data[0].id - expect(session1Id).toBeDefined() - expect(session2Id).toBeDefined() - - // Revoke Session 2 - await kratosPublic.performNativeLogout({ - performNativeLogoutBody: { - session_token: session2Token, - }, - }) - - const isSession1Valid = await validateKratosToken(session1Token) - const isSession2Valid = await validateKratosToken(session2Token) - expect(isSession1Valid).toBeDefined() - expect(isSession2Valid).toBeInstanceOf(KratosError) - }) -}) - -describe.skip("update status", () => { - // Status on kratos is not implemented - const authService = AuthWithPhonePasswordlessService() - - let kratosUserId: UserId - const phone = randomPhone() - - it("deactivate user", async () => { - { - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - kratosUserId = res.kratosUserId - } - await deactivateUser(kratosUserId) - await authService.loginToken({ phone }) - - const res = await authService.loginToken({ phone }) - expect(res).toBeInstanceOf(AuthenticationKratosError) - }) - - it("activate user", async () => { - await activateUser(kratosUserId) - const res = await authService.loginToken({ phone }) - if (res instanceof Error) throw res - expect(res.kratosUserId).toBe(kratosUserId) - }) -}) - -it("extend session", async () => { - const authService = AuthWithPhonePasswordlessService() - - const phone = randomPhone() - const res = await authService.createIdentityWithSession({ phone }) - if (res instanceof Error) throw res - - expect(res).toHaveProperty("kratosUserId") - const res2 = await kratosPublic.toSession({ xSessionToken: res.authToken }) - const sessionKratos = res2.data - if (!sessionKratos.expires_at) throw Error("should have expired_at") - const initialExpiresAt = new Date(sessionKratos.expires_at) - - const sessionId = sessionKratos.id as SessionId - - await extendSession(sessionId) - await sleep(200) - const res3 = await kratosPublic.toSession({ xSessionToken: res.authToken }) - const newSession = res3.data - if (!newSession.expires_at) throw Error("should have expired_at") - const newExpiresAt = new Date(newSession.expires_at) - - expect(initialExpiresAt.getTime()).toBeLessThan(newExpiresAt.getTime()) -}) - -describe("phone+email schema", () => { - const authServiceEmail = AuthWithEmailPasswordlessService() - const authServicePhone = AuthWithPhonePasswordlessService() - - let kratosUserId: UserId - const email = randomEmail() - const phone = randomPhone() - - it("create a user with phone", async () => { - const res0 = await authServicePhone.createIdentityWithSession({ phone }) - if (res0 instanceof Error) throw res0 - kratosUserId = res0.kratosUserId - - const newIdentity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(newIdentity.data.traits.phone).toBe(phone) - - expect(await authServiceEmail.hasEmail({ kratosUserId })).toBe(false) - }) - - it("upgrade to phone+email schema", async () => { - const res = await authServiceEmail.addUnverifiedEmailToIdentity({ - kratosUserId, - email, - }) - if (res instanceof Error) throw res - - const newIdentity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(newIdentity.data.schema_id).toBe("phone_email_no_password_v0") - expect(newIdentity.data.traits.email).toBe(email) - - expect(await authServiceEmail.hasEmail({ kratosUserId })).toBe(true) - expect(await authServiceEmail.isEmailVerified({ email })).toBe(false) - }) - - it("get user id through getUserIdFromIdentifier(email)", async () => { - const identities = IdentityRepository() - const userId = await identities.getUserIdFromIdentifier(email) - - if (userId instanceof Error) throw userId - expect(userId).toBe(kratosUserId) - }) - - it("can't add same email to multiple identities", async () => { - const phone = randomPhone() - const res0 = await authServicePhone.createIdentityWithSession({ phone }) - if (res0 instanceof Error) throw res0 - const kratosUserId = res0.kratosUserId - - const res = await authServiceEmail.addUnverifiedEmailToIdentity({ - kratosUserId, - email, - }) - if (!(res instanceof AuthenticationError)) throw new Error("wrong type") - expect(res.name).toBe("EmailAlreadyExistsError") - }) - - it("email verification", async () => { - const emailFlowId = await authServiceEmail.sendEmailWithCode({ email }) - if (emailFlowId instanceof Error) throw emailFlowId - - { - // TODO: look if there are rate limit on the side of kratos - const wrongCode = "000000" as EmailCode - const res = await authServiceEmail.validateCode({ - code: wrongCode, - emailFlowId, - }) - expect(res).toBeInstanceOf(EmailCodeInvalidError) - - expect(await authServiceEmail.isEmailVerified({ email })).toBe(false) - } - - { - const code = await getEmailCode(email) - - const res = await authServiceEmail.validateCode({ - code, - emailFlowId, - }) - if (res instanceof Error) throw res - expect(res.email).toBe(email) - - expect(await authServiceEmail.isEmailVerified({ email })).toBe(true) - } - }) - - it("login back to an email account", async () => { - const emailFlowId = await authServiceEmail.sendEmailWithCode({ email }) - if (emailFlowId instanceof Error) throw emailFlowId - - const code = await getEmailCode(email) - - { - const wrongCode = "000000" as EmailCode - const res = await authServiceEmail.validateCode({ - code: wrongCode, - emailFlowId: emailFlowId, - }) - expect(res).toBeInstanceOf(EmailCodeInvalidError) - } - - { - const res = await authServiceEmail.validateCode({ - code, - emailFlowId: emailFlowId, - }) - if (res instanceof Error) throw res - expect(res.email).toBe(email) - } - - { - const res = await authServiceEmail.loginToken({ email }) - if (res instanceof Error) throw res - expect(res.kratosUserId).toBe(kratosUserId) - } - }) - - it("login back to an email account using cookies auth", async () => { - const emailFlowId = await authServiceEmail.sendEmailWithCode({ email }) - if (emailFlowId instanceof Error) throw emailFlowId - - const code = await getEmailCode(email) - - { - const wrongCode = "000000" as EmailCode - const res = await authServiceEmail.validateCode({ - code: wrongCode, - emailFlowId: emailFlowId, - }) - expect(res).toBeInstanceOf(EmailCodeInvalidError) - } - - { - const res = await authServiceEmail.validateCode({ - code, - emailFlowId: emailFlowId, - }) - if (res instanceof Error) throw res - expect(res.email).toBe(email) - } - - { - const res = await authServiceEmail.loginCookie({ email }) - if (res instanceof Error) throw res - expect(res.cookiesToSendBackToClient.length).toBe(2) - } - }) - - // TODO: verification code expired - - it("login back to an phone+email account by phone", async () => { - const res = await authServicePhone.loginToken({ phone }) - if (res instanceof Error) throw res - - expect(res.kratosUserId).toBe(kratosUserId) - const identity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(identity.data.schema_id).toBe("phone_email_no_password_v0") - }) - - it("remove email", async () => { - const res = await authServiceEmail.removeEmailFromIdentity({ kratosUserId }) - if (res instanceof Error) throw res - - const identity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(identity.data.schema_id).toBe(SchemaIdType.PhoneNoPasswordV0) - expect(identity.data.traits.email).toBe(undefined) - }) - - it("can't remove phone if there is no email attached", async () => { - const res = await authServiceEmail.removePhoneFromIdentity({ kratosUserId }) - expect(res).toBeInstanceOf(IncompatibleSchemaUpgradeError) - }) - - it("remove phone from identity", async () => { - await authServiceEmail.addUnverifiedEmailToIdentity({ - kratosUserId, - email, - }) - - const emailRegistrationId = await authServiceEmail.sendEmailWithCode({ email }) - if (emailRegistrationId instanceof Error) throw emailRegistrationId - - { - const code = await getEmailCode(email) - await authServiceEmail.validateCode({ code, emailFlowId: emailRegistrationId }) - } - - await authServiceEmail.removePhoneFromIdentity({ kratosUserId }) - - const identity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(identity.data.schema_id).toBe("email_no_password_v0") - }) - - it("verification on an inexistent email address result in not send an email", async () => { - const email = randomEmail() - - const flow = await authServiceEmail.sendEmailWithCode({ email }) - if (flow instanceof Error) throw flow - - // there is no email - await expect(async () => getEmailCode(email)).rejects.toThrow() - }) -}) - -describe("decoding link header", () => { - const withNext = - '; rel="next",; rel="last"' - - const withoutNext = - '; rel="first",; rel="prev"' - - it("try decoding link successfully", () => { - expect(getNextPage(withNext)).toBe(1) - }) - - it("should be undefined when no more next is present", () => { - expect(getNextPage(withoutNext)).toBe(undefined) - }) -}) - -describe("cookie flow", () => { - it("login with cookie then revoke session", async () => { - const authService = AuthWithPhonePasswordlessService() - const phone = randomPhone() - - const res = (await authService.createIdentityWithCookie({ - phone, - })) as WithCookieResponse - expect(res).toHaveProperty("kratosUserId") - expect(res).toHaveProperty("cookiesToSendBackToClient") - - const cookies: Array = res.cookiesToSendBackToClient - let cookieStr = "" - for (const cookie of cookies) { - cookieStr = cookieStr + cookie + "; " - } - cookieStr = decodeURIComponent(cookieStr) - - const kratosSession = await kratosPublic.toSession({ cookie: cookieStr }) - const sessionId = kratosSession.data.id - const kratosResp = await kratosAdmin.disableSession({ - id: sessionId, - }) - expect(kratosResp.status).toBe(204) - }) -}) - -describe("device account flow", () => { - const authService = AuthWithUsernamePasswordDeviceIdService() - const username = crypto.randomUUID() as IdentityUsername - const password = crypto.randomUUID() as IdentityPassword - let kratosUserId: UserId - - it("create an account", async () => { - const res = await authService.createIdentityWithSession({ - username, - password, - }) - if (res instanceof Error) throw res - ;({ kratosUserId } = res) - - const newIdentity = await kratosAdmin.getIdentity({ id: kratosUserId }) - expect(newIdentity.data.schema_id).toBe("username_password_deviceid_v0") - expect(newIdentity.data.traits.username).toBe(username) - }) - - it("upgrade account", async () => { - const phone = randomPhone() - - const authService = AuthWithPhonePasswordlessService() - const res = await authService.updateIdentityFromDeviceAccount({ - phone, - userId: kratosUserId, - }) - if (res instanceof Error) throw res - - expect(res.phone).toBe(phone) - expect(res.id).toBe(kratosUserId) - }) -}) diff --git a/test/galoy/helpers/lightning.ts b/test/galoy/helpers/lightning.ts index 4ca80e813..154619c68 100644 --- a/test/galoy/helpers/lightning.ts +++ b/test/galoy/helpers/lightning.ts @@ -2,7 +2,7 @@ import { once } from "events" import { Wallets } from "@app" -import { WalletCurrency } from "@domain/shared" +import { BtcPaymentAmount, WalletCurrency } from "@domain/shared" import { onChannelUpdated, updateEscrows } from "@services/lnd/utils" import { baseLogger } from "@services/logger" import { WalletsRepository } from "@services/mongoose" @@ -412,8 +412,14 @@ export const fundWalletIdFromLightning = async ({ const invoice = wallet.currency === WalletCurrency.Btc - ? await Wallets.addInvoiceForSelfForBtcWallet({ walletId, amount }) - : await Wallets.addInvoiceForSelfForUsdWallet({ walletId, amount }) + ? await Wallets.addInvoiceForSelfForBtcWallet({ + walletId, + amount: BtcPaymentAmount(BigInt(amount)), + }) + : await Wallets.addInvoiceForSelfForUsdWallet({ + walletId, + amount: amount as FractionalCentAmount, + }) if (invoice instanceof Error) return invoice safePay({ lnd: lndOutside1, request: invoice.paymentRequest }) diff --git a/test/galoy/helpers/user.ts b/test/galoy/helpers/user.ts index cc7008411..181d62526 100644 --- a/test/galoy/helpers/user.ts +++ b/test/galoy/helpers/user.ts @@ -3,7 +3,7 @@ import { addWalletIfNonexistent } from "@app/accounts/add-wallet" import { getAdminAccounts, getDefaultAccountsConfig } from "@config" import { CouldNotFindAccountFromKratosIdError, CouldNotFindError } from "@domain/errors" -import { UsdWalletDescriptor, WalletCurrency } from "@domain/shared" +import { BtcPaymentAmount, WalletCurrency } from "@domain/shared" import { WalletType } from "@domain/wallets" import { @@ -208,8 +208,8 @@ export const createAccount = async ({ } export const createRandomUserAndBtcWallet = async () => { - const phone = randomPhone() - return createUserAndWallet(phone) + const { btcWalletDescriptor } = await createRandomUserAndWallets() + return btcWalletDescriptor } export type TestUser = { @@ -231,20 +231,31 @@ export const getUser = async (walletD: WalletDescripto export const createRandomUserAndWallets = async (): Promise<{ usdWalletDescriptor: WalletDescriptor<"USD"> - // btcWalletDescriptor: WalletDescriptor<"BTC"> + btcWalletDescriptor: WalletDescriptor<"BTC"> }> => { const phone = randomPhone() - const btcWalletDescriptor = await createUserAndWallet(phone) + const usdWalletDescriptor = await createUserAndWallet(phone) + + const btcWallet = await addWalletIfNonexistent({ + currency: WalletCurrency.Btc, + accountId: usdWalletDescriptor.accountId, + type: WalletType.Checking, + }) + if (btcWallet instanceof Error) throw btcWallet const usdWallet = await addWalletIfNonexistent({ currency: WalletCurrency.Usd, - accountId: btcWalletDescriptor.accountId, + accountId: usdWalletDescriptor.accountId, type: WalletType.Checking, }) if (usdWallet instanceof Error) throw usdWallet return { - // btcWalletDescriptor, + btcWalletDescriptor: { + id: btcWallet.id, + currency: WalletCurrency.Btc, + accountId: btcWallet.accountId, + }, usdWalletDescriptor: { id: usdWallet.id, currency: WalletCurrency.Usd, @@ -359,15 +370,18 @@ export const createAndFundNewWallet = async ({ if (wallet instanceof Error) throw wallet // Fund new wallet if a non-zero balance is passed if (balanceAmount.amount === 0n) return wallet - const addInvoiceFn = + const lnInvoice = wallet.currency === WalletCurrency.Btc - ? Wallets.addInvoiceForSelfForBtcWallet - : Wallets.addInvoiceForSelfForUsdWallet - const lnInvoice = await addInvoiceFn({ - walletId: wallet.id, - amount: Number(balanceAmount.amount), - memo: `Fund new wallet ${wallet.id}`, - }) + ? await Wallets.addInvoiceForSelfForBtcWallet({ + walletId: wallet.id, + amount: BtcPaymentAmount(balanceAmount.amount), + memo: `Fund new wallet ${wallet.id}`, + }) + : await Wallets.addInvoiceForSelfForUsdWallet({ + walletId: wallet.id, + amount: Number(balanceAmount.amount) as FractionalCentAmount, + memo: `Fund new wallet ${wallet.id}`, + }) if (lnInvoice instanceof Error) throw lnInvoice const { paymentRequest: invoice, paymentHash } = lnInvoice const updateInvoice = () => @@ -404,15 +418,18 @@ export const fundWallet = async ({ const wallet = await WalletsRepository().findById(walletId) if (wallet instanceof Error) throw wallet - const addInvoiceFn = + const lnInvoice = wallet.currency === WalletCurrency.Btc - ? Wallets.addInvoiceForSelfForBtcWallet - : Wallets.addInvoiceForSelfForUsdWallet - const lnInvoice = await addInvoiceFn({ - walletId: wallet.id, - amount: Number(balanceAmount.amount), - memo: `Fund new wallet ${wallet.id}`, - }) + ? await Wallets.addInvoiceForSelfForBtcWallet({ + walletId: wallet.id, + amount: BtcPaymentAmount(balanceAmount.amount), + memo: `Fund new wallet ${wallet.id}`, + }) + : await Wallets.addInvoiceForSelfForUsdWallet({ + walletId: wallet.id, + amount: Number(balanceAmount.amount) as FractionalCentAmount, + memo: `Fund new wallet ${wallet.id}`, + }) if (lnInvoice instanceof Error) throw lnInvoice const { paymentRequest: invoice, paymentHash } = lnInvoice const updateInvoice = () => diff --git a/test/galoy/helpers/wallet.ts b/test/galoy/helpers/wallet.ts index cae452dca..27fada7bd 100644 --- a/test/galoy/helpers/wallet.ts +++ b/test/galoy/helpers/wallet.ts @@ -10,7 +10,7 @@ export const getBalanceHelper = async ( ): Promise => { const balance = await getBalanceForWallet({ walletId }) if (balance instanceof Error) throw balance - return balance + return balance as unknown as CurrencyBaseAmount } export const getTransactionsForWalletId = async ( @@ -19,7 +19,9 @@ export const getTransactionsForWalletId = async ( const wallets = WalletsRepository() const wallet = await wallets.findById(walletId) if (wallet instanceof RepositoryError) return PartialResult.err(wallet) - return getTransactionsForWallets({ wallets: [wallet] }) + return getTransactionsForWallets({ wallets: [wallet] }) as Promise< + PartialResult> + > } // This is to test detection of funds coming in on legacy addresses diff --git a/test/galoy/integration/app/accounts/get-transactions-for-accounts.spec.ts b/test/galoy/integration/app/accounts/get-transactions-for-accounts.spec.ts deleted file mode 100644 index 11789bec1..000000000 --- a/test/galoy/integration/app/accounts/get-transactions-for-accounts.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Accounts } from "@app" - -import { toSats } from "@domain/bitcoin" -import { UsdDisplayCurrency, toCents } from "@domain/fiat" -import { InvalidWalletId } from "@domain/errors" - -import { AccountsRepository } from "@services/mongoose" -import { Transaction } from "@services/ledger/schema" - -import { AmountCalculator, WalletCurrency } from "@domain/shared" - -import { - createMandatoryUsers, - createRandomUserAndBtcWallet, - recordReceiveLnPayment, -} from "test/galoy/helpers" - -const calc = AmountCalculator() - -beforeAll(async () => { - await createMandatoryUsers() -}) - -const amount = toSats(10040) -const btcPaymentAmount: BtcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, -} - -const usdAmount = toCents(210) -const usdPaymentAmount: UsdPaymentAmount = { - amount: BigInt(usdAmount), - currency: WalletCurrency.Usd, -} - -const receiveAmounts = { btc: calc.mul(btcPaymentAmount, 3n), usd: usdPaymentAmount } - -const receiveBankFee = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 1n, currency: WalletCurrency.Usd }, -} - -const receiveDisplayAmounts = { - amountDisplayCurrency: Number(receiveAmounts.usd.amount) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, -} - -const randomMemo = () => "this is my memo #" + (Math.random() * 1_000_000).toFixed() - -describe("getTransactionsForAccountByWalletIds", () => { - it("returns an error if non-owned walletId is included", async () => { - const memo = randomMemo() - - const senderWalletDescriptor = await createRandomUserAndBtcWallet() - const senderAccount = await AccountsRepository().findById( - senderWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - // Create transaction - const receive = await recordReceiveLnPayment({ - walletDescriptor: senderWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - expect(receive).not.toBeInstanceOf(Error) - - // Attempt transactions call - const otherWalletDescriptor = await createRandomUserAndBtcWallet() - - const txns = await Accounts.getTransactionsForAccountByWalletIds({ - account: senderAccount, - walletIds: [otherWalletDescriptor.id], - }) - if (txns instanceof Error) throw txns - expect(txns.error).toBeInstanceOf(InvalidWalletId) - - // Restore system state - await Transaction.deleteMany({ memo }) - }) -}) diff --git a/test/galoy/integration/app/authentication/create-account-from-registration-payload.spec.ts b/test/galoy/integration/app/authentication/create-account-from-registration-payload.spec.ts deleted file mode 100644 index 1b8325501..000000000 --- a/test/galoy/integration/app/authentication/create-account-from-registration-payload.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createAccountFromRegistrationPayload } from "@app/authentication" -import { KRATOS_CALLBACK_API_KEY } from "@config" -import { - SecretForAuthNCallbackError, - RegistrationPayloadValidationError, -} from "@domain/authentication/errors" - -describe("createAccountFromRegistrationPayload", () => { - it("fails with bad auth key", async () => { - const account = await createAccountFromRegistrationPayload({ - secret: "invalid-key", - body: {}, - }) - expect(account).toBeInstanceOf(SecretForAuthNCallbackError) - }) - - it("fails with bad registration payload", async () => { - const account = await createAccountFromRegistrationPayload({ - secret: KRATOS_CALLBACK_API_KEY, - body: {}, - }) - expect(account).toBeInstanceOf(RegistrationPayloadValidationError) - }) -}) diff --git a/test/galoy/integration/app/authentication/extend.spec.ts b/test/galoy/integration/app/authentication/extend.spec.ts deleted file mode 100644 index ce8f27bfe..000000000 --- a/test/galoy/integration/app/authentication/extend.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { maybeExtendSession } from "@app/authentication" -import { extendSession } from "@services/kratos" - -jest.mock("@services/kratos", () => { - return { - extendSession: jest.fn(), - } -}) - -it("maybe extend session", async () => { - { - // token in 9 month, should be renew - const expiresAt = new Date() - expiresAt.setMonth(expiresAt.getMonth() + 9) - - const sessionId = "uuid" - - await maybeExtendSession({ - sessionId, - expiresAt: expiresAt.toISOString(), - }) - - expect(extendSession).toHaveBeenCalledTimes(1) - } - - { - // token in 13 month, should not be renew - const expiresAt = new Date() - expiresAt.setMonth(expiresAt.getMonth() + 13) - - const sessionId = "uuid" - - await maybeExtendSession({ - sessionId, - expiresAt: expiresAt.toISOString(), - }) - - // counter should not increase - expect(extendSession).toHaveBeenCalledTimes(1) - } -}) diff --git a/test/galoy/integration/app/wallets/add-invoice-for-wallet.spec.ts b/test/galoy/integration/app/wallets/add-invoice-for-wallet.spec.ts deleted file mode 100644 index 1ca277a4b..000000000 --- a/test/galoy/integration/app/wallets/add-invoice-for-wallet.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Accounts, Wallets } from "@app" - -import { AccountStatus } from "@domain/accounts" -import { InactiveAccountError } from "@domain/errors" - -import { AccountsRepository } from "@services/mongoose" - -import { createRandomUserAndBtcWallet } from "test/galoy/helpers" - -describe("addInvoice", () => { - it("fails for self if account is locked", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Lock account - const updatedAccount = await Accounts.updateAccountStatus({ - id: newAccount.id, - status: AccountStatus.Locked, - updatedByUserId: newAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Add invoice for self attempt - const selfRes = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newWalletDescriptor.id, - }) - expect(selfRes).toBeInstanceOf(InactiveAccountError) - - // Create invoice for recipient attempt - const recipientRes = await Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: newWalletDescriptor.id, - }) - expect(recipientRes).toBeInstanceOf(InactiveAccountError) - }) -}) diff --git a/test/galoy/integration/app/wallets/add-pending-on-chain-transaction.spec.ts b/test/galoy/integration/app/wallets/add-pending-on-chain-transaction.spec.ts deleted file mode 100644 index aecb90652..000000000 --- a/test/galoy/integration/app/wallets/add-pending-on-chain-transaction.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { addPendingTransaction } from "@app/wallets" - -import { WalletCurrency } from "@domain/shared" - -import { WalletOnChainAddressesRepository } from "@services/mongoose" -import { Wallet, WalletOnChainPendingReceive } from "@services/mongoose/schema" -import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" - -import { createRandomUserAndWallets } from "test/galoy/helpers" - -const address = "bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw" as OnChainAddress - -const btcAmount = { amount: 100_000n, currency: WalletCurrency.Btc } - -afterEach(async () => { - await WalletOnChainPendingReceive.deleteMany({}) - await Wallet.updateMany( - {}, - { $pull: { onchain: { address } } }, - { multi: true }, // This option updates all matching documents - ) -}) - -describe("addPendingTransaction", () => { - it("calls sendFilteredNotification on pending onchain receive", async () => { - // Setup mocks - const sendFilteredNotification = jest.fn() - const pushNotificationsServiceSpy = jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - // Create user - const { btcWalletDescriptor } = await createRandomUserAndWallets() - - // Add address to user wallet - await WalletOnChainAddressesRepository().persistNew({ - walletId: btcWalletDescriptor.id, - onChainAddress: { address }, - }) - - // Add pending transaction - await addPendingTransaction({ - txId: "txId" as OnChainTxHash, - vout: 0 as OnChainTxVout, - satoshis: btcAmount, - address, - }) - - // Expect sent notification - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBeTruthy() - - // Restore system state - pushNotificationsServiceSpy.mockRestore() - }) -}) diff --git a/test/galoy/integration/app/wallets/add-settled-on-chain-transaction.spec.ts b/test/galoy/integration/app/wallets/add-settled-on-chain-transaction.spec.ts deleted file mode 100644 index 0d3e43a74..000000000 --- a/test/galoy/integration/app/wallets/add-settled-on-chain-transaction.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { addSettledTransaction } from "@app/wallets" - -import { WalletCurrency } from "@domain/shared" -import { Transaction, TransactionMetadata } from "@services/ledger/schema" - -import { WalletOnChainAddressesRepository } from "@services/mongoose" -import { Wallet } from "@services/mongoose/schema" -import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" - -import { createMandatoryUsers, createRandomUserAndWallets } from "test/galoy/helpers" - -const address = "bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw" as OnChainAddress - -const btcAmount = { amount: 100_000n, currency: WalletCurrency.Btc } - -beforeAll(async () => { - await createMandatoryUsers() -}) - -afterEach(async () => { - await Transaction.deleteMany({}) - await TransactionMetadata.deleteMany({}) - await Wallet.updateMany( - {}, - { $pull: { onchain: { address } } }, - { multi: true }, // This option updates all matching documents - ) -}) - -describe("addSettledTransaction", () => { - it("calls sendFilteredNotification on successful onchain receive", async () => { - // Setup mocks - const sendFilteredNotification = jest.fn() - const pushNotificationsServiceSpy = jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - // Create user - const { btcWalletDescriptor } = await createRandomUserAndWallets() - - // Add address to user wallet - await WalletOnChainAddressesRepository().persistNew({ - walletId: btcWalletDescriptor.id, - onChainAddress: { address }, - }) - - // Add settled transaction - await addSettledTransaction({ - txId: "txId" as OnChainTxHash, - vout: 0 as OnChainTxVout, - satoshis: btcAmount, - address, - }) - - // Expect sent notification - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBeTruthy() - - // Restore system state - pushNotificationsServiceSpy.mockRestore() - }) -}) diff --git a/test/galoy/integration/app/wallets/create-on-chain-address.spec.ts b/test/galoy/integration/app/wallets/create-on-chain-address.spec.ts deleted file mode 100644 index 37de2d126..000000000 --- a/test/galoy/integration/app/wallets/create-on-chain-address.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Accounts, Wallets } from "@app" - -import { AccountStatus } from "@domain/accounts" -import { RateLimitConfig } from "@domain/rate-limit" -import { InactiveAccountError } from "@domain/errors" -import { - OnChainAddressCreateRateLimiterExceededError, - RateLimiterExceededError, -} from "@domain/rate-limit/errors" - -import { AccountsRepository } from "@services/mongoose" -import * as RateLimitImpl from "@services/rate-limit" - -import { createRandomUserAndBtcWallet } from "test/galoy/helpers" - -describe("onChainAddress", () => { - it("can apply requestId as idempotency key when creating new address", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - const requestId = ("requestId #" + - (Math.random() * 1_000_000).toFixed()) as OnChainAddressRequestId - - const address = await Wallets.createOnChainAddress({ - walletId: newWalletDescriptor.id, - requestId, - }) - - const retryCreateAddressWithRequestId = await Wallets.createOnChainAddress({ - walletId: newWalletDescriptor.id, - requestId, - }) - expect(retryCreateAddressWithRequestId).toBe(address) - - const retryCreateAddress = await Wallets.createOnChainAddress({ - walletId: newWalletDescriptor.id, - }) - expect(retryCreateAddress).not.toBe(address) - }) - - it("fails if account is locked", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Lock account - const updatedAccount = await Accounts.updateAccountStatus({ - id: newAccount.id, - status: AccountStatus.Locked, - updatedByUserId: newAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Create address attempt - const res = await Wallets.createOnChainAddress({ - walletId: newWalletDescriptor.id, - }) - expect(res).toBeInstanceOf(InactiveAccountError) - }) - - it("fails if rate limit is met", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Setup limiter mock - const { RedisRateLimitService } = jest.requireActual("@services/rate-limit") - const rateLimitServiceSpy = jest - .spyOn(RateLimitImpl, "RedisRateLimitService") - .mockReturnValue({ - ...RedisRateLimitService({ - keyPrefix: RateLimitConfig.onChainAddressCreate.key, - limitOptions: RateLimitConfig.onChainAddressCreate.limits, - }), - consume: () => new RateLimiterExceededError(), - }) - - // Create address attempt - const res = await Wallets.createOnChainAddress({ - walletId: newWalletDescriptor.id, - }) - expect(res).toBeInstanceOf(OnChainAddressCreateRateLimiterExceededError) - - // Restore system state - rateLimitServiceSpy.mockReset() - }) -}) diff --git a/test/galoy/integration/app/wallets/get-last-on-chain-address.spec.ts b/test/galoy/integration/app/wallets/get-last-on-chain-address.spec.ts deleted file mode 100644 index 1e1f516ef..000000000 --- a/test/galoy/integration/app/wallets/get-last-on-chain-address.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Accounts, Wallets } from "@app" - -import { AccountStatus } from "@domain/accounts" -import { InactiveAccountError } from "@domain/errors" - -import { AccountsRepository } from "@services/mongoose" - -import { createRandomUserAndBtcWallet } from "test/galoy/helpers" - -describe("onChainAddress", () => { - it("fails if account is locked", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Lock account - const updatedAccount = await Accounts.updateAccountStatus({ - id: newAccount.id, - status: AccountStatus.Locked, - updatedByUserId: newAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Create address attempt - const res = await Wallets.getLastOnChainAddress(newWalletDescriptor.id) - expect(res).toBeInstanceOf(InactiveAccountError) - }) -}) diff --git a/test/galoy/integration/app/wallets/get-pending-onchain-balance-for-wallets.spec.ts b/test/galoy/integration/app/wallets/get-pending-onchain-balance-for-wallets.spec.ts deleted file mode 100644 index 199b289db..000000000 --- a/test/galoy/integration/app/wallets/get-pending-onchain-balance-for-wallets.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Wallets } from "@app" - -import { ZERO_SATS } from "@domain/shared" -import { MultipleCurrenciesForSingleCurrencyOperationError } from "@domain/errors" - -import { WalletsRepository } from "@services/mongoose" - -import { createRandomUserAndBtcWallet, createRandomUserAndWallets } from "test/galoy/helpers" - -describe("getPendingOnChainBalanceForWallets", () => { - describe("with no pending incoming txns", () => { - it("returns zero balance", async () => { - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const wallet = await WalletsRepository().findById(newWalletDescriptor.id) - if (wallet instanceof Error) throw wallet - - const res = await Wallets.getPendingOnChainBalanceForWallets([wallet]) - expect(res).toStrictEqual({ [newWalletDescriptor.id]: ZERO_SATS }) - }) - - it("returns error for mixed wallet currencies", async () => { - const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - - const btcWallet = await WalletsRepository().findById(newWalletDescriptor.id) - if (btcWallet instanceof Error) throw btcWallet - const usdWallet = await WalletsRepository().findById(usdWalletDescriptor.id) - if (usdWallet instanceof Error) throw usdWallet - - const res = await Wallets.getPendingOnChainBalanceForWallets([btcWallet, usdWallet]) - expect(res).toBeInstanceOf(MultipleCurrenciesForSingleCurrencyOperationError) - }) - - it("returns error for no wallets passed", async () => { - const res = await Wallets.getPendingOnChainBalanceForWallets([]) - expect(res).toBeInstanceOf(MultipleCurrenciesForSingleCurrencyOperationError) - }) - }) -}) diff --git a/test/galoy/integration/app/wallets/send-intraledger.spec.ts b/test/galoy/integration/app/wallets/send-intraledger.spec.ts deleted file mode 100644 index a0156c821..000000000 --- a/test/galoy/integration/app/wallets/send-intraledger.spec.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { Accounts, Payments } from "@app" - -import { AccountStatus } from "@domain/accounts" -import { toSats } from "@domain/bitcoin" -import { PaymentSendStatus } from "@domain/bitcoin/lightning" -import { UsdDisplayCurrency, toCents } from "@domain/fiat" -import { - InactiveAccountError, - IntraledgerLimitsExceededError, - SelfPaymentError, - TradeIntraAccountLimitsExceededError, -} from "@domain/errors" - -import { AccountsRepository } from "@services/mongoose" -import { Transaction } from "@services/ledger/schema" -import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" - -import { AmountCalculator, WalletCurrency } from "@domain/shared" - -import { - createMandatoryUsers, - createRandomUserAndBtcWallet, - createRandomUserAndWallets, - recordReceiveLnPayment, -} from "test/galoy/helpers" - -let memo - -const calc = AmountCalculator() - -beforeAll(async () => { - await createMandatoryUsers() -}) - -beforeEach(() => { - memo = randomIntraLedgerMemo() -}) - -afterEach(async () => { - await Transaction.deleteMany({ memo }) - await Transaction.deleteMany({ memoPayer: memo }) -}) - -const amount = toSats(10040) -const btcPaymentAmount: BtcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, -} - -const usdAmount = toCents(210) -const usdPaymentAmount: UsdPaymentAmount = { - amount: BigInt(usdAmount), - currency: WalletCurrency.Usd, -} - -const receiveAmounts = { btc: calc.mul(btcPaymentAmount, 3n), usd: usdPaymentAmount } - -const receiveBankFee = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 1n, currency: WalletCurrency.Usd }, -} - -const receiveDisplayAmounts = { - amountDisplayCurrency: Number(receiveAmounts.usd.amount) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, -} - -const receiveAboveLimitAmounts = { - btc: { amount: 300_000_000n, currency: WalletCurrency.Btc }, - usd: { amount: 6_000_000n, currency: WalletCurrency.Usd }, -} -const receiveAboveLimitDisplayAmounts = { - amountDisplayCurrency: Number( - receiveAboveLimitAmounts.usd.amount, - ) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, -} - -const randomIntraLedgerMemo = () => - "this is my intraledger memo #" + (Math.random() * 1_000_000).toFixed() - -describe("intraLedgerPay", () => { - it("fails if sender account is locked", async () => { - const senderWalletDescriptor = await createRandomUserAndBtcWallet() - const senderAccount = await AccountsRepository().findById( - senderWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - const recipientWalletDescriptor = await createRandomUserAndBtcWallet() - const recipientAccount = await AccountsRepository().findById( - recipientWalletDescriptor.accountId, - ) - if (recipientAccount instanceof Error) throw recipientAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: senderWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Lock sender account - const updatedAccount = await Accounts.updateAccountStatus({ - id: senderAccount.id, - status: AccountStatus.Locked, - updatedByUserId: senderAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Initiate intraledger send - const res = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - senderWalletId: senderWalletDescriptor.id, - senderAccount: senderAccount, - recipientWalletId: recipientWalletDescriptor.id, - amount, - - memo, - }) - expect(res).toBeInstanceOf(InactiveAccountError) - }) - - it("fails if recipient account is locked", async () => { - const senderWalletDescriptor = await createRandomUserAndBtcWallet() - const senderAccount = await AccountsRepository().findById( - senderWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - const recipientWalletDescriptor = await createRandomUserAndBtcWallet() - const recipientAccount = await AccountsRepository().findById( - recipientWalletDescriptor.accountId, - ) - if (recipientAccount instanceof Error) throw recipientAccount - - // Lock recipient account - const updatedAccount = await Accounts.updateAccountStatus({ - id: recipientAccount.id, - status: AccountStatus.Locked, - updatedByUserId: recipientAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: senderWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - const res = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - senderWalletId: senderWalletDescriptor.id, - senderAccount: senderAccount, - recipientWalletId: recipientWalletDescriptor.id, - amount, - - memo, - }) - expect(res).toBeInstanceOf(InactiveAccountError) - }) - - it("fails if sends to self", async () => { - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Pay intraledger - const paymentResult = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: newWalletDescriptor.id, - memo, - amount, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(SelfPaymentError) - }) - - it("fails if amount greater than trade-intra-account limit", async () => { - // Create users - const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: usdWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - // Pay intraledger - const paymentResult = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: usdWalletDescriptor.id, - memo, - amount: toSats(receiveAboveLimitAmounts.btc.amount), - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(TradeIntraAccountLimitsExceededError) - }) - - it("fails if amount greater than intraledger limit", async () => { - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const recipientWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: recipientWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - // Pay intraledger - const paymentResult = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: recipientWalletDescriptor.id, - memo, - amount: toSats(receiveAboveLimitAmounts.btc.amount), - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(IntraledgerLimitsExceededError) - }) - - it("calls sendFilteredNotification on successful intraledger send", async () => { - // Setup mocks - const sendFilteredNotification = jest.fn() - const pushNotificationsServiceSpy = jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - // Create users - const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - const newAccount = await AccountsRepository().findById(newWalletDescriptor.accountId) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Pay intraledger - const paymentResult = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: usdWalletDescriptor.id, - memo, - amount, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toEqual(PaymentSendStatus.Success) - - // Expect sent notification - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBeTruthy() - - // Restore system state - pushNotificationsServiceSpy.mockReset() - }) -}) diff --git a/test/galoy/integration/app/wallets/send-lightning.spec.ts b/test/galoy/integration/app/wallets/send-lightning.spec.ts deleted file mode 100644 index e871fa577..000000000 --- a/test/galoy/integration/app/wallets/send-lightning.spec.ts +++ /dev/null @@ -1,813 +0,0 @@ -import { Accounts, Payments } from "@app" - -import { AccountStatus } from "@domain/accounts" -import { toSats } from "@domain/bitcoin" -import { - MaxFeeTooLargeForRoutelessPaymentError, - PaymentSendStatus, - decodeInvoice, -} from "@domain/bitcoin/lightning" -import { UsdDisplayCurrency, toCents } from "@domain/fiat" -import { LnPaymentRequestNonZeroAmountRequiredError } from "@domain/payments" -import { - InactiveAccountError, - InsufficientBalanceError, - IntraledgerLimitsExceededError, - SelfPaymentError, - TradeIntraAccountLimitsExceededError, - WithdrawalLimitsExceededError, -} from "@domain/errors" -import { AmountCalculator, WalletCurrency } from "@domain/shared" -import * as LnFeesImpl from "@domain/payments" - -import { - AccountsRepository, - LnPaymentsRepository, - WalletInvoicesRepository, -} from "@services/mongoose" -import { LedgerService } from "@services/ledger" -import { Transaction, TransactionMetadata } from "@services/ledger/schema" -import { WalletInvoice } from "@services/mongoose/schema" -import { LnPayment } from "@services/lnd/schema" -import * as LndImpl from "@services/lnd" -import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" - -import { - createMandatoryUsers, - createRandomUserAndBtcWallet, - createRandomUserAndWallets, - getBalanceHelper, - recordReceiveLnPayment, -} from "test/galoy/helpers" - -let lnInvoice: LnInvoice -let noAmountLnInvoice: LnInvoice -let largeWithAmountLnInvoice: LnInvoice -let memo - -const calc = AmountCalculator() - -const DEFAULT_PUBKEY = - "03ca1907342d5d37744cb7038375e1867c24a87564c293157c95b2a9d38dcfb4c2" as Pubkey - -beforeAll(async () => { - await createMandatoryUsers() - - const randomRequest = - "lnbcrt10n1p39jatkpp5djwv295kunhe5e0e4whj3dcjzwy7cmcxk8cl2a4dquyrp3dqydesdqqcqzpuxqr23ssp56u5m680x7resnvcelmsngc64ljm7g5q9r26zw0qyq5fenuqlcfzq9qyyssqxv4kvltas2qshhmqnjctnqkjpdfzu89e428ga6yk9jsp8rf382f3t03ex4e6x3a4sxkl7ruj6lsfpkuu9u9ee5kgr5zdyj7x2nwdljgq74025p" - const invoice = decodeInvoice(randomRequest) - if (invoice instanceof Error) throw invoice - lnInvoice = invoice - - const randomNoAmountRequest = - "lnbcrt1pjd9dmfpp5rf6q3rdstzcflshyux9dp05ft86xldx5s3ht99slsneneuefsjhsdqqcqzzsxqyz5vqsp5dl52mgulmljxlng5eafs7n3f54teg858dth67exxvk7wsgh62t6q9qyyssqjqekrkdga0uqnd0fv5dzhuky0l2wnmzr4q846x7grtw75zejla68pjh7vww2y6qvhx576yfexj8x24my72vj2y5929w5lju0f6fpnegp08kdm0" - const noAmountInvoice = decodeInvoice(randomNoAmountRequest) - if (noAmountInvoice instanceof Error) throw noAmountInvoice - noAmountLnInvoice = noAmountInvoice - - const largeWithAmountRequest = - "lnbcrt31pjdlc2mpp54seydar5l4pz20aq4ngmdp8ghx4s63476yrpy0a04l534g5u3ueqdqqcqzzsxqyz5vqsp5v45qm8fzn5r7hw7qcku0a92qrmfrsycqjwahue3vetyx9cljgeks9qyyssqzdpd0pq7m9qpy5v7r50yswmx57y7uh2q4czrz7cesxhz0rg52y8h6vp2e7jy9vsffxqjxtu82y58smj48f427up8kmlxql4m3r8pn8cq8yhwzl" - const largeWithAmountInvoice = decodeInvoice(largeWithAmountRequest) - if (largeWithAmountInvoice instanceof Error) throw largeWithAmountInvoice - largeWithAmountLnInvoice = largeWithAmountInvoice -}) - -beforeEach(async () => { - memo = randomLightningMemo() - await LnPayment.deleteMany({}) -}) - -afterEach(async () => { - await Transaction.deleteMany({}) - await TransactionMetadata.deleteMany({}) - await WalletInvoice.deleteMany({}) - await LnPayment.deleteMany({}) -}) - -const amount = toSats(10040) -const btcPaymentAmount: BtcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, -} - -const usdAmount = toCents(210) -const usdPaymentAmount: UsdPaymentAmount = { - amount: BigInt(usdAmount), - currency: WalletCurrency.Usd, -} - -const receiveAmounts = { btc: calc.mul(btcPaymentAmount, 3n), usd: usdPaymentAmount } - -const receiveBankFee = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 1n, currency: WalletCurrency.Usd }, -} - -const receiveDisplayAmounts = { - amountDisplayCurrency: Number(receiveAmounts.usd.amount) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, -} - -const receiveAboveLimitAmounts = { - btc: { amount: 300_000_000n, currency: WalletCurrency.Btc }, - usd: { amount: 6_000_000n, currency: WalletCurrency.Usd }, -} -const receiveAboveLimitDisplayAmounts = { - amountDisplayCurrency: Number( - receiveAboveLimitAmounts.usd.amount, - ) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, -} - -const randomLightningMemo = () => - "this is my lightning memo #" + (Math.random() * 1_000_000).toFixed() - -describe("initiated via lightning", () => { - describe("fee probe", () => { - it("fails if amount greater than limit", async () => { - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - // Execute probe - const { error } = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - walletId: newWalletDescriptor.id, - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - - amount: toSats(receiveAboveLimitAmounts.btc.amount), - }) - expect(error).toBeInstanceOf(WithdrawalLimitsExceededError) - }) - }) - - describe("settles via lightning", () => { - it("fails if sender account is locked", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Lock sender account - const updatedAccount = await Accounts.updateAccountStatus({ - id: newAccount.id, - status: AccountStatus.Locked, - updatedByUserId: newAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Attempt send payment - const res = await Payments.payInvoiceByWalletId({ - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - uncheckedPaymentRequest: lnInvoice.paymentRequest, - - memo, - }) - expect(res).toBeInstanceOf(InactiveAccountError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails when user has insufficient balance", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Attempt pay - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(InsufficientBalanceError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails to pay zero amount invoice without separate amount", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Attempt pay - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(LnPaymentRequestNonZeroAmountRequiredError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails if user sends balance amount without accounting for fee", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Attempt pay - const balance = await getBalanceHelper(newWalletDescriptor.id) - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - amount: balance, - }) - expect(paymentResult).toBeInstanceOf(InsufficientBalanceError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails if amount greater than limit", async () => { - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - // Attempt pay with invoice with amount - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: largeWithAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(WithdrawalLimitsExceededError) - - // Attempt pay with no amount invoice - const noAmountPaymentResult = - await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - - amount: toSats(receiveAboveLimitAmounts.btc.amount), - }) - expect(noAmountPaymentResult).toBeInstanceOf(WithdrawalLimitsExceededError) - }) - - it("pay zero amount invoice & revert txn when verifyMaxFee fails", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [], - defaultPubkey: (): Pubkey => DEFAULT_PUBKEY, - }) - - const { LnFees: LnFeesOrig } = jest.requireActual("@domain/payments") - const lndFeesSpy = jest.spyOn(LnFeesImpl, "LnFees").mockReturnValue({ - ...LnFeesOrig(), - verifyMaxFee: () => new MaxFeeTooLargeForRoutelessPaymentError(), - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Attempt pay - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - - // Expect transaction to be canceled - const txns = await LedgerService().getTransactionsByHash(lnInvoice.paymentHash) - if (txns instanceof Error) throw txns - - const { satsAmount, satsFee } = txns[0] - expect(txns.length).toEqual(2) - expect(txns).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - lnMemo: "Payment canceled", - credit: (satsAmount || 0) + (satsFee || 0), - debit: 0, - pendingConfirmation: false, - }), - expect.objectContaining({ - lnMemo: memo, - debit: (satsAmount || 0) + (satsFee || 0), - credit: 0, - pendingConfirmation: false, - }), - ]), - ) - - // Restore system state - lndFeesSpy.mockRestore() - lndServiceSpy.mockRestore() - }) - - it("persists ln-payment on successful ln send", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - defaultPubkey: (): Pubkey => DEFAULT_PUBKEY, - listAllPubkeys: () => [], - payInvoiceViaPaymentDetails: () => ({ - roundedUpFee: toSats(0), - revealedPreImage: "revealedPreImage" as RevealedPreImage, - sentFromPubkey: DEFAULT_PUBKEY, - }), - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Execute pay - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - amount, - }) - expect(paymentResult).toEqual(PaymentSendStatus.Success) - - // Check lnPayment collection after - const lnPaymentAfter = await LnPaymentsRepository().findByPaymentHash( - noAmountLnInvoice.paymentHash, - ) - if (lnPaymentAfter instanceof Error) throw lnPaymentAfter - expect(lnPaymentAfter.paymentHash).toEqual(noAmountLnInvoice.paymentHash) - - // Restore system state - lndServiceSpy.mockRestore() - }) - }) - - describe("settles intraledger", () => { - it("fails if recipient account is locked", async () => { - const { paymentHash, destination } = lnInvoice - - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [destination], - }) - - // Setup users and wallets - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - const recipientWalletDescriptor = await createRandomUserAndBtcWallet() - const recipientAccount = await AccountsRepository().findById( - recipientWalletDescriptor.accountId, - ) - if (recipientAccount instanceof Error) throw recipientAccount - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Add recipient invoice - const persisted = await WalletInvoicesRepository().persistNew({ - paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: destination, - recipientWalletDescriptor, - paid: false, - }) - if (persisted instanceof Error) throw persisted - - // Lock recipient account - const updatedAccount = await Accounts.updateAccountStatus({ - id: recipientAccount.id, - status: AccountStatus.Locked, - updatedByUserId: recipientAccount.kratosUserId, - }) - if (updatedAccount instanceof Error) throw updatedAccount - expect(updatedAccount.status).toEqual(AccountStatus.Locked) - - // Attempt send payment - const res = await Payments.payInvoiceByWalletId({ - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - uncheckedPaymentRequest: lnInvoice.paymentRequest, - - memo, - }) - expect(res).toBeInstanceOf(InactiveAccountError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails if sends to self", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [lnInvoice.destination], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Persist invoice as self-invoice - const persisted = await WalletInvoicesRepository().persistNew({ - paymentHash: lnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: lnInvoice.destination, - recipientWalletDescriptor: newWalletDescriptor, - paid: false, - }) - if (persisted instanceof Error) throw persisted - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Attempt pay - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(SelfPaymentError) - - // Restore system state - lndServiceSpy.mockRestore() - }) - - it("fails if amount greater than trade-intra-account limit", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [ - noAmountLnInvoice.destination, - largeWithAmountLnInvoice.destination, - ], - }) - - // Create users - const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - expect(largeWithAmountLnInvoice.paymentAmount).toStrictEqual( - receiveAboveLimitAmounts.btc, - ) - const usdAmount = receiveAboveLimitAmounts.usd - - // Persist invoice as self-invoice - const persisted = await WalletInvoicesRepository().persistNew({ - paymentHash: largeWithAmountLnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: largeWithAmountLnInvoice.destination, - recipientWalletDescriptor: usdWalletDescriptor, - paid: false, - usdAmount, - }) - if (persisted instanceof Error) throw persisted - - // Attempt pay with invoice with amount - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: largeWithAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(TradeIntraAccountLimitsExceededError) - - // Persist no-amount invoice as self-invoice - const noAmountPersisted = await WalletInvoicesRepository().persistNew({ - paymentHash: noAmountLnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: noAmountLnInvoice.destination, - recipientWalletDescriptor: usdWalletDescriptor, - paid: false, - }) - if (noAmountPersisted instanceof Error) throw noAmountPersisted - - // Attempt pay with no-amount invoice - const noAmountPaymentResult = - await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - - amount: toSats(receiveAboveLimitAmounts.btc.amount), - }) - expect(noAmountPaymentResult).toBeInstanceOf(TradeIntraAccountLimitsExceededError) - - // Restore system state - lndServiceSpy.mockReset() - }) - - it("fails if amount greater than intraledger limit", async () => { - // Setup mocks - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [ - noAmountLnInvoice.destination, - largeWithAmountLnInvoice.destination, - ], - }) - - // Create users - const newWalletDescriptor = await createRandomUserAndBtcWallet() - const otherWalletDescriptor = await createRandomUserAndBtcWallet() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Fund balance for send - for (let i = 0; i < 2; i++) { - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAboveLimitAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveAboveLimitDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - } - - // Persist invoice as self-invoice - const persisted = await WalletInvoicesRepository().persistNew({ - paymentHash: largeWithAmountLnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: largeWithAmountLnInvoice.destination, - recipientWalletDescriptor: otherWalletDescriptor, - paid: false, - }) - if (persisted instanceof Error) throw persisted - - // Attempt pay with invoice with amount - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: largeWithAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - }) - expect(paymentResult).toBeInstanceOf(IntraledgerLimitsExceededError) - - // Persist no-amount invoice as self-invoice - const noAmountPersisted = await WalletInvoicesRepository().persistNew({ - paymentHash: noAmountLnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: noAmountLnInvoice.destination, - recipientWalletDescriptor: otherWalletDescriptor, - paid: false, - }) - if (noAmountPersisted instanceof Error) throw noAmountPersisted - - // Attempt pay with no-amount invoice - const noAmountPaymentResult = - await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - - amount: toSats(receiveAboveLimitAmounts.btc.amount), - }) - expect(noAmountPaymentResult).toBeInstanceOf(IntraledgerLimitsExceededError) - - // Restore system state - lndServiceSpy.mockReset() - }) - - it("calls sendFilteredNotification on successful intraledger send", async () => { - // Setup mocks - const sendFilteredNotification = jest.fn() - const pushNotificationsServiceSpy = jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - const { LndService: LnServiceOrig } = jest.requireActual("@services/lnd") - const lndServiceSpy = jest.spyOn(LndImpl, "LndService").mockReturnValue({ - ...LnServiceOrig(), - listAllPubkeys: () => [noAmountLnInvoice.destination], - cancelInvoice: () => true, - }) - - // Create users - const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - const newAccount = await AccountsRepository().findById( - newWalletDescriptor.accountId, - ) - if (newAccount instanceof Error) throw newAccount - - // Persist invoice as self-invoice - const persisted = await WalletInvoicesRepository().persistNew({ - paymentHash: noAmountLnInvoice.paymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: true, - pubkey: noAmountLnInvoice.destination, - recipientWalletDescriptor: usdWalletDescriptor, - paid: false, - }) - if (persisted instanceof Error) throw persisted - - // Fund balance for send - const receive = await recordReceiveLnPayment({ - walletDescriptor: newWalletDescriptor, - paymentAmount: receiveAmounts, - bankFee: receiveBankFee, - displayAmounts: receiveDisplayAmounts, - memo, - }) - if (receive instanceof Error) throw receive - - // Execute pay - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: noAmountLnInvoice.paymentRequest, - memo, - senderWalletId: newWalletDescriptor.id, - senderAccount: newAccount, - amount, - }) - expect(paymentResult).toEqual(PaymentSendStatus.Success) - - // Expect sent notification - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBeTruthy() - - // Restore system state - pushNotificationsServiceSpy.mockRestore() - lndServiceSpy.mockRestore() - }) - }) -}) diff --git a/test/galoy/integration/app/wallets/send-onchain.spec.ts b/test/galoy/integration/app/wallets/send-onchain.spec.ts deleted file mode 100644 index 26c5b4732..000000000 --- a/test/galoy/integration/app/wallets/send-onchain.spec.ts +++ /dev/null @@ -1,710 +0,0 @@ -// import { Accounts, Prices, Wallets } from "@app" - -// import { getAccountLimits, getOnChainWalletConfig, ONE_DAY } from "@config" - -// import { AccountStatus } from "@domain/accounts" -// import { toSats } from "@domain/bitcoin" -// import { toCents, UsdDisplayCurrency } from "@domain/fiat" -// import { -// InactiveAccountError, -// InsufficientBalanceError, -// LessThanDustThresholdError, -// LimitsExceededError, -// SelfPaymentError, -// } from "@domain/errors" -// import { SubOneCentSatAmountForUsdSelfSendError } from "@domain/payments" -// import { -// AmountCalculator, -// WalletCurrency, -// InvalidBtcPaymentAmountError, -// } from "@domain/shared" - -// import { PayoutSpeed } from "@domain/bitcoin/onchain" -// import { PaymentSendStatus } from "@domain/bitcoin/lightning" - -// import { AccountsRepository } from "@services/mongoose" -// import { Transaction, TransactionMetadata } from "@services/ledger/schema" -// import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" -// import Ibex from "@services/ibex/client" - -// import { DealerPriceService } from "@services/dealer-price" - -// import { timestampDaysAgo } from "@utils" - -// import { -// createMandatoryUsers, -// createRandomUserAndBtcWallet, -// createRandomUserAndWallets, -// recordReceiveLnPayment, -// } from "test/helpers" -// import { getBalanceHelper } from "test/helpers/wallet" -// import { create } from "domain" - -// let outsideAddress: OnChainAddress -// let memo - -// const dealerFns = DealerPriceService() - -// const calc = AmountCalculator() - -// // Flash Fork: mock the Ibex service -// jest.mock('@services/ibex') - -// beforeAll(async () => { -// await createMandatoryUsers() - -// // outsideAddress = "bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw" as OnChainAddress -// }) - -// beforeEach(async () => { -// memo = randomOnChainMemo() -// }) - -// afterEach(async () => { -// jest.clearAllMocks() // replaces fn.mock.calls and fn.mock.instances -// await Transaction.deleteMany() -// await TransactionMetadata.deleteMany() -// }) - -// const amount = toSats(10040) -// const btcPaymentAmount: BtcPaymentAmount = { -// amount: BigInt(amount), -// currency: WalletCurrency.Btc, -// } - -// const usdAmount = toCents(210) -// const usdPaymentAmount: UsdPaymentAmount = { -// amount: BigInt(usdAmount), -// currency: WalletCurrency.Usd, -// } - -// const receiveAmounts = { btc: calc.mul(btcPaymentAmount, 3n), usd: usdPaymentAmount } - -// const receiveBankFee = { -// btc: { amount: 100n, currency: WalletCurrency.Btc }, -// usd: { amount: 1n, currency: WalletCurrency.Usd }, -// } - -// const receiveDisplayAmounts = { -// amountDisplayCurrency: Number(receiveAmounts.usd.amount) as DisplayCurrencyBaseAmount, -// feeDisplayCurrency: Number(receiveBankFee.usd.amount) as DisplayCurrencyBaseAmount, -// displayCurrency: UsdDisplayCurrency, -// } - -// const amountBelowDustThreshold = getOnChainWalletConfig().dustThreshold - 1 - -// const randomOnChainMemo = () => -// "this is my onchain memo #" + (Math.random() * 1_000_000).toFixed() - -// describe("onChainPay", () => { -// describe("common", () => { -// it.skip("fails to send all from empty wallet", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Execute use-case -// const res = await Wallets.payAllOnChainByWalletId({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: outsideAddress, -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(res).toBeInstanceOf(InsufficientBalanceError) -// expect(res instanceof Error && res.message).toEqual(`No balance left to send.`) -// }) - -// it.skip("fails if 'validatePaymentInput' fails", async () => { -// const amount = -1000 - -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: outsideAddress, -// amount, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(result).toBeInstanceOf(InvalidBtcPaymentAmountError) -// }) -// }) - -// describe("settles onchain", () => { -// it.skip("fails if builder 'withConversion' step fails", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: outsideAddress, -// amount: amountBelowDustThreshold, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(result).toBeInstanceOf(LessThanDustThresholdError) -// }) - -// it.skip("fails if withdrawal limit hit", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Get remaining limit -// const timestamp1DayAgo = timestampDaysAgo(ONE_DAY) -// if (timestamp1DayAgo instanceof Error) throw timestamp1DayAgo - -// const withdrawalLimit = getAccountLimits({ -// level: newAccount.level, -// }).withdrawalLimit -// const withdrawalLimitUsdAmount = { -// amount: BigInt(withdrawalLimit), -// currency: WalletCurrency.Usd, -// } -// const walletPriceRatio = await Prices.getCurrentPriceAsWalletPriceRatio({ -// currency: UsdDisplayCurrency, -// }) -// if (walletPriceRatio instanceof Error) throw walletPriceRatio -// const withdrawalLimitBtcAmount = walletPriceRatio.convertFromUsd( -// withdrawalLimitUsdAmount, -// ) - -// // Fund wallet past remaining limit -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: { -// usd: calc.mul(withdrawalLimitUsdAmount, 2n), -// btc: calc.mul(withdrawalLimitBtcAmount, 2n), -// }, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: outsideAddress, -// amount: Number(withdrawalLimitBtcAmount.amount) + 10_000, - -// speed: PayoutSpeed.Fast, -// memo, -// }) - -// expect(result).toBeInstanceOf(LimitsExceededError) -// }) - -// it.skip("fails if has insufficient balance for fee", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const balance = await getBalanceHelper(newWalletDescriptor.id) - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: outsideAddress, -// amount: balance, - -// speed: PayoutSpeed.Fast, -// memo, -// }) - -// //should fail because user does not have balance to pay for on-chain fee -// expect(result).toBeInstanceOf(InsufficientBalanceError) -// }) - -// it.skip("fails if sender account is locked", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// // Lock sender account -// const updatedAccount = await Accounts.updateAccountStatus({ -// id: newAccount.id, -// status: AccountStatus.Locked, -// updatedByUserId: newAccount.kratosUserId, -// }) -// if (updatedAccount instanceof Error) throw updatedAccount -// expect(updatedAccount.status).toEqual(AccountStatus.Locked) - -// // Attempt send payment -// const res = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// amount, -// address: outsideAddress, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(res).toBeInstanceOf(InactiveAccountError) -// }) - -// // How to send a graphql query to the server with a mocked Ibex response -// it("mocks an Ibex success response", async () => { -// // Setup mocks -// // const getAccountTransactions = jest.fn() -// // const getTransactionDetails = jest.fn() -// // const createAccount = jest.fn() -// // const getAccountDetails = jest.fn() -// // const generateBitcoinAddress = jest.fn() -// // const addInvoice = jest.fn() -// // const invoiceFromHash = jest.fn() -// // const getFeeEstimation = jest.fn() -// // const payInvoiceV2 = jest.fn() -// // const sendToAddressV2 = jest.fn() -// // const estimateFeeV2 = jest.fn() -// // const createLnurlPay = jest.fn() -// // const decodeLnurl = jest.fn() -// // const payToLnurl = jest.fn() - -// // const IbexSpy = jest -// // .spyOn(IbexImpl, "default") -// // .mockImplementationOnce(() => ({ -// // getAccountTransactions: getAccountTransactions, -// // getTransactionDetails: getTransactionDetails, -// // createAccount: createAccount, -// // getAccountDetails: getAccountDetails, -// // generateBitcoinAddress: generateBitcoinAddress, -// // addInvoice: addInvoice, -// // invoiceFromHash: invoiceFromHash, -// // getFeeEstimation: getFeeEstimation, -// // payInvoiceV2: payInvoiceV2, -// // sendToAddressV2: sendToAddressV2, -// // estimateFeeV2: estimateFeeV2, -// // createLnurlPay: createLnurlPay, -// // decodeLnurl: decodeLnurl, -// // payToLnurl: payToLnurl -// // })) - -// // Create users -// const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = -// await createRandomUserAndWallets() -// const newAccount = await AccountsRepository().findById( -// usdWalletDescriptor.accountId, -// ) - -// // Mocked Ibex response -// const ibexResp = { id: 1, name: 'John Doe' }; -// Ibex().sendToAddressV2.mockResolvedValue(mockedUser); - -// if (newAccount instanceof Error) throw newAccount -// const output = await Wallets.payOnChainByWalletIdForUsdWallet({ -// senderAccount: newAccount, -// senderWalletId: usdWalletDescriptor.id, -// amount: amount, -// address: outsideAddress, -// speed: PayoutSpeed.Fast, // unused by -// memo, -// }) - -// // Expect sent notification -// expect(sendToAddressV2.mock.calls.length).toBe(1) -// expect(sendToAddressV2.mock.calls[0][0].title).toBeTruthy() - -// // Restore system state - - -// // // Fund balance for send -// // const receive = await recordReceiveLnPayment({ -// // walletDescriptor: newWalletDescriptor, -// // paymentAmount: receiveAmounts, -// // bankFee: receiveBankFee, -// // displayAmounts: receiveDisplayAmounts, -// // memo, -// // }) -// // if (receive instanceof Error) throw receive - -// // const recipientWalletIdAddress = await Wallets.createOnChainAddress({ -// // walletId: usdWalletDescriptor.id, -// // }) -// // if (recipientWalletIdAddress instanceof Error) throw recipientWalletIdAddress - -// // // Execute payment -// // const paymentResult = await Wallets.payOnChainByWalletIdForBtcWallet({ -// // senderWalletId: newWalletDescriptor.id, -// // senderAccount: newAccount, -// // amount, -// // address: recipientWalletIdAddress, - -// // speed: PayoutSpeed.Fast, -// // memo, -// // }) -// // if (paymentResult instanceof Error) throw paymentResult -// // expect(paymentResult.status).toEqual(PaymentSendStatus.Success) - -// }) -// }) - -// describe("settles intraledger", () => { -// it("fails if builder 'withRecipient' step fails", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const newWalletIdAddress = await Wallets.createOnChainAddress({ -// walletId: newWalletDescriptor.id, -// }) -// if (newWalletIdAddress instanceof Error) throw newWalletIdAddress - -// const res = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// amount, -// address: newWalletIdAddress, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(res).toBeInstanceOf(SelfPaymentError) -// }) - -// it("fails if builder 'withConversion' step fails", async () => { -// const { -// btcWalletDescriptor: newWalletDescriptor, -// usdWalletDescriptor: recipientUsdWalletDescriptor, -// } = await createRandomUserAndWallets() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// // Execute -// const btcSendAmount = toSats(10) - -// const recipientUsdWalletIdAddress = await Wallets.createOnChainAddress({ -// walletId: recipientUsdWalletDescriptor.id, -// }) -// if (recipientUsdWalletIdAddress instanceof Error) throw recipientUsdWalletIdAddress - -// const usdAmount = await dealerFns.getCentsFromSatsForImmediateBuy({ -// amount: BigInt(btcSendAmount), -// currency: WalletCurrency.Btc, -// }) -// if (usdAmount instanceof Error) return usdAmount -// const btcSendAmountInUsd = Number(usdAmount.amount) -// expect(btcSendAmountInUsd).toBe(0) - -// const res = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address: recipientUsdWalletIdAddress, -// amount: btcSendAmount, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(res).toBeInstanceOf(SubOneCentSatAmountForUsdSelfSendError) -// }) - -// it("fails if intraledger limit hit", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Create recipient address -// const recipientWalletDescriptor = await createRandomUserAndBtcWallet() -// const address = await Wallets.createOnChainAddress({ -// walletId: recipientWalletDescriptor.id, -// }) -// if (address instanceof Error) throw address - -// // Get remaining limit -// const timestamp1DayAgo = timestampDaysAgo(ONE_DAY) -// if (timestamp1DayAgo instanceof Error) throw timestamp1DayAgo - -// const intraLedgerLimit = getAccountLimits({ -// level: newAccount.level, -// }).intraLedgerLimit -// const intraLedgerLimitUsdAmount = { -// amount: BigInt(intraLedgerLimit), -// currency: WalletCurrency.Usd, -// } -// const walletPriceRatio = await Prices.getCurrentPriceAsWalletPriceRatio({ -// currency: UsdDisplayCurrency, -// }) -// if (walletPriceRatio instanceof Error) throw walletPriceRatio -// const intraLedgerLimitBtcAmount = walletPriceRatio.convertFromUsd( -// intraLedgerLimitUsdAmount, -// ) - -// // Fund wallet past remaining limit -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: { -// usd: calc.mul(intraLedgerLimitUsdAmount, 2n), -// btc: calc.mul(intraLedgerLimitBtcAmount, 2n), -// }, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address, -// amount: Number(intraLedgerLimitBtcAmount.amount) + 10_000, - -// speed: PayoutSpeed.Fast, -// memo, -// }) - -// expect(result).toBeInstanceOf(LimitsExceededError) -// }) - -// it("fails if trade-intra-account limit hit", async () => { -// const { -// btcWalletDescriptor: newWalletDescriptor, -// usdWalletDescriptor: recipientWalletDescriptor, -// } = await createRandomUserAndWallets() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Create recipient address -// const address = await Wallets.createOnChainAddress({ -// walletId: recipientWalletDescriptor.id, -// }) -// if (address instanceof Error) throw address - -// // Get remaining limit -// const timestamp1DayAgo = timestampDaysAgo(ONE_DAY) -// if (timestamp1DayAgo instanceof Error) throw timestamp1DayAgo - -// const tradeIntraAccountLimit = getAccountLimits({ -// level: newAccount.level, -// }).tradeIntraAccountLimit -// const tradeIntraAccountLimitUsdAmount = { -// amount: BigInt(tradeIntraAccountLimit), -// currency: WalletCurrency.Usd, -// } - -// const hedgeBuyUsdBtcFromUsd = DealerPriceService().getSatsFromCentsForImmediateBuy -// const tradeIntraAccountLimitBtcAmount = await hedgeBuyUsdBtcFromUsd( -// tradeIntraAccountLimitUsdAmount, -// ) -// if (tradeIntraAccountLimitBtcAmount instanceof Error) { -// throw tradeIntraAccountLimitBtcAmount -// } - -// // Fund wallet past remaining limit -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: { -// usd: calc.mul(tradeIntraAccountLimitUsdAmount, 2n), -// btc: calc.mul(tradeIntraAccountLimitBtcAmount, 2n), -// }, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const result = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// address, -// amount: Number(tradeIntraAccountLimitBtcAmount.amount) + 10_000, - -// speed: PayoutSpeed.Fast, -// memo, -// }) - -// expect(result).toBeInstanceOf(LimitsExceededError) -// }) - -// it("fails if recipient account is locked", async () => { -// const newWalletDescriptor = await createRandomUserAndBtcWallet() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// const recipientWalletDescriptor = await createRandomUserAndBtcWallet() -// const recipientAccount = await AccountsRepository().findById( -// recipientWalletDescriptor.accountId, -// ) -// if (recipientAccount instanceof Error) throw recipientAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const recipientWalletIdAddress = await Wallets.createOnChainAddress({ -// walletId: recipientWalletDescriptor.id, -// }) -// if (recipientWalletIdAddress instanceof Error) throw recipientWalletIdAddress - -// // Lock recipient account -// const updatedAccount = await Accounts.updateAccountStatus({ -// id: recipientAccount.id, -// status: AccountStatus.Locked, -// updatedByUserId: recipientAccount.kratosUserId, -// }) -// if (updatedAccount instanceof Error) throw updatedAccount -// expect(updatedAccount.status).toEqual(AccountStatus.Locked) - -// // Attempt payment -// const res = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// amount, -// address: recipientWalletIdAddress, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// expect(res).toBeInstanceOf(InactiveAccountError) -// }) - -// it("calls sendFilteredNotification on successful intraledger receive", async () => { -// // Setup mocks -// const sendFilteredNotification = jest.fn() -// const pushNotificationsServiceSpy = jest -// .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") -// .mockImplementationOnce(() => ({ -// sendFilteredNotification, -// sendNotification: jest.fn(), -// })) - -// // Create users -// const { btcWalletDescriptor: newWalletDescriptor, usdWalletDescriptor } = -// await createRandomUserAndWallets() -// const newAccount = await AccountsRepository().findById( -// newWalletDescriptor.accountId, -// ) -// if (newAccount instanceof Error) throw newAccount - -// // Fund balance for send -// const receive = await recordReceiveLnPayment({ -// walletDescriptor: newWalletDescriptor, -// paymentAmount: receiveAmounts, -// bankFee: receiveBankFee, -// displayAmounts: receiveDisplayAmounts, -// memo, -// }) -// if (receive instanceof Error) throw receive - -// const recipientWalletIdAddress = await Wallets.createOnChainAddress({ -// walletId: usdWalletDescriptor.id, -// }) -// if (recipientWalletIdAddress instanceof Error) throw recipientWalletIdAddress - -// // Execute payment -// const paymentResult = await Wallets.payOnChainByWalletIdForBtcWallet({ -// senderWalletId: newWalletDescriptor.id, -// senderAccount: newAccount, -// amount, -// address: recipientWalletIdAddress, - -// speed: PayoutSpeed.Fast, -// memo, -// }) -// if (paymentResult instanceof Error) throw paymentResult -// expect(paymentResult.status).toEqual(PaymentSendStatus.Success) - -// // Expect sent notification -// expect(sendFilteredNotification.mock.calls.length).toBe(1) -// expect(sendFilteredNotification.mock.calls[0][0].title).toBeTruthy() - -// // Restore system state -// pushNotificationsServiceSpy.mockRestore() -// }) -// }) -// }) diff --git a/test/galoy/integration/jest.config.js b/test/galoy/integration/jest.config.js deleted file mode 100644 index 60e84181f..000000000 --- a/test/galoy/integration/jest.config.js +++ /dev/null @@ -1,29 +0,0 @@ -// // eslint-disable-next-line @typescript-eslint/no-var-requires -// const swcConfig = require("../swc-config.json") - -// module.exports = { -// moduleFileExtensions: ["js", "json", "ts", "cjs", "mjs"], -// rootDir: "../../", -// roots: ["/test/integration"], -// transform: { -// "^.+\\.(t|j)sx?$": ["@swc/jest", swcConfig], -// }, -// testRegex: ".*\\.spec\\.ts$", -// setupFilesAfterEnv: ["/test/integration/jest.setup.js"], -// testEnvironment: "node", -// moduleNameMapper: { -// "^@config$": ["src/config/index"], -// "^@app$": ["src/app/index"], -// "^@utils$": ["src/utils/index"], - -// "^@core/(.*)$": ["src/core/$1"], -// "^@app/(.*)$": ["src/app/$1"], -// "^@domain/(.*)$": ["src/domain/$1"], -// "^@services/(.*)$": ["src/services/$1"], -// "^@servers/(.*)$": ["src/servers/$1"], -// "^@graphql/(.*)$": ["src/graphql/$1"], -// "^test/(.*)$": ["test/$1"], -// }, -// // Flash fork: skip galoy tests -// testPathIgnorePatterns: ["/test/integration/galoy/"], -// } diff --git a/test/galoy/integration/jest.setup.js b/test/galoy/integration/jest.setup.js deleted file mode 100644 index a0b58c5c3..000000000 --- a/test/galoy/integration/jest.setup.js +++ /dev/null @@ -1,21 +0,0 @@ -import { disconnectAll } from "@services/redis" -import { setupMongoConnection } from "@services/mongodb" - -let mongoose - -// Mock prices -jest.mock("@app/prices/get-current-price", () => require("test/mocks/get-current-price")) - -beforeAll(async () => { - mongoose = await setupMongoConnection(true) -}) - -afterAll(async () => { - // avoids to use --forceExit - disconnectAll() - if (mongoose) { - await mongoose.connection.close() - } -}) - -jest.setTimeout(process.env.JEST_TIMEOUT || 30000) diff --git a/test/galoy/integration/services/ledger-facade.spec.ts b/test/galoy/integration/services/ledger-facade.spec.ts deleted file mode 100644 index 7143bc96a..000000000 --- a/test/galoy/integration/services/ledger-facade.spec.ts +++ /dev/null @@ -1,439 +0,0 @@ -import crypto from "crypto" - -import { BtcWalletDescriptor, UsdWalletDescriptor, WalletCurrency } from "@domain/shared" -import { LedgerTransactionType } from "@domain/ledger" -import { UsdDisplayCurrency } from "@domain/fiat" -import { CouldNotFindError } from "@domain/errors" - -import { LedgerService } from "@services/ledger" - -import { createMandatoryUsers } from "test/galoy/helpers" -import { - recordLnFailedPayment, - recordLnFeeReimbursement, - recordLnIntraLedgerPayment, - recordLnTradeIntraAccountTxn, - recordOnChainIntraLedgerPayment, - recordOnChainTradeIntraAccountTxn, - recordReceiveLnPayment, - recordReceiveOnChainFeeReconciliation, - recordReceiveOnChainPayment, - recordSendLnPayment, - recordSendOnChainPayment, - recordWalletIdIntraLedgerPayment, - recordWalletIdTradeIntraAccountTxn, -} from "test/galoy/helpers/ledger" - -beforeAll(async () => { - await createMandatoryUsers() -}) - -describe("Facade", () => { - const receiveAmount = { - usd: { amount: 100n, currency: WalletCurrency.Usd }, - btc: { amount: 200n, currency: WalletCurrency.Btc }, - } - const sendAmount = { - usd: { amount: 20n, currency: WalletCurrency.Usd }, - btc: { amount: 40n, currency: WalletCurrency.Btc }, - } - const bankFee = { - usd: { amount: 10n, currency: WalletCurrency.Usd }, - btc: { amount: 20n, currency: WalletCurrency.Btc }, - } - - const displayReceiveUsdAmounts = { - amountDisplayCurrency: Number(receiveAmount.usd.amount) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number(bankFee.usd.amount) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, - } - - const displayReceiveEurAmounts = { - amountDisplayCurrency: 120 as DisplayCurrencyBaseAmount, - feeDisplayCurrency: 12 as DisplayCurrencyBaseAmount, - displayCurrency: "EUR" as DisplayCurrency, - } - - const displaySendEurAmounts = { - amountDisplayCurrency: 24 as DisplayCurrencyBaseAmount, - feeDisplayCurrency: 12 as DisplayCurrencyBaseAmount, - displayCurrency: "EUR" as DisplayCurrency, - } - - describe("recordReceive", () => { - it("recordReceiveLnPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordReceiveLnPayment({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: receiveAmount, - bankFee, - displayAmounts: displayReceiveEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.Invoice) - }) - - it("recordReceiveOnChainPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordReceiveOnChainPayment({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: receiveAmount, - bankFee, - displayAmounts: displayReceiveEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.OnchainReceipt) - }) - - it("recordLnFailedPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordLnFailedPayment({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: receiveAmount, - bankFee, - displayAmounts: displayReceiveEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.Payment) - }) - - it("recordLnFeeReimbursement", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordLnFeeReimbursement({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: receiveAmount, - bankFee, - displayAmounts: displayReceiveEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.LnFeeReimbursement) - }) - }) - - describe("recordSend", () => { - it("recordSendLnPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordSendLnPayment({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: sendAmount, - bankFee, - displayAmounts: displaySendEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.Payment) - }) - - it("recordSendOnChainPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordSendOnChainPayment({ - walletDescriptor: btcWalletDescriptor, - paymentAmount: sendAmount, - bankFee, - displayAmounts: displaySendEurAmounts, - }) - if (res instanceof Error) throw res - - const txns = await LedgerService().getTransactionsByWalletId(btcWalletDescriptor.id) - if (txns instanceof Error) throw txns - if (!(txns && txns.length)) throw new Error() - const txn = txns[0] - - expect(txn.type).toBe(LedgerTransactionType.OnchainPayment) - }) - }) - - describe("recordIntraledger", () => { - it("recordLnIntraLedgerPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordLnIntraLedgerPayment({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.LnIntraLedger) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.LnIntraLedger) - }) - - it("recordWalletIdIntraLedgerPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordWalletIdIntraLedgerPayment({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.IntraLedger) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.IntraLedger) - }) - - it("recordOnChainIntraLedgerPayment", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordOnChainIntraLedgerPayment({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.OnchainIntraLedger) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.OnchainIntraLedger) - }) - }) - - describe("recordTradeIntraAccount", () => { - it("recordLnTradeIntraAccountTxn", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordLnTradeIntraAccountTxn({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.LnTradeIntraAccount) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.LnTradeIntraAccount) - }) - - it("recordWalletIdTradeIntraAccountTxn", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordWalletIdTradeIntraAccountTxn({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.WalletIdTradeIntraAccount) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.WalletIdTradeIntraAccount) - }) - - it("recordOnChainTradeIntraAccountTxn", async () => { - const btcWalletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const usdWalletDescriptor = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const res = await recordOnChainTradeIntraAccountTxn({ - senderWalletDescriptor: btcWalletDescriptor, - recipientWalletDescriptor: usdWalletDescriptor, - paymentAmount: sendAmount, - senderDisplayAmounts: { - senderAmountDisplayCurrency: displaySendEurAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displaySendEurAmounts.feeDisplayCurrency, - senderDisplayCurrency: displaySendEurAmounts.displayCurrency, - }, - recipientDisplayAmounts: { - recipientAmountDisplayCurrency: displayReceiveUsdAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayReceiveUsdAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayReceiveUsdAmounts.displayCurrency, - }, - }) - if (res instanceof Error) throw res - - const senderTxns = await LedgerService().getTransactionsByWalletId( - btcWalletDescriptor.id, - ) - if (senderTxns instanceof Error) throw senderTxns - if (!(senderTxns && senderTxns.length)) throw new Error() - const senderTxn = senderTxns[0] - expect(senderTxn.type).toBe(LedgerTransactionType.OnChainTradeIntraAccount) - - const recipientTxns = await LedgerService().getTransactionsByWalletId( - usdWalletDescriptor.id, - ) - if (recipientTxns instanceof Error) throw recipientTxns - if (!(recipientTxns && recipientTxns.length)) throw new Error() - const recipientTxn = recipientTxns[0] - expect(recipientTxn.type).toBe(LedgerTransactionType.OnChainTradeIntraAccount) - }) - }) - - describe("recordReceiveOnChainFeeReconciliation", () => { - it("recordReceiveOnChainFeeReconciliation", async () => { - const lowerFee = { amount: 1000n, currency: WalletCurrency.Btc } - const higherFee = { amount: 2100n, currency: WalletCurrency.Btc } - - const res = await recordReceiveOnChainFeeReconciliation({ - estimatedFee: lowerFee, - actualFee: higherFee, - }) - if (res instanceof Error) throw res - - const { transactionIds } = res - expect(transactionIds).toHaveLength(2) - - const ledger = LedgerService() - - const tx0 = await ledger.getTransactionById(transactionIds[0]) - const tx1 = await ledger.getTransactionById(transactionIds[1]) - const liabilitiesTxn = [tx0, tx1].find( - (tx): tx is LedgerTransaction => - !(tx instanceof CouldNotFindError), - ) - if (liabilitiesTxn === undefined) throw new Error("Could not find transaction") - expect(liabilitiesTxn.type).toBe(LedgerTransactionType.OnchainPayment) - }) - }) -}) diff --git a/test/galoy/integration/services/ledger-service.spec.ts b/test/galoy/integration/services/ledger-service.spec.ts deleted file mode 100644 index 4db6e2492..000000000 --- a/test/galoy/integration/services/ledger-service.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { UnknownLedgerError } from "@domain/ledger" -import { BtcWalletDescriptor, UsdWalletDescriptor, WalletCurrency } from "@domain/shared" -import { LedgerService } from "@services/ledger" - -import { createMandatoryUsers, recordWalletIdIntraLedgerPayment } from "test/galoy/helpers" - -let walletDescriptor: WalletDescriptor<"BTC"> -let walletDescriptorOther: WalletDescriptor<"USD"> - -beforeAll(async () => { - await createMandatoryUsers() - - walletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - walletDescriptorOther = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const paymentAmount = { - usd: { amount: 200n, currency: WalletCurrency.Usd }, - btc: { amount: 400n, currency: WalletCurrency.Btc }, - } - - const displayAmounts = { - amountDisplayCurrency: 240 as DisplayCurrencyBaseAmount, - feeDisplayCurrency: 24 as DisplayCurrencyBaseAmount, - displayCurrency: "EUR" as DisplayCurrency, - } - - const senderDisplayAmounts = { - senderAmountDisplayCurrency: displayAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displayAmounts.feeDisplayCurrency, - senderDisplayCurrency: displayAmounts.displayCurrency, - } - - const recipientDisplayAmounts = { - recipientAmountDisplayCurrency: displayAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayAmounts.displayCurrency, - } - - await recordWalletIdIntraLedgerPayment({ - senderWalletDescriptor: walletDescriptor, - recipientWalletDescriptor: walletDescriptorOther, - paymentAmount, - senderDisplayAmounts, - recipientDisplayAmounts, - }) - - await recordWalletIdIntraLedgerPayment({ - senderWalletDescriptor: walletDescriptorOther, - recipientWalletDescriptor: walletDescriptor, - paymentAmount, - senderDisplayAmounts, - recipientDisplayAmounts, - }) -}) - -describe("LedgerService", () => { - describe("getTransactionsByWalletIds", () => { - const ledger = LedgerService() - - it("returns valid data for walletIds passed", async () => { - const txns = await ledger.getTransactionsByWalletIds({ - walletIds: [walletDescriptor.id, walletDescriptorOther.id], - }) - if (txns instanceof Error) throw txns - - expect(txns.slice).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - walletId: walletDescriptor.id, - }), - expect.objectContaining({ - walletId: walletDescriptorOther.id, - }), - ]), - ) - }) - - it("returns valid data using after cursor", async () => { - const allTxns = await ledger.getTransactionsByWalletIds({ - walletIds: [walletDescriptor.id, walletDescriptorOther.id], - }) - if (allTxns instanceof Error) throw allTxns - const firstTxnId = allTxns.slice[0].id - - const txns = await ledger.getTransactionsByWalletIds({ - walletIds: [walletDescriptor.id, walletDescriptorOther.id], - paginationArgs: { after: firstTxnId }, - }) - if (txns instanceof Error) throw txns - - expect(txns.total).toEqual(allTxns.total - 1) - }) - - it("returns error for invalid after cursor", async () => { - const txns = await ledger.getTransactionsByWalletIds({ - walletIds: [walletDescriptor.id, walletDescriptorOther.id], - paginationArgs: { after: "invalid-cursor" }, - }) - expect(txns).toBeInstanceOf(UnknownLedgerError) - }) - }) -}) diff --git a/test/galoy/integration/services/onchain-service.spec.ts b/test/galoy/integration/services/onchain-service.spec.ts deleted file mode 100644 index 4824987b7..000000000 --- a/test/galoy/integration/services/onchain-service.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { OnChainAddressAlreadyCreatedForRequestIdError } from "@domain/bitcoin/onchain" -import { WalletCurrency } from "@domain/shared" -import { OnChainService } from "@services/bria" - -const onchain = OnChainService() - -describe("onChainAddress", () => { - it("can apply requestId as idempotency key when creating new address", async () => { - const walletDescriptor: WalletDescriptor<"BTC"> = { - id: "walletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "accountId" as AccountId, - } - const args = { - walletDescriptor, - requestId: ("requestId #" + - (Math.random() * 1_000_000).toFixed()) as OnChainAddressRequestId, - } - - const identifier = await onchain.getAddressForWallet(args) - expect(identifier).not.toBeInstanceOf(Error) - - const identifierFromRetry = await onchain.getAddressForWallet(args) - expect(identifierFromRetry).toBeInstanceOf( - OnChainAddressAlreadyCreatedForRequestIdError, - ) - }) -}) diff --git a/test/galoy/legacy-integration/01-setup/01-connection.spec.ts b/test/galoy/legacy-integration/01-setup/01-connection.spec.ts deleted file mode 100644 index 3f295dfab..000000000 --- a/test/galoy/legacy-integration/01-setup/01-connection.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { OnChainService } from "@services/bria" -import { redis } from "@services/redis" -import { Account } from "@services/mongoose/schema" - -import { - lnd1, - lnd2, - lndOutside1, - lndOutside2, - getWalletInfo, - bitcoindClient, - resetIntegrationLnds, - getChannels, - getChainBalance, - waitUntilBriaConnected, -} from "test/galoy/helpers" - -it("connects to bitcoind", async () => { - const { chain } = await bitcoindClient.getBlockchainInfo() - expect(chain).toEqual("regtest") -}) - -describe("connects to lnds", () => { - beforeAll(async () => { - await resetIntegrationLnds() - }) - - const lnds = [lnd1, lnd2] - for (const item in lnds) { - it(`connects to lnd${+item + 1}`, async () => { - const { public_key } = await getWalletInfo({ lnd: lnds[item] }) - expect(public_key.length).toBe(64 + 2) - - const { channels } = await getChannels({ lnd: lnds[item] }) - expect(channels.length).toEqual(0) - - const { chain_balance: chainBalance } = await getChainBalance({ lnd: lnds[item] }) - expect(chainBalance).toEqual(0) - }) - } - - it("connects to outside lnds", async () => { - const lnds = [lndOutside1, lndOutside2] - for (const lnd of lnds) { - const { public_key } = await getWalletInfo({ lnd }) - expect(public_key.length).toBe(64 + 2) - - const { channels } = await getChannels({ lnd }) - expect(channels.length).toEqual(0) - - const { chain_balance: chainBalance } = await getChainBalance({ lnd }) - expect(chainBalance).toEqual(0) - } - }) -}) - -it("connects to mongodb", async () => { - const users = await Account.find() - expect(users).toBeInstanceOf(Array) -}) - -it("connects to redis", async () => { - const value = "value" - await redis.set("key", value) - const result = await redis.get("key") - expect(result).toBe(value) -}) - -describe("connects to bria", () => { - beforeAll(async () => { - await waitUntilBriaConnected() - }) - - it("receives a bria balance", async () => { - const res = await OnChainService().getHotBalance() - expect(res).not.toBeInstanceOf(Error) - }) -}) diff --git a/test/galoy/legacy-integration/01-setup/01-funds-bitcoind-lnds.spec.ts b/test/galoy/legacy-integration/01-setup/01-funds-bitcoind-lnds.spec.ts deleted file mode 100644 index 598b832fe..000000000 --- a/test/galoy/legacy-integration/01-setup/01-funds-bitcoind-lnds.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { btc2sat, sat2btc } from "@domain/bitcoin" -import { getFunderWalletId } from "@services/ledger/caching" - -import { - bitcoindClient, - bitcoindSignerClient, - checkIsBalanced, - createMandatoryUsers, - createSignerWallet, - fundLnd, - fundWalletIdFromOnchain, - getChainBalance, - lnd1, - lndOutside1, - mineAndConfirm, -} from "test/galoy/helpers" -import { BitcoindWalletClient } from "test/galoy/helpers/bitcoind" - -let bitcoindOutside: BitcoindWalletClient - -beforeAll(async () => { - await createMandatoryUsers() -}) - -afterAll(async () => { - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -describe("Bitcoind", () => { - it("check no wallet", async () => { - const wallets = await bitcoindClient.listWallets() - expect(wallets.length).toBe(0) - }) - - it("create signer wallet for bria", async () => { - const walletName = "dev" - const { name } = await createSignerWallet(walletName) - expect(name).toBe(walletName) - - const wallets = await bitcoindSignerClient.listWallets() - expect(wallets).toContain(walletName) - }) - - it("create outside wallet", async () => { - const walletName = "outside" - const { name } = await bitcoindClient.createWallet({ walletName }) - expect(name).toBe(walletName) - const wallets = await bitcoindClient.listWallets() - expect(wallets).toContain(walletName) - bitcoindOutside = new BitcoindWalletClient(walletName) - }) - - it("should be funded mining 10 blocks", async () => { - const numOfBlocks = 10 - const bitcoindAddress = await bitcoindOutside.getNewAddress() - await mineAndConfirm({ - walletClient: bitcoindOutside, - numOfBlocks, - address: bitcoindAddress, - }) - const balance = await bitcoindOutside.getBalance() - expect(balance).toBeGreaterThanOrEqual(50 * numOfBlocks) - }) - - it("funds outside lnd node", async () => { - const outsideLnds = [lndOutside1] - for (const lnd of outsideLnds) { - const amount = btc2sat(1) - const { chain_balance: initialBalance } = await getChainBalance({ - lnd, - }) - const sats = initialBalance + amount - await fundLnd(lnd, sat2btc(amount)) - const { chain_balance: balance } = await getChainBalance({ lnd }) - expect(balance).toBe(sats) - } - }) - - it("funds lnd1 node", async () => { - const amount = btc2sat(1) - const { chain_balance: initialBalance } = await getChainBalance({ lnd: lnd1 }) - const sats = initialBalance + amount - - const funderWalletId = await getFunderWalletId() - await fundWalletIdFromOnchain({ - walletId: funderWalletId, - amountInBitcoin: sat2btc(amount), - lnd: lnd1, - }) - - const { chain_balance: balance } = await getChainBalance({ lnd: lnd1 }) - expect(balance).toBe(sats) - }) -}) diff --git a/test/galoy/legacy-integration/01-setup/01-lightning-channels.spec.ts b/test/galoy/legacy-integration/01-setup/01-lightning-channels.spec.ts deleted file mode 100644 index 6fd0b1867..000000000 --- a/test/galoy/legacy-integration/01-setup/01-lightning-channels.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { onChannelUpdated, updateEscrows } from "@services/lnd/utils" -import { ledgerAdmin } from "@services/mongodb" -import { sleep } from "@utils" - -import { - checkIsBalanced, - closeChannel, - getChannel, - getChannels, - lnd1, - lnd2, - lndOutside1, - lndOutside2, - mineBlockAndSync, - openChannelTesting, - setChannelFees, - subscribeToChannels, - waitFor, - waitUntilSync, -} from "test/galoy/helpers" - -//this is the fixed opening and closing channel fee on devnet -const channelFee = 7700 -const lnds = [lnd1, lnd2, lndOutside1, lndOutside1] -let channelLengthMain: number, channelLengthOutside1: number - -beforeEach(async () => { - await waitUntilSync({ lnds }) - channelLengthMain = (await getChannels({ lnd: lnd1 })).channels.length - channelLengthOutside1 = (await getChannels({ lnd: lndOutside1 })).channels.length -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -// Setup the next network -// lnd2 <- lnd1 <-> lndOutside1 -> lndOutside2 -// this setup avoids close channels for routing fees tests -describe("Lightning channels", () => { - it("opens channel from lnd1 to lnd2", async () => { - const socket = `lnd2:9735` - const { lndNewChannel: channel } = await openChannelTesting({ - lnd: lnd1, - lndPartner: lnd2, - socket, - }) - - const { channels } = await getChannels({ lnd: lnd1 }) - expect(channels.length).toEqual(channelLengthMain + 1) - - await setChannelFees({ lnd: lnd1, channel, base: 0, rate: 0 }) - await setChannelFees({ lnd: lnd2, channel, base: 0, rate: 0 }) - }) - - it("opens channel from lnd1 to lndOutside1", async () => { - const socket = `lnd-outside-1:9735` - - const initFeeInLedger = await ledgerAdmin.getBankOwnerBalance() - - const { lndNewChannel: channel } = await openChannelTesting({ - lnd: lnd1, - lndPartner: lndOutside1, - socket, - }) - - const { channels } = await getChannels({ lnd: lnd1 }) - expect(channels.length).toEqual(channelLengthMain + 1) - - const finalFeeInLedger = await ledgerAdmin.getBankOwnerBalance() - expect(finalFeeInLedger - initFeeInLedger).toBe(channelFee * -1) - - await setChannelFees({ lnd: lnd1, channel, base: 1, rate: 0 }) - await setChannelFees({ lnd: lndOutside1, channel, base: 1, rate: 0 }) - }) - - it("opens channel from lndOutside1 to lnd1", async () => { - const socket = `lnd1:9735` - - const { lndNewChannel: channel } = await openChannelTesting({ - lnd: lndOutside1, - lndPartner: lnd1, - socket, - }) - - const { channels } = await getChannels({ lnd: lnd1 }) - expect(channels.length).toEqual(channelLengthMain + 1) - - await setChannelFees({ lnd: lnd1, channel, base: 1, rate: 0 }) - await setChannelFees({ lnd: lndOutside1, channel, base: 1, rate: 0 }) - }) - - it("opens private channel from lndOutside1 to lndOutside2", async () => { - const socket = `lnd-outside-2:9735` - - const { lndNewChannel: channel } = await openChannelTesting({ - lnd: lndOutside1, - lndPartner: lndOutside2, - socket, - is_private: true, - }) - - const { channels } = await getChannels({ lnd: lndOutside1 }) - expect(channels.length).toEqual(channelLengthOutside1 + 1) - expect(channels.some((e) => e.is_private)).toBe(true) - - // Set fee policy on lndOutside1 as routing node between lnd1 and lndOutside2 - let count = 0 - let countMax = 9 - let setOnLndOutside1 - while (count < countMax && setOnLndOutside1 !== true) { - if (count > 0) await sleep(500) - count++ - - setOnLndOutside1 = await setChannelFees({ - lnd: lndOutside1, - channel, - base: 0, - rate: 5000, - }) - } - expect(count).toBeGreaterThan(0) - expect(count).toBeLessThan(countMax) - expect(setOnLndOutside1).toBe(true) - - let policies - let errMsg: string | undefined = "FullChannelDetailsNotFound" - count = 0 - countMax = 8 - // Try to getChannel for up to 2 secs (250ms x 8) - while (count < countMax && errMsg === "FullChannelDetailsNotFound") { - count++ - await sleep(250) - try { - ;({ policies } = await getChannel({ id: channel.id, lnd: lndOutside1 })) - errMsg = undefined - } catch (err) { - if (Array.isArray(err)) errMsg = err[1] - } - } - expect(count).toBeGreaterThan(0) - expect(count).toBeLessThan(countMax) - expect(errMsg).not.toBe("FullChannelDetailsNotFound") - expect(policies && policies.length).toBeGreaterThan(0) - - const { base_fee_mtokens, fee_rate } = policies[0] - expect(base_fee_mtokens).toBe("0") - expect(fee_rate).toEqual(5000) - }) - - // FIXME: we need a way to calculate the closing fee - // lnd doesn't give it back to us (undefined) - // and bitcoind doesn't give fee for "outside" wallet - it.skip("opensAndCloses channel from lnd1 to lndOutside1", async () => { - const socket = `lnd-outside-1:9735` - - await openChannelTesting({ lnd: lnd1, lndPartner: lndOutside1, socket }) - - const { channels } = await getChannels({ lnd: lnd1 }) - expect(channels.length).toEqual(channelLengthMain + 1) - - let closedChannel - const sub = subscribeToChannels({ lnd: lnd1 }) - sub.on("channel_closed", async (channel) => { - await onChannelUpdated({ channel, lnd: lnd1, stateChange: "closed" }) - closedChannel = channel - }) - - await closeChannel({ lnd: lnd1, id: channels[channels.length - 1].id }) - - await Promise.all([waitFor(() => closedChannel), mineBlockAndSync({ lnds })]) - - // FIXME - // expect(finalFeeInLedger - initFeeInLedger).toBe(channelFee * -1) - sub.removeAllListeners() - - await updateEscrows() - - { - const { channels } = await getChannels({ lnd: lnd1 }) - expect(channels.length).toEqual(channelLengthMain) - } - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-a-user-setup.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-a-user-setup.spec.ts deleted file mode 100644 index 8ef1c5e97..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-a-user-setup.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { randomUUID } from "crypto" - -import { Accounts } from "@app" -import { setUsername } from "@app/accounts" -import { UsernameIsImmutableError, UsernameNotAvailableError } from "@domain/accounts" -import { ValidationError } from "@domain/shared" -import { CsvWalletsExport } from "@services/ledger/csv-wallet-export" -import { AccountsRepository } from "@services/mongoose" -import { Account } from "@services/mongoose/schema" - -import { - createMandatoryUsers, - randomPhone, - createUserAndWalletFromPhone, - getAccountRecordByPhone, - getDefaultWalletIdByPhone, - getAccountIdByPhone, -} from "test/galoy/helpers" - -let accountRecordC: AccountRecord -let walletIdA: WalletId -let accountIdA: AccountId, accountIdB: AccountId, accountIdC: AccountId - -const phoneA = randomPhone() -const phoneB = randomPhone() -const phoneC = randomPhone() - -describe("UserWallet", () => { - beforeAll(async () => { - await createMandatoryUsers() - - await createUserAndWalletFromPhone(phoneA) - await createUserAndWalletFromPhone(phoneB) - await createUserAndWalletFromPhone(phoneC) - - accountRecordC = await getAccountRecordByPhone(phoneC) - - walletIdA = await getDefaultWalletIdByPhone(phoneA) - - accountIdA = await getAccountIdByPhone(phoneA) - accountIdB = await getAccountIdByPhone(phoneB) - accountIdC = await getAccountIdByPhone(phoneC) - }) - - it("has a role if it was configured", async () => { - const dealer = await Account.findOne({ role: "dealer" }) - expect(dealer).toBeTruthy() - }) - - it("has a title if it was configured", () => { - expect(accountRecordC).toHaveProperty("title") - }) - - describe("setUsername", () => { - it("does not set username if length is less than 3", async () => { - await expect( - setUsername({ username: "ab", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - }) - - it("does not set username if contains invalid characters", async () => { - await expect( - setUsername({ username: "ab+/", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - }) - - it("does not allow non english characters", async () => { - await expect( - setUsername({ username: "ñ_user1", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - }) - - it("does not set username starting with 1, 3, bc1, lnbc1", async () => { - await expect( - setUsername({ username: "1ab", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - await expect( - setUsername({ username: "3basd", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - await expect( - setUsername({ username: "bc1be", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - await expect( - setUsername({ username: "lnbc1qwe1", id: accountIdA }), - ).resolves.toBeInstanceOf(ValidationError) - }) - - it("allows set username", async () => { - let result = await setUsername({ username: "userA", id: accountIdA }) - expect(result).not.toBeInstanceOf(Error) - result = await setUsername({ username: "userB", id: accountIdB }) - expect(result).not.toBeInstanceOf(Error) - }) - - it("does not allow set username if already taken", async () => { - const username = "userA" - await expect(setUsername({ username, id: accountIdC })).resolves.toBeInstanceOf( - UsernameNotAvailableError, - ) - }) - - it("does not allow set username with only case difference", async () => { - await expect( - setUsername({ username: "UserA", id: accountIdC }), - ).resolves.toBeInstanceOf(UsernameNotAvailableError) - - // set username for accountC - const result = await setUsername({ username: "lily", id: accountIdC }) - expect(result).not.toBeInstanceOf(Error) - }) - - it("does not allow re-setting username", async () => { - await expect( - setUsername({ username: "abc", id: accountIdA }), - ).resolves.toBeInstanceOf(UsernameIsImmutableError) - }) - }) - - describe("usernameExists", () => { - it("return true if username already exists", async () => { - const username = "userA" as Username - - const accountsRepo = AccountsRepository() - const account = await accountsRepo.findByUsername(username) - if (account instanceof Error) throw account - expect(account.id).toStrictEqual(accountIdA) - }) - - it("return true for other capitalization", async () => { - const username = "userA" as Username - - const accountsRepo = AccountsRepository() - const account = await accountsRepo.findByUsername( - username.toLocaleUpperCase() as Username, - ) - if (account instanceof Error) throw account - expect(account.id).toStrictEqual(accountIdA) - }) - - it("return false if username does not exist", async () => { - const accountsRepo = AccountsRepository() - const account = await accountsRepo.findByUsername("user" as Username) - expect(account).toBeInstanceOf(Error) - }) - }) - - describe("getStringCsv", () => { - const csvHeader = - "id,walletId,type,credit,debit,fee,currency,timestamp,pendingConfirmation,journalId,lnMemo,usd,feeUsd,recipientWalletId,username,memoFromPayer,paymentHash,pubkey,feeKnownInAdvance,address,txHash" - it("exports to csv", async () => { - const csv = new CsvWalletsExport() - await csv.addWallet(walletIdA) - const base64Data = csv.getBase64() - expect(typeof base64Data).toBe("string") - const data = Buffer.from(base64Data, "base64") - expect(data.includes(csvHeader)).toBeTruthy() - }) - }) - - describe("updateAccountStatus", () => { - it("sets account status (with history) for given user id", async () => { - let account - - const updatedByUserId = randomUUID() as UserId - - account = await Accounts.updateAccountStatus({ - id: accountIdC, - status: "pending", - updatedByUserId, - }) - if (account instanceof Error) { - throw account - } - expect(account.status).toEqual("pending") - - account = await Accounts.updateAccountStatus({ - id: account.id, - status: "locked", - updatedByUserId, - comment: "Looks spammy", - }) - if (account instanceof Error) { - throw account - } - expect(account.statusHistory.slice(-1)[0]).toMatchObject({ - status: "locked", - updatedByUserId, - comment: "Looks spammy", - }) - expect(account.status).toEqual("locked") - - account = await Accounts.updateAccountStatus({ - id: account.id, - status: "active", - updatedByUserId, - }) - if (account instanceof Error) { - throw account - } - expect(account.statusHistory.length).toBe(4) - expect(account.status).toEqual("active") - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-bria-handlers.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-bria-handlers.spec.ts deleted file mode 100644 index 0d2961528..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-bria-handlers.spec.ts +++ /dev/null @@ -1,725 +0,0 @@ -import crypto from "crypto" - -import { getOnChainWalletConfig } from "@config" - -import { - addPendingTransaction, - removePendingTransaction, - addSettledTransaction, - getLastOnChainAddress, - registerBroadcastedPayout, -} from "@app/wallets" - -import { AmountCalculator, WalletCurrency, ZERO_SATS } from "@domain/shared" -import { UnknownLedgerError, toLiabilitiesWalletId } from "@domain/ledger" -import { DisplayPriceRatio, WalletPriceRatio } from "@domain/payments" -import { - DisplayAmountsConverter, - UsdDisplayCurrency, - displayAmountFromNumber, -} from "@domain/fiat" - -import { WalletOnChainPendingReceive } from "@services/mongoose/schema" -import { Transaction } from "@services/ledger/schema" -import * as LedgerFacade from "@services/ledger/facade" -import { getBankOwnerWalletId } from "@services/ledger/caching" - -import { LessThanDustThresholdError } from "@domain/errors" - -import { generateHash, createRandomUserAndBtcWallet } from "test/galoy/helpers" - -const calc = AmountCalculator() - -const VOUT_0 = 0 as OnChainTxVout -const VOUT_1 = 1 as OnChainTxVout -const { dustThreshold } = getOnChainWalletConfig() - -let walletId: WalletId -let bankOwnerWalletId: WalletId -let accountId: AccountId -let address: OnChainAddress - -beforeAll(async () => { - ;({ id: walletId, accountId } = await createRandomUserAndBtcWallet()) - - const addressResult = await getLastOnChainAddress(walletId) - if (addressResult instanceof Error) throw addressResult - address = addressResult - - bankOwnerWalletId = await getBankOwnerWalletId() -}) - -const getRandomBtcAmountForOnchain = (): BtcPaymentAmount => { - const floor = 10_000 - const amount = floor + Math.round(Math.random() * floor) - return { amount: BigInt(amount), currency: WalletCurrency.Btc } -} - -const dustAmount: BtcPaymentAmount = { - amount: BigInt(dustThreshold - 1), - currency: WalletCurrency.Btc, -} - -describe("Bria Event Handlers", () => { - describe("addPendingTransaction", () => { - it("persists a new pending transaction", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - - const res = await addPendingTransaction({ - txId, - vout, - satoshis: getRandomBtcAmountForOnchain(), - address, - }) - expect(res).toBe(true) - - const result = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(result).toEqual(1) - }) - - it("handles event replay", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - - const args = { - txId, - vout, - satoshis: getRandomBtcAmountForOnchain(), - address, - } - const results = await Promise.all([ - addPendingTransaction(args), - addPendingTransaction(args), - ]) - const success = results.filter((res) => res === true) - expect(success).toHaveLength(2) - - const result = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(result).toEqual(1) - }) - - it("records multiple utxos for same transaction hash", async () => { - const txId = generateHash() as OnChainTxHash - - const results = await Promise.all([ - addPendingTransaction({ - txId, - vout: VOUT_0, - satoshis: getRandomBtcAmountForOnchain(), - address, - }), - addPendingTransaction({ - txId, - vout: VOUT_1, - satoshis: getRandomBtcAmountForOnchain(), - address, - }), - ]) - expect(results.filter((res) => res === true)).toHaveLength(2) - - const resultVout0 = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout: VOUT_0, - }) - expect(resultVout0).toEqual(1) - - const resultVout1 = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout: VOUT_1, - }) - expect(resultVout1).toEqual(1) - - const resultTxId = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - }) - expect(resultTxId).toEqual(2) - }) - - it("fails if the amount is less than on chain dust amount", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - const satoshis = dustAmount - - const result = await addPendingTransaction({ - txId, - vout, - satoshis, - address, - }) - - expect(result).toBeInstanceOf(LessThanDustThresholdError) - }) - }) - - describe("removePendingTransaction", () => { - it("removes an already existing transaction", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - const satoshis = getRandomBtcAmountForOnchain() - - const pendingRes = await addPendingTransaction({ - txId, - vout, - satoshis, - address, - }) - expect(pendingRes).toBe(true) - - const pendingTxnsBefore = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsBefore).toEqual(1) - - const settledRes = await removePendingTransaction({ - txId, - vout, - address, - }) - expect(settledRes).toBe(true) - - const pendingTxnsAfter = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsAfter).toEqual(0) - }) - - it("ignores transactions that were not recorded", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - - const pendingRes = await removePendingTransaction({ - txId, - vout, - address, - }) - expect(pendingRes).toBe(true) - }) - }) - - describe("addSettledTransaction", () => { - it("persists a new settled transaction", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - const satoshis = getRandomBtcAmountForOnchain() - - const pendingRes = await addPendingTransaction({ - txId, - vout, - satoshis, - address, - }) - expect(pendingRes).toBe(true) - - const pendingTxnsBefore = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsBefore).toEqual(1) - - const settledRes = await addSettledTransaction({ - txId, - vout, - satoshis, - address, - }) - expect(settledRes).toBe(true) - - const pendingTxnsAfter = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsAfter).toEqual(0) - - const ledgerTxns = await Transaction.countDocuments({ - hash: txId, - vout, - accounts: toLiabilitiesWalletId(walletId), - }) - expect(ledgerTxns).toEqual(1) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ hash: txId }) - }) - - it("handles event replay", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - const satoshis = getRandomBtcAmountForOnchain() - - const args = { - txId, - vout, - satoshis, - address, - } - - const pendingRes = await addPendingTransaction(args) - expect(pendingRes).toBe(true) - - const pendingTxnsBefore = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsBefore).toEqual(1) - - const settledResults = await Promise.all([ - addSettledTransaction(args), - addSettledTransaction(args), - ]) - expect(settledResults.filter((res) => res === true)).toHaveLength(2) - - const pendingTxnsAfter = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout, - }) - expect(pendingTxnsAfter).toEqual(0) - - const ledgerTxns = await Transaction.countDocuments({ - hash: txId, - vout, - accounts: toLiabilitiesWalletId(walletId), - }) - expect(ledgerTxns).toEqual(1) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ hash: txId }) - }) - - it("records multiple utxos for same transaction hash", async () => { - const txId = generateHash() as OnChainTxHash - const satoshis = getRandomBtcAmountForOnchain() - - const args1 = { - txId, - vout: VOUT_0, - satoshis, - address, - } - const args2 = { - txId, - vout: VOUT_1, - satoshis, - address, - } - - const results = await Promise.all([ - addPendingTransaction(args1), - addPendingTransaction(args2), - ]) - expect(results.filter((res) => res === true)).toHaveLength(2) - - const PendingVout0Before = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout: VOUT_0, - }) - expect(PendingVout0Before).toEqual(1) - - const PendingVout1Before = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - vout: VOUT_1, - }) - expect(PendingVout1Before).toEqual(1) - - const settledResults = await Promise.all([ - addSettledTransaction(args1), - addSettledTransaction(args2), - ]) - expect(settledResults.filter((res) => res === true)).toHaveLength(2) - - const pendingTxnsAfter = await WalletOnChainPendingReceive.countDocuments({ - transactionHash: txId, - }) - expect(pendingTxnsAfter).toEqual(0) - - expect(args1.vout).not.toEqual(args2.vout) - const ledgerTxnsVout1 = await Transaction.countDocuments({ - hash: txId, - vout: args1.vout, - accounts: toLiabilitiesWalletId(walletId), - }) - expect(ledgerTxnsVout1).toEqual(1) - const ledgerTxnsVout2 = await Transaction.countDocuments({ - hash: txId, - vout: args2.vout, - accounts: toLiabilitiesWalletId(walletId), - }) - expect(ledgerTxnsVout2).toEqual(1) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ hash: txId }) - }) - - it("fails if the amount is less than on chain dust amount", async () => { - const txId = generateHash() as OnChainTxHash - const vout = VOUT_0 - const satoshis = dustAmount - - const result = await addSettledTransaction({ - txId, - vout, - satoshis, - address, - }) - - expect(result).toBeInstanceOf(LessThanDustThresholdError) - }) - }) - - describe("registerBroadcastedPayout", () => { - const addressForPayout = "addressForPayout" as OnChainAddress - const estimatedFee = { amount: 2500n, currency: WalletCurrency.Btc } - - const addOnChainPaymentLedgerTxns = async ({ - estimatedFee, - actualFee, - }: { - estimatedFee: BtcPaymentAmount - actualFee: BtcPaymentAmount - }): Promise => { - const payoutId = crypto.randomUUID() as PayoutId - - const priceRatio = WalletPriceRatio({ - usd: { amount: 20n, currency: WalletCurrency.Usd }, - btc: { amount: 1000n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - const displayAmount = displayAmountFromNumber({ - amount: 20, - currency: UsdDisplayCurrency, - }) - if (displayAmount instanceof Error) throw displayAmount - const displayPriceRatio = DisplayPriceRatio({ - displayAmount, - walletAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - }) - if (displayPriceRatio instanceof Error) throw displayPriceRatio - - const btcMinerFee = estimatedFee - const btcBankFee = { amount: 2_000n, currency: WalletCurrency.Btc } - const usdBankFee = priceRatio.convertFromBtc(btcBankFee) - const btcProtocolAndBankFee = calc.add(btcMinerFee, btcBankFee) - const usdProtocolAndBankFee = priceRatio.convertFromBtc(btcProtocolAndBankFee) - - const btcPaymentAmount = { amount: 10_000n, currency: WalletCurrency.Btc } - const usdPaymentAmount = priceRatio.convertFromBtc(btcPaymentAmount) - const btcTotalAmount = calc.add(btcProtocolAndBankFee, btcPaymentAmount) - const usdTotalAmount = priceRatio.convertFromBtc(btcTotalAmount) - - const { - displayAmount: displayPaymentAmount, - displayFee: displayProtocolAndBankFee, - } = DisplayAmountsConverter(displayPriceRatio).convert({ - btcPaymentAmount, - btcProtocolAndBankFee, - usdPaymentAmount, - usdProtocolAndBankFee, - }) - - const { - metadata, - debitAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = LedgerFacade.OnChainSendLedgerMetadata({ - paymentAmounts: { - btcPaymentAmount, - btcProtocolAndBankFee, - - usdPaymentAmount, - usdProtocolAndBankFee, - }, - - amountDisplayCurrency: Number( - displayPaymentAmount.amountInMinor, - ) as DisplayCurrencyBaseAmount, - feeDisplayCurrency: Number( - displayProtocolAndBankFee.amountInMinor, - ) as DisplayCurrencyBaseAmount, - displayCurrency: displayPaymentAmount.currency, - - payeeAddresses: ["address" as OnChainAddress], - sendAll: false, - memoOfPayer: undefined, - }) - const journal = await LedgerFacade.recordSendOnChain({ - description: "", - amountToDebitSender: { - btc: btcTotalAmount, - usd: usdTotalAmount, - }, - bankFee: { btc: btcBankFee, usd: usdBankFee }, - senderWalletDescriptor: { id: walletId, currency: WalletCurrency.Btc, accountId }, - metadata, - additionalDebitMetadata: debitAccountAdditionalMetadata, - additionalInternalMetadata: internalAccountsAdditionalMetadata, - }) - if (journal instanceof Error) throw journal - - const updated = await LedgerFacade.setOnChainTxPayoutId({ - journalId: journal.journalId, - payoutId, - }) - if (updated instanceof Error) throw updated - - return { - type: "payout_broadcast", - id: payoutId, - proportionalFee: actualFee, - satoshis: btcPaymentAmount, - txId: generateHash() as OnChainTxHash, - vout: 0 as OnChainTxVout, - address: addressForPayout, - } - } - - it("handles a broadcast event", async () => { - // Setup a transaction in database - const btcFeeDifference = ZERO_SATS - const actualFee = calc.add(estimatedFee, btcFeeDifference) - - const broadcastPayload = await addOnChainPaymentLedgerTxns({ - estimatedFee, - actualFee, - }) - const { proportionalFee, id: payoutId, txId, vout } = broadcastPayload - - // Run before-broadcast checks - const txnsBefore = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsBefore instanceof Error) throw txnsBefore - expect(txnsBefore.length).toEqual(2) - - const hashesBefore = txnsBefore.map((txn) => txn.txHash) - const hashesBeforeSet = new Set(hashesBefore) - expect(hashesBeforeSet.size).toEqual(1) - - // Register broadcast - const res = await registerBroadcastedPayout({ - payoutId, - proportionalFee, - txId, - vout, - }) - expect(res).toBe(true) - - // Run after-broadcast checks - const txnsAfter = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsAfter instanceof Error) throw txnsAfter - expect(txnsAfter.length).toEqual(2) - - const hashesAfter = txnsAfter.map((txn) => txn.txHash) - const hashesAfterSet = new Set(hashesAfter) - expect(hashesAfterSet.size).toEqual(1) - - expect(hashesBefore[0]).not.toEqual(hashesAfter[0]) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ payout_id: payoutId }) - }) - - it("handles a broadcast event, with underpaid fee", async () => { - // Setup a transaction in database - const btcFeeDifference = { amount: 1500n, currency: WalletCurrency.Btc } - const actualFee = calc.add(estimatedFee, btcFeeDifference) - - const broadcastPayload = await addOnChainPaymentLedgerTxns({ - estimatedFee, - actualFee, - }) - const { proportionalFee, id: payoutId, txId, vout } = broadcastPayload - - // Run before-broadcast checks - const txnsBefore = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsBefore instanceof Error) throw txnsBefore - expect(txnsBefore.length).toEqual(2) - - const bankTxns = txnsBefore.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxns.length).toEqual(1) - const bankTxnIdBefore = bankTxns[0].id - - const hashesBefore = txnsBefore.map((txn) => txn.txHash) - const hashesBeforeSet = new Set(hashesBefore) - expect(hashesBeforeSet.size).toEqual(1) - - // Register broadcast - const res = await registerBroadcastedPayout({ - payoutId, - proportionalFee, - txId, - vout, - }) - expect(res).toBe(true) - - // Run after-broadcast checks - const txnsAfter = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsAfter instanceof Error) throw txnsAfter - expect(txnsAfter.length).toEqual(3) - - const bankTxnsAfter = txnsAfter.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxnsAfter.length).toEqual(2) - const addedBankTxn = bankTxnsAfter.find((txn) => txn.id !== bankTxnIdBefore) - if (addedBankTxn === undefined) throw new Error("Expected bankOwner txn not found") - - expect(addedBankTxn.debit).toEqual(Number(btcFeeDifference.amount)) - - const hashesAfter = txnsAfter.map((txn) => txn.txHash) - const hashesAfterSet = new Set(hashesAfter) - expect(hashesAfterSet.size).toEqual(1) - - expect(hashesBefore[0]).not.toEqual(hashesAfter[0]) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ payout_id: payoutId }) - }) - - it("handles a broadcast event, with overpaid fee", async () => { - // Setup a transaction in database - const btcFeeDifference = { amount: 1500n, currency: WalletCurrency.Btc } - const actualFee = calc.sub(estimatedFee, btcFeeDifference) - - const broadcastPayload = await addOnChainPaymentLedgerTxns({ - estimatedFee, - actualFee, - }) - const { proportionalFee, id: payoutId, txId, vout } = broadcastPayload - - // Run before-broadcast checks - const txnsBefore = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsBefore instanceof Error) throw txnsBefore - expect(txnsBefore.length).toEqual(2) - - const bankTxns = txnsBefore.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxns.length).toEqual(1) - const bankTxnIdBefore = bankTxns[0].id - - const hashesBefore = txnsBefore.map((txn) => txn.txHash) - const hashesBeforeSet = new Set(hashesBefore) - expect(hashesBeforeSet.size).toEqual(1) - - // Register broadcast - const res = await registerBroadcastedPayout({ - payoutId, - proportionalFee, - txId, - vout, - }) - expect(res).toBe(true) - - // Run after-broadcast checks - const txnsAfter = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsAfter instanceof Error) throw txnsAfter - expect(txnsAfter.length).toEqual(3) - - const bankTxnsAfter = txnsAfter.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxnsAfter.length).toEqual(2) - const addedBankTxn = bankTxnsAfter.find((txn) => txn.id !== bankTxnIdBefore) - if (addedBankTxn === undefined) throw new Error("Expected bankOwner txn not found") - - expect(addedBankTxn.credit).toEqual(Number(btcFeeDifference.amount)) - - const hashesAfter = txnsAfter.map((txn) => txn.txHash) - const hashesAfterSet = new Set(hashesAfter) - expect(hashesAfterSet.size).toEqual(1) - - expect(hashesBefore[0]).not.toEqual(hashesAfter[0]) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ payout_id: payoutId }) - }) - - // TODO: fix mock below and unskip this test - it.skip("handles failed pending fee reconciliation and then retry", async () => { - // Setup a transaction in database - const btcFeeDifference = { amount: 1500n, currency: WalletCurrency.Btc } - const actualFee = calc.add(estimatedFee, btcFeeDifference) - - const broadcastPayload = await addOnChainPaymentLedgerTxns({ - estimatedFee, - actualFee, - }) - const { proportionalFee, id: payoutId, txId, vout } = broadcastPayload - - // Run before-broadcast checks - const txnsBefore = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsBefore instanceof Error) throw txnsBefore - expect(txnsBefore.length).toEqual(2) - - const bankTxns = txnsBefore.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxns.length).toEqual(1) - const bankTxnIdBefore = bankTxns[0].id - - const hashesBefore = txnsBefore.map((txn) => txn.txHash) - const hashesBeforeSet = new Set(hashesBefore) - expect(hashesBeforeSet.size).toEqual(1) - - // Register broadcast with failure - const spy = jest - .spyOn(LedgerFacade, "getTransactionsByPayoutId") - .mockImplementation(async () => new UnknownLedgerError()) - - const res = await registerBroadcastedPayout({ - payoutId, - proportionalFee, - txId, - vout, - }) - expect(res).toBeInstanceOf(UnknownLedgerError) - - spy.mockRestore() - - // Run after-broadcast checks - const txnsAfter = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsAfter instanceof Error) throw txnsAfter - expect(txnsAfter.length).toEqual(2) - - const bankTxnsAfter = txnsAfter.filter((txn) => txn.walletId === bankOwnerWalletId) - expect(bankTxnsAfter.length).toEqual(1) - - const hashesAfter = txnsAfter.map((txn) => txn.txHash) - const hashesAfterSet = new Set(hashesAfter) - expect(hashesAfterSet.size).toEqual(1) - - expect(hashesBefore[0]).not.toEqual(hashesAfter[0]) - - // Retry registering broadcast event - const resRetry = await registerBroadcastedPayout({ - payoutId, - proportionalFee, - txId, - vout, - }) - expect(resRetry).toBe(true) - - const txnsAfterRetry = await LedgerFacade.getTransactionsByPayoutId(payoutId) - if (txnsAfterRetry instanceof Error) throw txnsAfterRetry - expect(txnsAfterRetry.length).toEqual(3) - - const bankTxnsAfterRetry = txnsAfterRetry.filter( - (txn) => txn.walletId === bankOwnerWalletId, - ) - expect(bankTxnsAfterRetry.length).toEqual(2) - const addedBankTxn = bankTxnsAfterRetry.find((txn) => txn.id !== bankTxnIdBefore) - if (addedBankTxn === undefined) throw new Error("Expected bankOwner txn not found") - - expect(addedBankTxn.debit).toEqual(Number(btcFeeDifference.amount)) - - const hashesAfterRetry = txnsAfter.map((txn) => txn.txHash) - const hashesAfterRetrySet = new Set(hashesAfterRetry) - expect(hashesAfterRetrySet.size).toEqual(1) - - expect(hashesBefore[0]).not.toEqual(hashesAfterRetry[0]) - expect(hashesAfter[0]).toEqual(hashesAfterRetry[0]) - - // Cleanup to clear tests accounting - await Transaction.deleteMany({ payout_id: payoutId }) - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-bria.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-bria.spec.ts deleted file mode 100644 index 96e30d441..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-bria.spec.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Wallets } from "@app" - -import { sat2btc, toSats } from "@domain/bitcoin" -import { UnknownRepositoryError } from "@domain/errors" -import { utxoSettledEventHandler } from "@servers/event-handlers/bria" - -import { BriaSubscriber, BriaPayloadType } from "@services/bria" -import { GrpcStreamClient, timeoutWithCancel } from "@utils" - -import { - bitcoindClient, - bitcoindOutside, - checkIsBalanced, - createMandatoryUsers, - createUserAndWalletFromPhone, - getDefaultWalletIdByPhone, - randomPhone, - sendToAddressAndConfirm, -} from "test/galoy/helpers" - -let walletIdA: WalletId - -const TIMEOUT_BRIA_EVENT = 60_000 - -const phone = randomPhone() - -beforeAll(async () => { - await createMandatoryUsers() - - await bitcoindClient.loadWallet({ filename: "outside" }) - - await createUserAndWalletFromPhone(phone) - walletIdA = await getDefaultWalletIdByPhone(phone) -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -afterAll(async () => { - jest.restoreAllMocks() - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -describe("BriaSubscriber", () => { - const bria = BriaSubscriber() - - describe("subscribeToAll", () => { - it("receives utxo events", async () => { - const amountSats = toSats(5_000) - // Receive onchain - const address = await Wallets.createOnChainAddress({ - walletId: walletIdA, - }) - if (address instanceof Error) throw address - expect(address.substring(0, 4)).toBe("bcrt") - - let expectedTxId: string | Error = "" - expectedTxId = await sendToAddressAndConfirm({ - walletClient: bitcoindOutside, - address, - amount: sat2btc(amountSats), - }) - if (expectedTxId instanceof Error) throw expectedTxId - - const receivedEvents: BriaEvent[] = [] - const nExpectedEvents = 2 - let recording = false - const testEventHandler = (resolve) => { - return async (event: BriaEvent): Promise => { - if ( - event.payload.type === BriaPayloadType.UtxoDetected && - event.payload.txId === expectedTxId - ) { - recording = true - } - // required to avoid checkIsBalanced error - if (event.payload.type === BriaPayloadType.UtxoSettled) { - await utxoSettledEventHandler({ event: event.payload }) - } - - if (recording) { - receivedEvents.push(event) - if (receivedEvents.length === nExpectedEvents) { - setTimeout(() => { - resolve(receivedEvents) - }, 1) - } - } - return Promise.resolve(true) - } - } - - const [timeoutPromise, cancelTimeoutFn] = timeoutWithCancel( - TIMEOUT_BRIA_EVENT, - "Timeout", - ) - - let wrapper - const eventPromise = new Promise(async (resolve) => { - wrapper = await bria.subscribeToAll(testEventHandler(resolve)) - }) - - const res = await Promise.race([eventPromise, timeoutPromise]) - if (res instanceof Error) throw res - cancelTimeoutFn() - - if (receivedEvents[0].payload.type != BriaPayloadType.UtxoDetected) { - throw new Error("unexpected event type") - } - expect(receivedEvents[0].payload.txId).toEqual(expectedTxId) - - wrapper.cancel() - }) - - it("re-subscribes", async () => { - const amountSats = toSats(5_000) - - // Receive onchain - const address = await Wallets.createOnChainAddress({ - walletId: walletIdA, - }) - if (address instanceof Error) throw address - expect(address.substring(0, 4)).toBe("bcrt") - - const expectedTxId = await sendToAddressAndConfirm({ - walletClient: bitcoindOutside, - address, - amount: sat2btc(amountSats), - }) - if (expectedTxId instanceof Error) throw expectedTxId - - let recording = false - const receivedEvents: BriaEvent[] = [] - const nExpectedEvents = 3 - const testEventHandler = (resolve) => { - return async (event: BriaEvent): Promise => { - if ( - event.payload.type === BriaPayloadType.UtxoDetected && - event.payload.txId === expectedTxId - ) { - recording = true - } - - // required to avoid checkIsBalanced error - if (event.payload.type === BriaPayloadType.UtxoSettled) { - await utxoSettledEventHandler({ event: event.payload }) - } - - if (recording) { - receivedEvents.push(event) - } - - if (receivedEvents.length == 2) { - return new UnknownRepositoryError() - } - if (receivedEvents.length === nExpectedEvents) { - setTimeout(() => { - resolve(receivedEvents) - }, 1) - } - return Promise.resolve(true) - } - } - - const [timeoutPromise, cancelTimeoutFn] = timeoutWithCancel( - TIMEOUT_BRIA_EVENT, - "Timeout", - ) - - let wrapper - const eventPromise = new Promise(async (resolve) => { - const stream = await bria.subscribeToAll(testEventHandler(resolve)) - if (stream instanceof Error) throw stream - stream.backoff = new GrpcStreamClient.FibonacciBackoff(100, 1) - wrapper = stream - }) - - const res = await Promise.race([eventPromise, timeoutPromise]) - if (res instanceof Error) throw res - cancelTimeoutFn() - - expect(receivedEvents[1]).toEqual(receivedEvents[2]) - - wrapper.cancel() - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts deleted file mode 100644 index 02caf1684..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { LightningError as LnError } from "lightning" - -import { SECS_PER_10_MINS } from "@config" - -import { Lightning } from "@app" -import * as Wallets from "@app/wallets" -import { handleHeldInvoices } from "@app/wallets" - -import { toSats } from "@domain/bitcoin" -import { InvoiceNotFoundError } from "@domain/bitcoin/lightning" -import { toCents } from "@domain/fiat" -import { CouldNotFindWalletInvoiceError } from "@domain/errors" - -import { WalletInvoicesRepository } from "@services/mongoose" -import { LedgerService } from "@services/ledger" -import { LndService } from "@services/lnd" -import { KnownLndErrorDetails } from "@services/lnd/errors" -import { baseLogger } from "@services/logger" -import { setupInvoiceSubscribe } from "@servers/trigger" - -import { sleep } from "@utils" - -import { parseLndErrorDetails } from "@services/lnd/config" - -import { WalletInvoice } from "@services/mongoose/schema" - -import { - checkIsBalanced, - createUserAndWalletFromPhone, - getBalanceHelper, - getDefaultWalletIdByPhone, - getError, - getHash, - getPubKey, - getUsdWalletIdByPhone, - lnd1, - lndOutside1, - pay, - randomPhone, - safePay, - subscribeToInvoices, -} from "test/galoy/helpers" - -let walletIdB: WalletId -let walletIdUsdB: WalletId -let walletIdF: WalletId -let walletIdUsdF: WalletId -let initBalanceB: Satoshis -let initBalanceUsdB: UsdCents - -const phoneB = randomPhone() -const phoneF = randomPhone() - -beforeAll(async () => { - await createUserAndWalletFromPhone(phoneB) - await createUserAndWalletFromPhone(phoneF) - walletIdB = await getDefaultWalletIdByPhone(phoneB) - walletIdUsdB = await getUsdWalletIdByPhone(phoneB) - walletIdF = await getDefaultWalletIdByPhone(phoneF) - walletIdUsdF = await getUsdWalletIdByPhone(phoneF) -}) - -beforeEach(async () => { - initBalanceB = toSats(await getBalanceHelper(walletIdB)) - initBalanceUsdB = toCents(await getBalanceHelper(walletIdUsdB)) -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -describe("UserWallet - Lightning", () => { - it("calls updateInvoice multiple times idempotently", async () => { - // larger amount to not fall below the escrow limit - const sats = 500_000 - const memo = "myMemo" - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdB, - amount: toSats(sats), - memo, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest, paymentHash } = lnInvoice - - const balanceBefore = await getBalanceHelper(walletIdB) - const updateInvoice = async () => { - // TODO: we could use event instead of a sleep to lower test latency - await sleep(500) - - return Wallets.updatePendingInvoiceByPaymentHash({ - paymentHash, - logger: baseLogger, - }) - } - - // first arg is the outsideLndpayResult - const [, result] = await Promise.all([ - safePay({ lnd: lndOutside1, request: paymentRequest }), - updateInvoice(), - ]) - expect(result).not.toBeInstanceOf(Error) - - const balanceAfter = await getBalanceHelper(walletIdB) - expect(balanceAfter).toBeGreaterThan(balanceBefore) - - // should be idempotent (not return error when called again) - const resultRetry = await updateInvoice() - expect(resultRetry).not.toBeInstanceOf(Error) - - const balanceAfterRetry = await getBalanceHelper(walletIdB) - expect(balanceAfterRetry).toEqual(balanceAfter) - }) - - it("if trigger is missing the USD invoice, then it should be denied", async () => { - /* - the reason we are doing this behavior is to limit the discrepancy between our books, - and the state of lnd. - if we get invoices that lnd has been settled because we were not using holdinvoice, - then there would be discrepancy between the time lnd settled the invoice - and the time it's being settle in our ledger - the reason this could happen is because trigger has to restart - the discrepancy in ledger is an okish behavior for bitcoin invoice, because there - are no price risk, but it's an unbearable risk for non bitcoin wallets, - because of the associated price risk exposure - */ - - const cents = 1000 - const memo = "myUsdMemo" - - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: walletIdUsdB as WalletId, - amount: toCents(cents), - memo, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice } = lnInvoice - - // fake timestamp in wallet invoice to avoid the use of fake timers - await WalletInvoice.findOneAndUpdate( - { _id: lnInvoice.paymentHash }, - { timestamp: new Date(Date.now() - SECS_PER_10_MINS * 1000) }, - ) - - const checker = await Lightning.PaymentStatusChecker(invoice) - if (checker instanceof Error) throw checker - - const isPaidBeforePay = await checker.invoiceIsPaid() - expect(isPaidBeforePay).not.toBeInstanceOf(Error) - expect(isPaidBeforePay).toBe(false) - - const paymentHash = getHash(invoice) - const pubkey = getPubKey(invoice) - - await Promise.all([ - (async () => { - const err = await getError(() => - pay({ lnd: lndOutside1, request: invoice }), - ) - expect(err[1]).toBe("PaymentRejectedByDestination") - })(), - (async () => { - await sleep(500) - - // make sure invoice is held - - const lndService = LndService() - if (lndService instanceof Error) throw lndService - - { - const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash }) - if (lnInvoiceLookup instanceof Error) throw lnInvoiceLookup - - expect(lnInvoiceLookup.isHeld).toBe(true) - } - - // handling invoice - await handleHeldInvoices(baseLogger) - - const ledger = LedgerService() - const ledgerTxs = await ledger.getTransactionsByHash(paymentHash) - if (ledgerTxs instanceof Error) throw ledgerTxs - expect(ledgerTxs).toStrictEqual([]) - - const isPaidAfterPay = await checker.invoiceIsPaid() - expect(isPaidAfterPay).not.toBeInstanceOf(Error) - expect(isPaidAfterPay).toBe(false) - - const finalBalance = await getBalanceHelper(walletIdUsdB) - expect(finalBalance).toBe(initBalanceUsdB) - - const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash }) - expect(lnInvoiceLookup).toBeInstanceOf(InvoiceNotFoundError) - - { - const walletInvoiceRepo = WalletInvoicesRepository() - const result = await walletInvoiceRepo.findByPaymentHash(paymentHash) - expect(result).toBeInstanceOf(CouldNotFindWalletInvoiceError) - } - - // making sure relooping is a no-op and doesn't throw - await handleHeldInvoices(baseLogger) - })(), - ]) - }) - - it("if trigger is missing the BTC invoice, then it should be processed", async () => { - const sats = 25000 - const memo = "myBtcMemo" - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdB as WalletId, - amount: toSats(sats), - memo, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice } = lnInvoice - - const checker = await Lightning.PaymentStatusChecker(invoice) - if (checker instanceof Error) throw checker - - const isPaidBeforePay = await checker.invoiceIsPaid() - expect(isPaidBeforePay).not.toBeInstanceOf(Error) - expect(isPaidBeforePay).toBe(false) - - const paymentHash = getHash(invoice) - const pubkey = getPubKey(invoice) - - await Promise.all([ - pay({ lnd: lndOutside1, request: invoice }), - (async () => { - await sleep(500) - - // make sure invoice is held - - const lndService = LndService() - if (lndService instanceof Error) throw lndService - - { - const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash }) - if (lnInvoiceLookup instanceof Error) throw lnInvoiceLookup - - expect(lnInvoiceLookup.isHeld).toBe(true) - } - - // handling invoice - await handleHeldInvoices(baseLogger) - - const ledger = LedgerService() - const ledgerTxs = await ledger.getTransactionsByHash(paymentHash) - if (ledgerTxs instanceof Error) throw ledgerTxs - expect(ledgerTxs).toHaveLength(1) - - const isPaidAfterPay = await checker.invoiceIsPaid() - expect(isPaidAfterPay).not.toBeInstanceOf(Error) - expect(isPaidAfterPay).toBe(true) - - const finalBalance = await getBalanceHelper(walletIdB) - expect(finalBalance).toBe(initBalanceB + sats) - - const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash }) - expect(lnInvoiceLookup).not.toBeInstanceOf(Error) - if (lnInvoiceLookup instanceof Error) throw lnInvoiceLookup - expect(lnInvoiceLookup.isSettled).toBeTruthy() - - { - const walletInvoiceRepo = WalletInvoicesRepository() - const result = await walletInvoiceRepo.findByPaymentHash(paymentHash) - expect(result).not.toBeInstanceOf(Error) - if (result instanceof Error) throw result - expect(result.paid).toBeTruthy() - } - - // making sure relooping is a no-op and doesn't throw - await handleHeldInvoices(baseLogger) - })(), - ]) - }) -}) - -describe("Invoice handling from trigger", () => { - describe("btc recipient invoice", () => { - const sats = toSats(500) - - it("should process held invoice when trigger comes back up", async () => { - // Create invoice for self - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdF, - amount: sats, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - if (lnInvoice instanceof Error) throw lnInvoice - - // Pay invoice promise - const startPay = async () => { - try { - return await pay({ - lnd: lndOutside1, - request: lnInvoice.paymentRequest, - }) - } catch (err) { - return parseLndErrorDetails(err) - } - } - - // Listener promise - const delayedListener = async (subInvoices) => { - await sleep(500) - setupInvoiceSubscribe({ - lnd: lnd1, - pubkey: process.env.LND1_PUBKEY as Pubkey, - subInvoices, - }) - } - - // Pay and then listen - const subInvoices = subscribeToInvoices({ lnd: lnd1 }) - const [result] = await Promise.all([startPay(), delayedListener(subInvoices)]) - - // See successful payment - expect(result.is_confirmed).toBeTruthy() - subInvoices.removeAllListeners() - }) - - it("should process new invoice payment when trigger comes back up", async () => { - // Create invoice for self - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdF, - amount: sats, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - if (lnInvoice instanceof Error) throw lnInvoice - - // Kick off listener - const subInvoices = subscribeToInvoices({ lnd: lnd1 }) - setupInvoiceSubscribe({ - lnd: lnd1, - pubkey: process.env.LND1_PUBKEY as Pubkey, - subInvoices, - }) - - // Pay invoice - const result = await pay({ - lnd: lndOutside1, - request: lnInvoice.paymentRequest, - }) - - // See successful payment - expect(result.is_confirmed).toBeTruthy() - subInvoices.removeAllListeners() - }) - }) - - describe("usd recipient invoice", () => { - const cents = toCents(100) - - it("should process held invoice when trigger comes back up", async () => { - // Create invoice for self - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: walletIdUsdF, - amount: cents, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - if (lnInvoice instanceof Error) throw lnInvoice - - // Pay invoice promise - const startPay = async () => { - try { - return await pay({ - lnd: lndOutside1, - request: lnInvoice.paymentRequest, - }) - } catch (err) { - return parseLndErrorDetails(err) - } - } - - // Listener promise - const delayedListener = async (subInvoices) => { - await sleep(500) - setupInvoiceSubscribe({ - lnd: lnd1, - pubkey: process.env.LND1_PUBKEY as Pubkey, - subInvoices, - }) - } - - // Pay and then listen - const subInvoices = subscribeToInvoices({ lnd: lnd1 }) - const [result] = await Promise.all([startPay(), delayedListener(subInvoices)]) - - // See successful payment - expect(result.is_confirmed).toBeTruthy() - subInvoices.removeAllListeners() - }) - - it("should decline held invoice when trigger comes back up", async () => { - // Create invoice for self - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: walletIdUsdF, - amount: cents, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - if (lnInvoice instanceof Error) throw lnInvoice - - // fake timestamp in wallet invoice to avoid the use of fake timers - await WalletInvoice.findOneAndUpdate( - { _id: lnInvoice.paymentHash }, - { timestamp: new Date(Date.now() - SECS_PER_10_MINS * 1000) }, - ) - - // Pay invoice promise - const startPay = async () => { - try { - return await pay({ - lnd: lndOutside1, - request: lnInvoice.paymentRequest, - }) - } catch (err) { - return parseLndErrorDetails(err) - } - } - - // Listener promise - const delayedListener = async (subInvoices) => { - await sleep(500) - setupInvoiceSubscribe({ - lnd: lnd1, - pubkey: process.env.LND1_PUBKEY as Pubkey, - subInvoices, - }) - } - - // Pay and then listen - const subInvoices = subscribeToInvoices({ lnd: lnd1 }) - const [result] = await Promise.all([startPay(), delayedListener(subInvoices)]) - - // See successful payment - expect(result).toMatch(KnownLndErrorDetails.PaymentRejectedByDestination) - subInvoices.removeAllListeners() - }) - - it("should process new invoice payment when trigger comes back up", async () => { - // Create invoice for self - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: walletIdUsdF, - amount: cents, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - if (lnInvoice instanceof Error) throw lnInvoice - - // Kick off listener - const subInvoices = subscribeToInvoices({ lnd: lnd1 }) - setupInvoiceSubscribe({ - lnd: lnd1, - pubkey: process.env.LND1_PUBKEY as Pubkey, - subInvoices, - }) - - // Pay invoice - const result = await pay({ - lnd: lndOutside1, - request: lnInvoice.paymentRequest, - }) - - // See successful payment - expect(result.is_confirmed).toBeTruthy() - subInvoices.removeAllListeners() - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-receive-rewards.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-receive-rewards.spec.ts deleted file mode 100644 index c9464b45c..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-receive-rewards.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Payments } from "@app" -import { MEMO_SHARING_SATS_THRESHOLD, OnboardingEarn } from "@config" -import { getFunderWalletId } from "@services/ledger/caching" -import { AccountsRepository, WalletsRepository } from "@services/mongoose" -import difference from "lodash.difference" -import find from "lodash.find" - -import { - checkIsBalanced, - createUserAndWalletFromPhone, - getAccountIdByPhone, - getDefaultWalletIdByPhone, - getAccountRecordByPhone, - randomPhone, -} from "test/galoy/helpers" -import { resetSelfAccountIdLimits } from "test/galoy/helpers/rate-limit" -import { getBalanceHelper, getTransactionsForWalletId } from "test/galoy/helpers/wallet" - -let accountIdB: AccountId -let walletIdB: WalletId - -const onBoardingEarnIds = [ - "whereBitcoinExist" as QuizQuestionId, - "whyStonesShellGold" as QuizQuestionId, - "NoCounterfeitMoney" as QuizQuestionId, -] -const onBoardingEarnAmt: number = Object.keys(OnboardingEarn) - .filter((k) => find(onBoardingEarnIds, (o) => o === k)) - .reduce((p, k) => p + OnboardingEarn[k], 0) - -jest.mock("@config", () => { - const config = jest.requireActual("@config") - config.yamlConfig.rewards = { - denyPhoneCountries: [], - allowPhoneCountries: ["US"], - denyIPCountries: [], - allowIPCountries: [], - denyASNs: [], - allowASNs: [], - } - return config -}) - -jest.mock("@domain/accounts-ips/ip-metadata-authorizer", () => ({ - IPMetadataAuthorizer: () => ({ - authorize: () => true, - }), -})) - -jest.mock("@domain/users", () => ({ - PhoneMetadataAuthorizer: () => ({ - authorize: () => true, - }), -})) - -const phone = randomPhone() - -beforeAll(async () => { - await createUserAndWalletFromPhone(phone) - - accountIdB = await getAccountIdByPhone(phone) - walletIdB = await getDefaultWalletIdByPhone(phone) -}) - -afterAll(async () => { - jest.restoreAllMocks() -}) - -describe("UserWallet - addEarn", () => { - it("adds balance only once", async () => { - const resetOk = await resetSelfAccountIdLimits(accountIdB) - if (resetOk instanceof Error) throw resetOk - - const initialBalance = await getBalanceHelper(walletIdB) - - const accountRecordBBeforeEarn = await getAccountRecordByPhone(phone) - - const getAndVerifyRewards = async () => { - for (const onBoardingEarnId of onBoardingEarnIds) { - await Payments.addEarn({ - quizQuestionId: onBoardingEarnId as QuizQuestionId, - accountId: accountIdB, - }) - } - const finalBalance = await getBalanceHelper(walletIdB) - let rewards = onBoardingEarnAmt - - if (difference(onBoardingEarnIds, accountRecordBBeforeEarn.earn).length === 0) { - rewards = 0 - } - - expect(finalBalance).toBe(initialBalance + rewards) - await checkIsBalanced() - } - - await getAndVerifyRewards() - - // yet, if we do it another time, the balance should not increase, - // because all the rewards has already been been consumed: - await getAndVerifyRewards() - }) - - it("receives transaction with reward memo", async () => { - const OnboardingEarnIds = Object.keys(OnboardingEarn) - expect(OnboardingEarnIds.length).toBeGreaterThanOrEqual(1) - - const { result: transactionsBefore } = await getTransactionsForWalletId(walletIdB) - - let OnboardingEarnId = "" - let txCheck: WalletTransaction | undefined - for (OnboardingEarnId of OnboardingEarnIds) { - txCheck = transactionsBefore?.slice.find((tx) => tx.memo === OnboardingEarnId) - if (!txCheck) break - } - expect(txCheck).toBeUndefined() - - const amount = OnboardingEarn[OnboardingEarnId] - expect(amount).toBeLessThan(MEMO_SHARING_SATS_THRESHOLD) - - const funderWalletId = await getFunderWalletId() - const funderWallet = await WalletsRepository().findById(funderWalletId) - if (funderWallet instanceof Error) throw funderWallet - const funderAccount = await AccountsRepository().findById(funderWallet.accountId) - if (funderAccount instanceof Error) throw funderAccount - const payment = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - senderWalletId: funderWalletId, - senderAccount: funderAccount, - recipientWalletId: walletIdB, - amount, - memo: OnboardingEarnId, - }) - if (payment instanceof Error) return payment - - const { result: transactionsAfter } = await getTransactionsForWalletId(walletIdB) - const rewardTx = transactionsAfter?.slice.find((tx) => tx.memo === OnboardingEarnId) - expect(rewardTx).not.toBeUndefined() - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts deleted file mode 100644 index 29a844342..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts +++ /dev/null @@ -1,685 +0,0 @@ -import { Payments, Wallets } from "@app" -import { getMidPriceRatio } from "@app/prices" - -import { getDealerConfig } from "@config" - -import { PaymentSendStatus } from "@domain/bitcoin/lightning" -import { LimitsExceededError } from "@domain/errors" -import { paymentAmountFromNumber, WalletCurrency } from "@domain/shared" - -import { AccountsRepository } from "@services/mongoose" - -import { - addNewWallet, - checkIsBalanced, - createInvoice, - randomAccount, - lndOutside1, - createRandomUserAndWallets, - fundWallet, - createRandomUserAndBtcWallet, -} from "test/galoy/helpers" - -const MOCKED_LIMIT = 100 as UsdCents -const AMOUNT_ABOVE_THRESHOLD = 10 as UsdCents -const MOCKED_BALANCE_ABOVE_THRESHOLD = (MOCKED_LIMIT + 20) as UsdCents -const accountLimits: IAccountLimits = { - intraLedgerLimit: MOCKED_LIMIT, - withdrawalLimit: MOCKED_LIMIT, - tradeIntraAccountLimit: MOCKED_LIMIT, -} - -jest.mock("@config", () => { - const config = jest.requireActual("@config") - return { - ...config, - getDealerConfig: jest.fn().mockReturnValue({ - usd: { hedgingEnabled: true }, - }), - getAccountLimits: jest.fn().mockReturnValue({ - intraLedgerLimit: 100 as UsdCents, - withdrawalLimit: 100 as UsdCents, - tradeIntraAccountLimit: 100 as UsdCents, - }), - getInvoiceCreateAttemptLimits: jest.fn().mockReturnValue({ - ...config.getInvoiceCreateAttemptLimits(), - points: 100, - }), - } -}) - -const centsToUsdString = (cents: UsdCents) => `$${(cents / 100).toFixed(2)}` - -const usdHedgeEnabled = getDealerConfig().usd.hedgingEnabled - -let otherAccountId: AccountId -let otherBtcWallet: Wallet -let otherUsdWallet: Wallet // eslint-disable-line @typescript-eslint/no-unused-vars - -beforeAll(async () => { - otherAccountId = (await randomAccount()).id - - const btcWallet = await addNewWallet({ - accountId: otherAccountId, - currency: WalletCurrency.Btc, - }) - if (btcWallet instanceof Error) throw btcWallet - otherBtcWallet = btcWallet - - const usdWallet = await addNewWallet({ - accountId: otherAccountId, - currency: WalletCurrency.Usd, - }) - if (usdWallet instanceof Error) throw usdWallet - otherUsdWallet = usdWallet -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -afterAll(() => { - jest.restoreAllMocks() -}) - -const btcAmountFromUsdNumber = async ( - centsAmount: number | bigint, -): Promise => { - const usdPaymentAmount = paymentAmountFromNumber({ - amount: Number(centsAmount), - currency: WalletCurrency.Usd, - }) - if (usdPaymentAmount instanceof Error) throw usdPaymentAmount - - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - return midPriceRatio.convertFromUsd(usdPaymentAmount) -} - -describe("UserWallet Limits - Lightning Pay", () => { - describe("single payment above limit fails limit check", () => { - it("fails to pay when withdrawalLimit exceeded", async () => { - // Create new wallet - const btcWalletDescriptor = await createRandomUserAndBtcWallet() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: await btcAmountFromUsdNumber(MOCKED_BALANCE_ABOVE_THRESHOLD), - }) - - // Test limits - const usdAmountAboveThreshold = - accountLimits.withdrawalLimit + AMOUNT_ABOVE_THRESHOLD - const btcThresholdAmount = await btcAmountFromUsdNumber(usdAmountAboveThreshold) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: Number(btcThresholdAmount.amount), - }) - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.withdrawalLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - }) - - it("fails to pay when amount exceeds intraLedger limit", async () => { - // Create new wallet - const btcWalletDescriptor = await createRandomUserAndBtcWallet() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: await btcAmountFromUsdNumber(MOCKED_BALANCE_ABOVE_THRESHOLD), - }) - - // Test limits - const usdAmountAboveThreshold = - accountLimits.intraLedgerLimit + AMOUNT_ABOVE_THRESHOLD - const btcThresholdAmount = await btcAmountFromUsdNumber(usdAmountAboveThreshold) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: otherBtcWallet.id, - amount: Number(btcThresholdAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.intraLedgerLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - }) - - it("fails to pay when amount exceeds tradeIntraAccount limit", async () => { - const usdFundingAmount = paymentAmountFromNumber({ - amount: MOCKED_BALANCE_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdFundingAmount instanceof Error) throw usdFundingAmount - - // Create new wallets - const { btcWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: await btcAmountFromUsdNumber(MOCKED_BALANCE_ABOVE_THRESHOLD), - }) - await fundWallet({ - walletId: usdWalletDescriptor.id, - balanceAmount: usdFundingAmount, - }) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - const usdAmountAboveThreshold = - accountLimits.tradeIntraAccountLimit + AMOUNT_ABOVE_THRESHOLD - const btcThresholdAmount = await btcAmountFromUsdNumber(usdAmountAboveThreshold) - const usdPaymentAmount = paymentAmountFromNumber({ - amount: usdAmountAboveThreshold, - currency: WalletCurrency.Usd, - }) - if (usdPaymentAmount instanceof Error) throw usdPaymentAmount - - // Test BTC -> USD limits - { - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: usdWalletDescriptor.id, - amount: Number(usdPaymentAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.tradeIntraAccountLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - } - - // Test USD -> BTC limits - { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: btcWalletDescriptor.id, - amount: Number(btcThresholdAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequestBtc } = lnInvoice - - const paymentResultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: uncheckedPaymentRequestBtc, - memo: null, - senderWalletId: usdWalletDescriptor.id, - senderAccount, - }) - expect(paymentResultUsd).toBeInstanceOf(LimitsExceededError) - const expectedErrorUsd = `Cannot transfer more than ${centsToUsdString( - accountLimits.tradeIntraAccountLimit, - )} in 24 hours` - expect((paymentResultUsd as Error).message).toBe(expectedErrorUsd) - } - }) - }) - - describe("multiple payments up to limit succeed, last payment fails", () => { - const getPartialAmountsForMultiplePaymentsBelowLimit = async ({ - limit, - numPayments, - }: { - limit: keyof IAccountLimits - numPayments: number - }) => { - const bufferForSpread = 2 - const partialUsdAmount = - Math.floor(accountLimits[limit] / numPayments) - bufferForSpread - - const partialUsdSendAmount = paymentAmountFromNumber({ - amount: partialUsdAmount, - currency: WalletCurrency.Usd, - }) - if (partialUsdSendAmount instanceof Error) throw partialUsdAmount - - const partialBtcSendAmount = await btcAmountFromUsdNumber(partialUsdAmount) - - return { - partialUsdSendAmount, - partialBtcSendAmount, - } - } - - it("fails to pay when intraLedgerLimit exceeded", async () => { - // Create and fund new wallets - const btcFundingAmount = await btcAmountFromUsdNumber( - MOCKED_BALANCE_ABOVE_THRESHOLD, - ) - const usdFundingAmount = paymentAmountFromNumber({ - amount: MOCKED_BALANCE_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdFundingAmount instanceof Error) throw usdFundingAmount - - const { btcWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: btcFundingAmount, - }) - await fundWallet({ - walletId: usdWalletDescriptor.id, - balanceAmount: usdFundingAmount, - }) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - // Succeeds for multiple payments below limit - const numPayments = 2 - const { partialBtcSendAmount } = - await getPartialAmountsForMultiplePaymentsBelowLimit({ - limit: "intraLedgerLimit", - numPayments, - }) - for (let i = 0; i < numPayments; i++) { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: otherBtcWallet.id, - amount: Number(partialBtcSendAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - const btcAmountAboveThreshold = await btcAmountFromUsdNumber(AMOUNT_ABOVE_THRESHOLD) - const usdAmountAboveThreshold = paymentAmountFromNumber({ - amount: AMOUNT_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdAmountAboveThreshold instanceof Error) throw usdAmountAboveThreshold - - { - // Fails for payment just above limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: otherBtcWallet.id, - amount: Number(btcAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.intraLedgerLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - - const paymentResultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: usdWalletDescriptor.id, - senderAccount, - }) - expect(paymentResultUsd).toBeInstanceOf(LimitsExceededError) - const expectedErrorUsd = `Cannot transfer more than ${centsToUsdString( - accountLimits.intraLedgerLimit, - )} in 24 hours` - expect((paymentResultUsd as Error).message).toBe(expectedErrorUsd) - } - - { - // Succeeds for same payment just above withdrawal limit - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: Number(btcAmountAboveThreshold.amount), - }) - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - { - // Succeeds for same payment just above tradeIntraAccount limit - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: usdWalletDescriptor.id, - amount: Number(usdAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - }) - - it("fails to pay when tradeIntraAccountLimit exceeded", async () => { - // Create and fund new wallets - const btcFundingAmount = await btcAmountFromUsdNumber( - MOCKED_BALANCE_ABOVE_THRESHOLD, - ) - const usdFundingAmount = paymentAmountFromNumber({ - amount: MOCKED_BALANCE_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdFundingAmount instanceof Error) throw usdFundingAmount - - const { btcWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: btcFundingAmount, - }) - await fundWallet({ - walletId: usdWalletDescriptor.id, - balanceAmount: usdFundingAmount, - }) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - // Succeeds for multiple payments below limit - const numPayments = 2 - const { partialBtcSendAmount, partialUsdSendAmount } = - await getPartialAmountsForMultiplePaymentsBelowLimit({ - limit: "tradeIntraAccountLimit", - numPayments, - }) - { - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: usdWalletDescriptor.id, - amount: Number(partialUsdSendAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: btcWalletDescriptor.id, - amount: Number(partialBtcSendAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: usdWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - const btcAmountAboveThreshold = await btcAmountFromUsdNumber(AMOUNT_ABOVE_THRESHOLD) - const usdAmountAboveThreshold = paymentAmountFromNumber({ - amount: AMOUNT_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdAmountAboveThreshold instanceof Error) throw usdAmountAboveThreshold - - { - // Fails for payment just above limit, from btc - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: usdWalletDescriptor.id, - amount: Number(usdAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.tradeIntraAccountLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - } - - { - // Fails for payment just above limit, from usd - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: btcWalletDescriptor.id, - amount: Number(btcAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: usdWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.tradeIntraAccountLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - } - - { - // Succeeds for same payment just above withdrawal limit - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: Number(btcAmountAboveThreshold.amount), - }) - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - { - // Succeeds for same payment just above intraledger limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: otherBtcWallet.id, - amount: Number(btcAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - }) - - it("fails to pay when withdrawalLimit exceeded", async () => { - // Create and fund new wallets - const btcFundingAmount = await btcAmountFromUsdNumber( - MOCKED_BALANCE_ABOVE_THRESHOLD, - ) - const usdFundingAmount = paymentAmountFromNumber({ - amount: MOCKED_BALANCE_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdFundingAmount instanceof Error) throw usdFundingAmount - - const { btcWalletDescriptor, usdWalletDescriptor } = - await createRandomUserAndWallets() - await fundWallet({ - walletId: btcWalletDescriptor.id, - balanceAmount: btcFundingAmount, - }) - await fundWallet({ - walletId: usdWalletDescriptor.id, - balanceAmount: usdFundingAmount, - }) - - const senderAccount = await AccountsRepository().findById( - btcWalletDescriptor.accountId, - ) - if (senderAccount instanceof Error) throw senderAccount - - // Succeeds for multiple payments below limit - const numPayments = 2 - const { partialBtcSendAmount } = - await getPartialAmountsForMultiplePaymentsBelowLimit({ - limit: "withdrawalLimit", - numPayments, - }) - for (let i = 0; i < numPayments; i++) { - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: Number(partialBtcSendAmount.amount), - }) - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - const btcAmountAboveThreshold = await btcAmountFromUsdNumber(AMOUNT_ABOVE_THRESHOLD) - const usdAmountAboveThreshold = paymentAmountFromNumber({ - amount: AMOUNT_ABOVE_THRESHOLD, - currency: WalletCurrency.Usd, - }) - if (usdAmountAboveThreshold instanceof Error) throw usdAmountAboveThreshold - - { - // Fails for payment just above limit - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: Number(btcAmountAboveThreshold.amount), - }) - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: request, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(LimitsExceededError) - const expectedError = `Cannot transfer more than ${centsToUsdString( - accountLimits.withdrawalLimit, - )} in 24 hours` - expect((paymentResult as Error).message).toBe(expectedError) - } - - { - // Succeeds for same payment just above intraledger limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: otherBtcWallet.id, - amount: Number(btcAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - - { - // Succeeds for same payment just above tradeIntraAccount limit - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: usdWalletDescriptor.id, - amount: Number(usdAmountAboveThreshold.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: btcWalletDescriptor.id, - senderAccount, - }) - expect(paymentResult).not.toBeInstanceOf(Error) - expect(paymentResult).toBe(PaymentSendStatus.Success) - } - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-tx-display.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-tx-display.spec.ts deleted file mode 100644 index 3f899496c..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-tx-display.spec.ts +++ /dev/null @@ -1,1203 +0,0 @@ -import { Payments, Wallets } from "@app" -import { getCurrentPriceAsDisplayPriceRatio } from "@app/prices" - -import { - MaxFeeTooLargeForRoutelessPaymentError, - PaymentSendStatus, -} from "@domain/bitcoin/lightning" -import { sat2btc, toSats } from "@domain/bitcoin" -import { LedgerTransactionType, UnknownLedgerError } from "@domain/ledger" -import * as LnFeesImpl from "@domain/payments" -import { paymentAmountFromNumber, WalletCurrency } from "@domain/shared" -import { TxStatus } from "@domain/wallets" -import { UsdDisplayCurrency, displayAmountFromNumber } from "@domain/fiat" - -import { updateDisplayCurrency } from "@app/accounts" - -import { PayoutSpeed } from "@domain/bitcoin/onchain" - -import { translateToLedgerTx } from "@services/ledger" -import { MainBook, Transaction } from "@services/ledger/books" -import * as BriaImpl from "@services/bria" -import { BriaPayloadType } from "@services/bria" -import { AccountsRepository } from "@services/mongoose" -import { toObjectId } from "@services/mongoose/utils" -import { baseLogger } from "@services/logger" - -import { - utxoDetectedEventHandler, - utxoSettledEventHandler, -} from "@servers/event-handlers/bria" - -import { sleep } from "@utils" - -import { - bitcoindClient, - bitcoindOutside, - createChainAddress, - createInvoice, - createUserAndWalletFromPhone, - getAccountByPhone, - getDefaultWalletIdByPhone, - getTransactionsForWalletId, - getUsdWalletIdByPhone, - lndOutside1, - onceBriaSubscribe, - RANDOM_ADDRESS, - randomPhone, - safePay, - sendToAddressAndConfirm, -} from "test/galoy/helpers" - -let accountB: Account -let accountC: Account - -let walletIdB: WalletId -let walletIdUsdB: WalletId -let walletIdC: WalletId - -const phoneB = randomPhone() -const phoneC = randomPhone() - -beforeAll(async () => { - await createUserAndWalletFromPhone(phoneB) - await createUserAndWalletFromPhone(phoneC) - - accountB = await getAccountByPhone(phoneB) - accountC = await getAccountByPhone(phoneC) - - walletIdB = await getDefaultWalletIdByPhone(phoneB) - walletIdUsdB = await getUsdWalletIdByPhone(phoneB) - walletIdC = await getDefaultWalletIdByPhone(phoneC) - - await bitcoindClient.loadWallet({ filename: "outside" }) - - // Update account display currencies - const updatedAccountB = await updateDisplayCurrency({ - accountId: accountB.id, - currency: "EUR", - }) - if (updatedAccountB instanceof Error) throw updatedAccountB - - const updatedAccountC = await updateDisplayCurrency({ - accountId: accountC.id, - currency: "CRC", - }) - if (updatedAccountC instanceof Error) throw updatedAccountC - - const accountBRaw = await AccountsRepository().findById(accountB.id) - if (accountBRaw instanceof Error) throw accountBRaw - accountB = accountBRaw - if (accountB.displayCurrency !== "EUR") { - throw new Error("Error changing display currency for accountB") - } - - const accountCRaw = await AccountsRepository().findById(accountC.id) - if (accountCRaw instanceof Error) throw accountCRaw - accountC = accountCRaw - if (accountC.displayCurrency !== "CRC") { - throw new Error("Error changing display currency for accountC") - } -}) - -afterAll(async () => { - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -describe("Display properties on transactions", () => { - const getAllTransactionsByHash = async ( - hash: PaymentHash | OnChainTxHash, - ): Promise[] | LedgerServiceError> => { - try { - const { results } = await MainBook.ledger({ - hash, - }) - /* eslint @typescript-eslint/ban-ts-comment: "off" */ - // @ts-ignore-next-line no-implicit-any error - return results.map((tx) => translateToLedgerTx(tx)) - } catch (err) { - return new UnknownLedgerError(err) - } - } - - const getAllTransactionsByJournalId = async ( - journalId: LedgerJournalId, - ): Promise[] | LedgerServiceError> => { - try { - const { results } = await MainBook.ledger({ - _journal: toObjectId(journalId), - }) - /* eslint @typescript-eslint/ban-ts-comment: "off" */ - // @ts-ignore-next-line no-implicit-any error - return results.map((tx) => translateToLedgerTx(tx)) - } catch (err) { - return new UnknownLedgerError(err) - } - } - - const getAllTransactionsByMemo = async ( - memoPayer: string, - ): Promise[] | LedgerServiceError> => { - try { - const { results } = await MainBook.ledger({ - memoPayer, - }) - /* eslint @typescript-eslint/ban-ts-comment: "off" */ - // @ts-ignore-next-line no-implicit-any error - return results.map((tx) => translateToLedgerTx(tx)) - } catch (err) { - return new UnknownLedgerError(err) - } - } - - const getDisplayAmounts = async ({ - satsAmount, - satsFee, - }: { - satsAmount: Satoshis - satsFee: Satoshis - }) => { - const currencies = ["EUR" as DisplayCurrency, "CRC" as DisplayCurrency] - - const btcAmount = paymentAmountFromNumber({ - amount: satsAmount, - currency: WalletCurrency.Btc, - }) - if (btcAmount instanceof Error) throw btcAmount - const btcFee = paymentAmountFromNumber({ - amount: satsFee, - currency: WalletCurrency.Btc, - }) - if (btcFee instanceof Error) throw btcFee - - const result: Record< - string, - { - displayAmount: DisplayCurrencyBaseAmount - displayFee: DisplayCurrencyBaseAmount - displayCurrency: DisplayCurrency - } - > = {} - for (const currency of currencies) { - const displayPriceRatio = await getCurrentPriceAsDisplayPriceRatio({ - currency, - }) - if (displayPriceRatio instanceof Error) throw displayPriceRatio - - const displayAmount = displayPriceRatio.convertFromWallet(btcAmount) - if (satsAmount > 0 && displayAmount.amountInMinor === 0n) { - displayAmount.amountInMinor = 1n - } - - const displayFee = displayPriceRatio.convertFromWalletToCeil(btcFee) - - result[currency] = { - displayAmount: Number(displayAmount.amountInMinor) as DisplayCurrencyBaseAmount, - displayFee: Number(displayFee.amountInMinor) as DisplayCurrencyBaseAmount, - displayCurrency: currency, - } - } - - return result - } - - describe("ln", () => { - describe("receive", () => { - it("(LnReceiveLedgerMetadata) receives payment from outside", async () => { - // larger amount to not fall below the escrow limit - const amountInvoice = toSats(100_000) - - const recipientWalletId = walletIdB - - // Receive payment - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: recipientWalletId, - amount: amountInvoice, - memo, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice, paymentHash } = lnInvoice - - const [outsideLndpayResult, updateResult] = await Promise.all([ - safePay({ lnd: lndOutside1, request: invoice }), - (async () => { - // TODO: we could use event instead of a sleep to lower test latency - await sleep(500) - return Wallets.updatePendingInvoiceByPaymentHash({ - paymentHash, - logger: baseLogger, - }) - })(), - ]) - expect(outsideLndpayResult?.is_confirmed).toBe(true) - expect(updateResult).not.toBeInstanceOf(Error) - - // Check entries - const txns = await getAllTransactionsByHash(paymentHash) - if (txns instanceof Error) throw txns - - const recipientTxn = txns.find( - ({ walletId, type }) => - walletId === recipientWalletId && type === LedgerTransactionType.Invoice, - ) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== recipientWalletId) - expect(internalTxns.length).toBeGreaterThan(0) - - const { EUR: expectedRecipientDisplayProps } = await getDisplayAmounts({ - satsAmount: recipientTxn.satsAmount || toSats(0), - satsFee: recipientTxn.satsFee || toSats(0), - }) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedRecipientDisplayProps, - type: LedgerTransactionType.Invoice, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: recipientTxn.centsAmount, - displayFee: recipientTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - }) - - describe("intraledger", () => { - it("(LnIntraledgerLedgerMetadata) sends to another Galoy user with memo", async () => { - // TxMetadata: - // - LnIntraledgerLedgerMetadata - - const amountInvoice = 1_001 - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdC - - // Send payment - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: recipientWalletId, - amount: amountInvoice, - memo, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice } = lnInvoice - - const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: invoice, - memo: null, - senderWalletId: senderWalletId, - senderAccount, - }) - if (paymentResult instanceof Error) throw paymentResult - - // Check entries - const txns = await getAllTransactionsByHash(lnInvoice.paymentHash) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find(({ walletId }) => walletId === senderWalletId) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - const recipientTxn = txns.find(({ walletId }) => walletId === recipientWalletId) - - const { EUR: expectedSenderDisplayProps, CRC: expectedRecipientDisplayProps } = - await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.LnIntraLedger, - }), - ) - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedRecipientDisplayProps, - type: LedgerTransactionType.LnIntraLedger, - }), - ) - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(0) - }) - - it("(LnTradeIntraAccountLedgerMetadata) pay self amountless invoice from btc wallet to usd wallet", async () => { - // TxMetadata: - // - LnIntraledgerLedgerMetadata - - const amountInvoice = toSats(100) - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdUsdB - - // Send payment - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const request = await Wallets.addInvoiceNoAmountForSelf({ - walletId: recipientWalletId, - memo, - }) - if (request instanceof Error) throw request - const { paymentRequest: uncheckedPaymentRequest, paymentHash } = request - - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest, - memo: null, - senderWalletId: senderWalletId, - senderAccount: senderAccount, - amount: amountInvoice, - }) - if (paymentResult instanceof Error) throw paymentResult - expect(paymentResult).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByHash(paymentHash) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find(({ walletId }) => walletId === senderWalletId) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - const recipientTxn = txns.find(({ walletId }) => walletId === recipientWalletId) - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(2) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.LnTradeIntraAccount, - }), - ) - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.LnTradeIntraAccount, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - }) - - describe("send", () => { - it("(LnSendLedgerMetadata) pay zero amount invoice with amount less than 1 cent", async () => { - // TxMetadata: - // - LnSendLedgerMetadata - - const amountInvoice = toSats(1) - - const senderWalletId = walletIdB - - // Send payment - const { request, id } = await createInvoice({ lnd: lndOutside1 }) - const paymentHash = id as PaymentHash - - const { result: fee, error } = - await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - walletId: senderWalletId, - uncheckedPaymentRequest: request, - amount: amountInvoice, - }) - if (error instanceof Error) throw error - expect(fee).not.toBeNull() - - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: request, - memo: null, - amount: amountInvoice, - senderWalletId: walletIdB, - senderAccount: accountB, - }) - if (paymentResult instanceof Error) throw paymentResult - expect(paymentResult).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByHash(paymentHash) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, type }) => - walletId === senderWalletId && type === LedgerTransactionType.Payment, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== senderWalletId) - expect(internalTxns.length).toBeGreaterThan(0) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.Payment, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - - const reimbursementTxn = txns.find( - ({ walletId, type }) => - walletId === senderWalletId && - type === LedgerTransactionType.LnFeeReimbursement, - ) - expect(reimbursementTxn).toBeUndefined() - }) - - it("(LnFeeReimbursementReceiveLedgerMetadata) pay zero amount invoice", async () => { - // TxMetadata: - // - LnSendLedgerMetadata - // - LnFeeReimbursementReceiveLedgerMetadata - - const amountInvoice = toSats(20_000) - - const senderWalletId = walletIdB - - // Send payment - const { request, id } = await createInvoice({ lnd: lndOutside1 }) - const paymentHash = id as PaymentHash - - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: request, - memo: null, - amount: amountInvoice, - senderWalletId: walletIdB, - senderAccount: accountB, - }) - if (paymentResult instanceof Error) throw paymentResult - expect(paymentResult).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByHash(paymentHash) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, type }) => - walletId === senderWalletId && type === LedgerTransactionType.Payment, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const reimbursementTxn = txns.find( - ({ walletId, type }) => - walletId === senderWalletId && - type === LedgerTransactionType.LnFeeReimbursement, - ) - if (reimbursementTxn === undefined) - throw new Error("'reimbursementTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== senderWalletId) - expect(internalTxns.length).toBeGreaterThan(0) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - const { EUR: expectedReimbursementDisplayProps } = await getDisplayAmounts({ - satsAmount: reimbursementTxn.satsAmount || toSats(0), - satsFee: reimbursementTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.Payment, - }), - ) - expect(reimbursementTxn).toEqual( - expect.objectContaining({ - ...expectedReimbursementDisplayProps, - type: LedgerTransactionType.LnFeeReimbursement, - }), - ) - - expect(internalTxns).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - type: LedgerTransactionType.Payment, - }), - expect.objectContaining({ - displayAmount: reimbursementTxn.centsAmount, - displayFee: reimbursementTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - type: LedgerTransactionType.LnFeeReimbursement, - }), - ]), - ) - }) - - it("(LnFailedPaymentReceiveLedgerMetadata) pay zero amount invoice & revert txn when verifyMaxFee fails", async () => { - // TxMetadata: - // - LnFailedPaymentReceiveLedgerMetadata - const { LnFees: LnFeesOrig } = jest.requireActual("@domain/payments") - const lndFeesSpy = jest.spyOn(LnFeesImpl, "LnFees").mockReturnValue({ - ...LnFeesOrig(), - verifyMaxFee: () => new MaxFeeTooLargeForRoutelessPaymentError(), - }) - - const senderWalletId = walletIdB - const senderAccount = accountB - - const amountInvoice = toSats(20_000) - - const { request, id } = await createInvoice({ lnd: lndOutside1 }) - const paymentHash = id as PaymentHash - - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: request, - memo: null, - amount: amountInvoice, - senderWalletId, - senderAccount, - }) - expect(paymentResult).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - // Restore system state - lndFeesSpy.mockReset() - - const txns = await getAllTransactionsByHash(paymentHash) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, debit }) => walletId === senderWalletId && debit > 0, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const repaidTxn = txns.find( - ({ walletId, credit }) => walletId === senderWalletId && credit > 0, - ) - if (repaidTxn === undefined) throw new Error("'repaidTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== senderWalletId) - expect(internalTxns.length).toEqual(2) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.Payment, - }), - ) - expect(repaidTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.Payment, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - }) - }) - - describe("onchain", () => { - const sendToWalletTestWrapper = async ({ - amountSats, - walletId, - }: { - amountSats: Satoshis - walletId: WalletId - }) => { - const address = await Wallets.createOnChainAddress({ - walletId, - }) - if (address instanceof Error) throw address - expect(address.substring(0, 4)).toBe("bcrt") - - const txId = await sendToAddressAndConfirm({ - walletClient: bitcoindOutside, - address, - amount: sat2btc(amountSats), - }) - if (txId instanceof Error) throw txId - - // Register confirmed txn in database - const detectedEvent = await onceBriaSubscribe({ - type: BriaPayloadType.UtxoDetected, - txId, - }) - if (detectedEvent?.payload.type !== BriaPayloadType.UtxoDetected) { - throw new Error(`Expected ${BriaPayloadType.UtxoDetected} event`) - } - const resultPending = await utxoDetectedEventHandler({ - event: detectedEvent.payload, - }) - if (resultPending instanceof Error) { - throw resultPending - } - - const settledEvent = await onceBriaSubscribe({ - type: BriaPayloadType.UtxoSettled, - txId, - }) - if (settledEvent?.payload.type !== BriaPayloadType.UtxoSettled) { - throw new Error(`Expected ${BriaPayloadType.UtxoSettled} event`) - } - const resultSettled = await utxoSettledEventHandler({ event: settledEvent.payload }) - if (resultSettled instanceof Error) { - throw resultSettled - } - - return txId - } - - describe("receive", () => { - it("(OnChainReceiveLedgerMetadata) receives on-chain transaction", async () => { - // TxMetadata: - // - OnChainReceiveLedgerMetadata - - const amountSats = toSats(20_000) - - const recipientWalletId = walletIdB - - // Execute receive - const txId = await sendToWalletTestWrapper({ - walletId: recipientWalletId, - amountSats, - }) - - // Check entries - const txns = await getAllTransactionsByHash(txId) - if (txns instanceof Error) throw txns - - const recipientTxn = txns.find(({ walletId }) => walletId === recipientWalletId) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== recipientWalletId) - expect(internalTxns.length).toBeGreaterThan(0) - - const { EUR: expectedRecipientDisplayProps } = await getDisplayAmounts({ - satsAmount: recipientTxn.satsAmount || toSats(0), - satsFee: recipientTxn.satsFee || toSats(0), - }) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedRecipientDisplayProps, - type: LedgerTransactionType.OnchainReceipt, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: recipientTxn.centsAmount, - displayFee: recipientTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - - it("(Pending, no metadata) identifies unconfirmed incoming on-chain transactions", async () => { - const amountSats = toSats(20_000) - - const recipientWalletId = walletIdB - - // Execute receive - const address = await Wallets.createOnChainAddress({ - walletId: recipientWalletId, - }) - if (address instanceof Error) throw address - - const txId = (await bitcoindOutside.sendToAddress({ - address, - amount: sat2btc(amountSats), - })) as OnChainTxHash - - const detectedEvent = await onceBriaSubscribe({ - type: BriaPayloadType.UtxoDetected, - txId, - }) - if (detectedEvent?.payload.type !== BriaPayloadType.UtxoDetected) { - throw new Error(`Expected ${BriaPayloadType.UtxoDetected} event`) - } - const resultPending = await utxoDetectedEventHandler({ - event: detectedEvent.payload, - }) - if (resultPending instanceof Error) { - throw resultPending - } - - // Check entries - const { result: txs, error } = await getTransactionsForWalletId(recipientWalletId) - if (error instanceof Error || txs === null) { - throw error - } - const pendingTxs = txs.slice.filter(({ status }) => status === TxStatus.Pending) - expect(pendingTxs.length).toBe(1) - const recipientTxn = pendingTxs[0] - - const { EUR: expectedRecipientDisplayProps } = await getDisplayAmounts({ - satsAmount: toSats(recipientTxn.settlementAmount), - satsFee: toSats(recipientTxn.settlementFee), - }) - - const settlementDisplayAmountObj = displayAmountFromNumber({ - amount: expectedRecipientDisplayProps.displayAmount, - currency: expectedRecipientDisplayProps.displayCurrency, - }) - if (settlementDisplayAmountObj instanceof Error) throw settlementDisplayAmountObj - - const settlementDisplayFeeObj = displayAmountFromNumber({ - amount: expectedRecipientDisplayProps.displayFee, - currency: expectedRecipientDisplayProps.displayCurrency, - }) - if (settlementDisplayFeeObj instanceof Error) throw settlementDisplayFeeObj - - const expectedRecipientWalletTxnDisplayProps = { - settlementDisplayAmount: settlementDisplayAmountObj.displayInMajor, - settlementDisplayFee: settlementDisplayFeeObj.displayInMajor, - } - - expect(recipientTxn).toEqual( - expect.objectContaining(expectedRecipientWalletTxnDisplayProps), - ) - expect(recipientTxn.settlementDisplayPrice).toEqual( - expect.objectContaining({ - displayCurrency: expectedRecipientDisplayProps.displayCurrency, - }), - ) - - // Settle pending - await bitcoindOutside.generateToAddress({ nblocks: 3, address: RANDOM_ADDRESS }) - - const settledEvent = await onceBriaSubscribe({ - type: BriaPayloadType.UtxoSettled, - txId, - }) - if (settledEvent?.payload.type !== BriaPayloadType.UtxoSettled) { - throw new Error(`Expected ${BriaPayloadType.UtxoSettled} event`) - } - const resultSettled = await utxoSettledEventHandler({ - event: settledEvent.payload, - }) - if (resultSettled instanceof Error) { - throw resultSettled - } - }) - }) - - describe("intraledger", () => { - it("(OnChainIntraledgerLedgerMetadata) pays another galoy user via onchain address", async () => { - // TxMetadata: - // - OnChainIntraledgerLedgerMetadata - const amountSats = toSats(20_000) - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdC - - // Execute Send - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const address = await Wallets.createOnChainAddress({ - walletId: recipientWalletId, - }) - if (address instanceof Error) throw address - - const paid = await Wallets.payOnChainByWalletIdForBtcWallet({ - senderAccount, - senderWalletId, - address, - amount: amountSats, - speed: PayoutSpeed.Fast, - memo, - }) - if (paid instanceof Error) throw paid - expect(paid.status).toBe(PaymentSendStatus.Success) - - // Check entries - const memoTxns = await getAllTransactionsByMemo(memo) - if (memoTxns instanceof Error) throw memoTxns - expect(memoTxns.length).toEqual(1) - const { journalId } = memoTxns[0] - const txns = await getAllTransactionsByJournalId(journalId) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, debit }) => walletId === senderWalletId && debit > 0, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const recipientTxn = txns.find( - ({ walletId, credit }) => walletId === recipientWalletId && credit > 0, - ) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(0) - - const { EUR: expectedSenderDisplayProps, CRC: expectedRecipientDisplayProps } = - await getDisplayAmounts({ - satsAmount: recipientTxn.satsAmount || toSats(0), - satsFee: recipientTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.OnchainIntraLedger, - }), - ) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedRecipientDisplayProps, - type: LedgerTransactionType.OnchainIntraLedger, - }), - ) - }) - - it("(OnChainTradeIntraAccountLedgerMetadata) self trade via onchain address", async () => { - // TxMetadata: - // - OnChainTradeIntraAccountLedgerMetadata - - const amountSats = toSats(20_000) - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdUsdB - - // Execute Send - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const address = await Wallets.createOnChainAddress({ - walletId: recipientWalletId, - }) - if (address instanceof Error) throw address - - const paid = await Wallets.payOnChainByWalletIdForBtcWallet({ - senderAccount, - senderWalletId, - address, - amount: amountSats, - speed: PayoutSpeed.Fast, - memo, - }) - if (paid instanceof Error) throw paid - expect(paid.status).toBe(PaymentSendStatus.Success) - - // Check entries - const memoTxns = await getAllTransactionsByMemo(memo) - if (memoTxns instanceof Error) throw memoTxns - expect(memoTxns.length).toEqual(1) - const { journalId } = memoTxns[0] - const txns = await getAllTransactionsByJournalId(journalId) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, debit }) => walletId === senderWalletId && debit > 0, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const recipientTxn = txns.find( - ({ walletId, credit }) => walletId === recipientWalletId && credit > 0, - ) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(2) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: recipientTxn.satsAmount || toSats(0), - satsFee: recipientTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.OnChainTradeIntraAccount, - currency: WalletCurrency.Btc, - }), - ) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.OnChainTradeIntraAccount, - currency: WalletCurrency.Usd, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - }) - - describe("send", () => { - it("(OnChainSendLedgerMetadata) sends an onchain payment", async () => { - // TxMetadata: - // - OnChainSendLedgerMetadata - - const { OnChainService: OnChainServiceOrig } = - jest.requireActual("@services/bria") - const briaSpy = jest.spyOn(BriaImpl, "OnChainService").mockReturnValue({ - ...OnChainServiceOrig(), - queuePayoutToAddress: async () => "payoutId" as PayoutId, - }) - - const amountSats = toSats(20_000) - - const senderWalletId = walletIdB - const senderAccount = accountB - - // Execute Send - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const { address } = await createChainAddress({ - format: "p2wpkh", - lnd: lndOutside1, - }) - - const paid = await Wallets.payOnChainByWalletIdForBtcWallet({ - senderAccount, - senderWalletId, - address, - amount: amountSats, - speed: PayoutSpeed.Fast, - memo, - }) - if (paid instanceof Error) throw paid - expect(paid.status).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByMemo(memo) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, debit }) => walletId === senderWalletId && debit > 0, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - - const internalTxns = txns.filter(({ walletId }) => walletId !== senderWalletId) - expect(internalTxns.length).toEqual(2) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.OnchainPayment, - currency: WalletCurrency.Btc, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - - // Clean up after test - await Transaction.deleteMany({ memo }) - briaSpy.mockRestore() - }) - }) - }) - - describe("wallet-id", () => { - describe("intraledger", () => { - it("(WalletIdIntraledgerLedgerMetadata) sends to an internal walletId", async () => { - // TxMetadata: - // - WalletIdIntraledgerLedgerMetadata - - const amountSats = 20_000 - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdC - - // Send payment - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const paid = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - senderWalletId, - senderAccount, - memo, - recipientWalletId, - amount: amountSats, - }) - expect(paid).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByMemo(memo) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find(({ walletId }) => walletId === senderWalletId) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - const recipientTxn = txns.find(({ walletId }) => walletId === recipientWalletId) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(0) - - const { EUR: expectedSenderDisplayProps, CRC: expectedRecipientDisplayProps } = - await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.IntraLedger, - currency: WalletCurrency.Btc, - }), - ) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedRecipientDisplayProps, - type: LedgerTransactionType.IntraLedger, - currency: WalletCurrency.Btc, - }), - ) - }) - - it("(WalletIdTradeIntraAccountLedgerMetadata) sends to self WalletId", async () => { - // TxMetadata: - // - WalletIdTradeIntraAccountLedgerMetadata - - const amountSats = 20_000 - - const senderWalletId = walletIdB - const senderAccount = accountB - const recipientWalletId = walletIdUsdB - - // Send payment - const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - - const paid = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - senderWalletId, - senderAccount, - memo, - recipientWalletId, - amount: amountSats, - }) - expect(paid).toBe(PaymentSendStatus.Success) - - // Check entries - const txns = await getAllTransactionsByMemo(memo) - if (txns instanceof Error) throw txns - - const senderTxn = txns.find( - ({ walletId, debit }) => walletId === senderWalletId && debit > 0, - ) - if (senderTxn === undefined) throw new Error("'senderTxn' not found") - const recipientTxn = txns.find( - ({ walletId, credit }) => walletId === recipientWalletId && credit > 0, - ) - if (recipientTxn === undefined) throw new Error("'recipientTxn' not found") - - const internalTxns = txns.filter( - ({ walletId }) => walletId !== senderWalletId && walletId !== recipientWalletId, - ) - expect(internalTxns.length).toEqual(2) - - const { EUR: expectedSenderDisplayProps } = await getDisplayAmounts({ - satsAmount: senderTxn.satsAmount || toSats(0), - satsFee: senderTxn.satsFee || toSats(0), - }) - - expect(senderTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.WalletIdTradeIntraAccount, - currency: WalletCurrency.Btc, - }), - ) - - expect(recipientTxn).toEqual( - expect.objectContaining({ - ...expectedSenderDisplayProps, - type: LedgerTransactionType.WalletIdTradeIntraAccount, - currency: WalletCurrency.Usd, - }), - ) - - for (const txn of internalTxns) { - expect(txn).toEqual( - expect.objectContaining({ - displayAmount: senderTxn.centsAmount, - displayFee: senderTxn.centsFee, - displayCurrency: UsdDisplayCurrency, - }), - ) - } - }) - }) - }) -}) diff --git a/test/galoy/legacy-integration/02-user-wallet/02-tx-onchain-fees.spec.ts b/test/galoy/legacy-integration/02-user-wallet/02-tx-onchain-fees.spec.ts deleted file mode 100644 index fe67eedc5..000000000 --- a/test/galoy/legacy-integration/02-user-wallet/02-tx-onchain-fees.spec.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { once } from "events" - -import { Wallets } from "@app" -import { getOnChainWalletConfig } from "@config" -import { sat2btc, toSats } from "@domain/bitcoin" -import { LessThanDustThresholdError } from "@domain/errors" -import { toCents } from "@domain/fiat" -import { WalletCurrency, paymentAmountFromNumber } from "@domain/shared" -import { PayoutSpeed } from "@domain/bitcoin/onchain" - -import { DealerPriceService } from "@services/dealer-price" -import { AccountsRepository, WalletsRepository } from "@services/mongoose" -import { baseLogger } from "@services/logger" -import { getFunderWalletId } from "@services/ledger/caching" - -import { - lndCreateOnChainAddress, - bitcoindClient, - bitcoindOutside, - createUserAndWalletFromPhone, - getAccountByPhone, - getDefaultWalletIdByPhone, - getUsdWalletIdByPhone, - lndonchain, - randomPhone, - sendToAddressAndConfirm, - subscribeToChainAddress, - waitUntilBlockHeight, -} from "test/galoy/helpers" - -const defaultAmount = toSats(5244) -const defaultUsdAmount = toCents(105) -const defaultSpeed = PayoutSpeed.Fast -const { dustThreshold } = getOnChainWalletConfig() - -let walletIdA: WalletId -let walletIdB: WalletId -let walletIdUsdA: WalletId -let accountA: Account - -const dealerFns = DealerPriceService() - -const phoneA = randomPhone() -const phoneB = randomPhone() - -beforeAll(async () => { - await bitcoindClient.loadWallet({ filename: "outside" }) - - await createUserAndWalletFromPhone(phoneA) - await createUserAndWalletFromPhone(phoneB) - - walletIdA = await getDefaultWalletIdByPhone(phoneA) - walletIdUsdA = await getUsdWalletIdByPhone(phoneA) - accountA = await getAccountByPhone(phoneA) - walletIdB = await getDefaultWalletIdByPhone(phoneB) - - // Fund walletIdA - await sendToLndWalletTestWrapper({ - amountSats: toSats(defaultAmount * 5), - walletId: walletIdA, - }) - - // Fund walletIdUsdA - await sendToLndWalletTestWrapper({ - amountSats: toSats(defaultAmount * 5), - walletId: walletIdUsdA, - }) - - // Fund lnd - const funderWalletId = await getFunderWalletId() - await sendToLndWalletTestWrapper({ - amountSats: toSats(2_000_000_000), - walletId: funderWalletId, - }) -}) - -afterAll(async () => { - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -const sendToLndWalletTestWrapper = async ({ - amountSats, - walletId, -}: { - amountSats: Satoshis - walletId: WalletId -}) => { - const address = await lndCreateOnChainAddress(walletId) - if (address instanceof Error) throw address - expect(address.substring(0, 4)).toBe("bcrt") - - const initialBlockNumber = await bitcoindClient.getBlockCount() - const txId = await sendToAddressAndConfirm({ - walletClient: bitcoindOutside, - address, - amount: sat2btc(amountSats), - }) - if (txId instanceof Error) throw txId - - // Register confirmed txn in database - const sub = subscribeToChainAddress({ - lnd: lndonchain, - bech32_address: address, - min_height: initialBlockNumber, - }) - await once(sub, "confirmation") - sub.removeAllListeners() - - await waitUntilBlockHeight({ lnd: lndonchain }) - - const updated = await Wallets.updateLegacyOnChainReceipt({ logger: baseLogger }) - if (updated instanceof Error) throw updated - - return txId as OnChainTxHash -} - -describe("UserWallet - getOnchainFee", () => { - describe("from btc wallet", () => { - it("returns a fee greater than zero for an external address", async () => { - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - const feeAmount = await Wallets.getOnChainFeeForBtcWallet({ - walletId: walletIdA, - account: accountA, - amount: defaultAmount, - address, - speed: defaultSpeed, - }) - if (feeAmount instanceof Error) throw feeAmount - expect(feeAmount.currency).toBe(WalletCurrency.Btc) - const fee = Number(feeAmount.amount) - expect(fee).toBeGreaterThan(0) - - const wallet = await WalletsRepository().findById(walletIdA) - if (wallet instanceof Error) throw wallet - - const account = await AccountsRepository().findById(wallet.accountId) - if (account instanceof Error) throw account - - expect(fee).toBeGreaterThan(account.withdrawFee) - }) - - it("returns zero for an on us address", async () => { - const address = await Wallets.createOnChainAddress({ - walletId: walletIdB, - }) - if (address instanceof Error) throw address - const feeAmount = await Wallets.getOnChainFeeForBtcWallet({ - walletId: walletIdA, - account: accountA, - amount: defaultAmount, - address, - speed: defaultSpeed, - }) - if (feeAmount instanceof Error) throw feeAmount - const fee = Number(feeAmount.amount) - expect(fee).toBe(0) - }) - - it("returns error for dust amount", async () => { - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - const amount = toSats(dustThreshold - 1) - const fee = await Wallets.getOnChainFeeForBtcWallet({ - walletId: walletIdA, - account: accountA, - amount, - address, - speed: defaultSpeed, - }) - expect(fee).toBeInstanceOf(LessThanDustThresholdError) - expect(fee).toHaveProperty( - "message", - `Use lightning to send amounts less than ${dustThreshold} sats`, - ) - }) - - it("returns error for minimum amount", async () => { - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - const amount = toSats(1) - const fee = await Wallets.getOnChainFeeForBtcWallet({ - walletId: walletIdA, - account: accountA, - amount, - address, - speed: defaultSpeed, - }) - expect(fee).toBeInstanceOf(LessThanDustThresholdError) - expect(fee).toHaveProperty( - "message", - `Use lightning to send amounts less than ${dustThreshold} sats`, - ) - }) - }) - - describe("from usd wallet", () => { - const amountCases = [ - { amountCurrency: WalletCurrency.Usd, senderAmount: defaultUsdAmount }, - { amountCurrency: WalletCurrency.Btc, senderAmount: defaultAmount }, - ] - const testAmountCaseAmounts = async ( - convert: ( - amount: UsdPaymentAmount, - ) => Promise, - ) => { - const usdAmount = amountCases.filter( - (testCase) => testCase.amountCurrency === WalletCurrency.Usd, - )[0].senderAmount - - const convertedBtcFromUsdAmount = await convert({ - amount: BigInt(usdAmount), - currency: WalletCurrency.Usd, - }) - if (convertedBtcFromUsdAmount instanceof Error) throw convertedBtcFromUsdAmount - - expect(defaultAmount).toEqual(Number(convertedBtcFromUsdAmount.amount)) - } - - amountCases.forEach(({ amountCurrency, senderAmount }) => { - describe(`${amountCurrency.toLowerCase()} send amount`, () => { - it("returns a fee greater than zero for an external address", async () => { - await testAmountCaseAmounts(dealerFns.getSatsFromCentsForImmediateSell) - - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - - const paymentAmount = paymentAmountFromNumber({ - amount: defaultAmount, - currency: WalletCurrency.Btc, - }) - if (paymentAmount instanceof Error) throw paymentAmount - - const getFeeArgs = { - walletId: walletIdUsdA, - account: accountA, - address, - speed: defaultSpeed, - amount: senderAmount, - } - const feeAmount = - amountCurrency === WalletCurrency.Usd - ? await Wallets.getOnChainFeeForUsdWallet(getFeeArgs) - : await Wallets.getOnChainFeeForUsdWalletAndBtcAmount(getFeeArgs) - if (feeAmount instanceof Error) throw feeAmount - expect(feeAmount.currency).toBe(WalletCurrency.Usd) - const fee = Number(feeAmount.amount) - expect(fee).toBeGreaterThan(0) - - const wallet = await WalletsRepository().findById(walletIdUsdA) - if (wallet instanceof Error) throw wallet - - const account = await AccountsRepository().findById(wallet.accountId) - if (account instanceof Error) throw account - - const usdAmount = await dealerFns.getCentsFromSatsForImmediateSell({ - amount: BigInt(account.withdrawFee), - currency: WalletCurrency.Btc, - }) - if (usdAmount instanceof Error) throw usdAmount - const withdrawFeeAsUsd = Number(usdAmount.amount) - expect(fee).toBeGreaterThan(withdrawFeeAsUsd) - }) - - it("returns zero for an on us address", async () => { - const address = await Wallets.createOnChainAddress({ - walletId: walletIdB, - }) - if (address instanceof Error) throw address - - const getFeeArgs = { - walletId: walletIdUsdA, - account: accountA, - address, - speed: defaultSpeed, - amount: senderAmount, - } - const feeAmount = - amountCurrency === WalletCurrency.Usd - ? await Wallets.getOnChainFeeForUsdWallet(getFeeArgs) - : await Wallets.getOnChainFeeForUsdWalletAndBtcAmount(getFeeArgs) - if (feeAmount instanceof Error) throw feeAmount - const fee = Number(feeAmount.amount) - expect(fee).toBe(0) - }) - - it("returns error for dust amount", async () => { - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - const amount = toSats(dustThreshold - 1) - - const usdAmount = await dealerFns.getCentsFromSatsForImmediateBuy({ - amount: BigInt(amount), - currency: WalletCurrency.Btc, - }) - if (usdAmount instanceof Error) throw usdAmount - const amountInUsd = Number(usdAmount.amount) - - const getFeeArgsPartial = { - walletId: walletIdUsdA, - account: accountA, - address, - speed: defaultSpeed, - } - const fee = - amountCurrency === WalletCurrency.Usd - ? await Wallets.getOnChainFeeForUsdWallet({ - ...getFeeArgsPartial, - amount: amountInUsd, - }) - : await Wallets.getOnChainFeeForUsdWalletAndBtcAmount({ - ...getFeeArgsPartial, - amount, - }) - expect(fee).toBeInstanceOf(LessThanDustThresholdError) - expect(fee).toHaveProperty( - "message", - `Use lightning to send amounts less than ${dustThreshold} sats`, - ) - }) - - it("returns error for minimum amount", async () => { - const address = (await bitcoindOutside.getNewAddress()) as OnChainAddress - - const getFeeArgsPartial = { - walletId: walletIdUsdA, - account: accountA, - address, - speed: defaultSpeed, - } - const fee = - amountCurrency === WalletCurrency.Usd - ? await Wallets.getOnChainFeeForUsdWallet({ - ...getFeeArgsPartial, - amount: toCents(1), - }) - : await Wallets.getOnChainFeeForUsdWalletAndBtcAmount({ - ...getFeeArgsPartial, - amount: toSats(1), - }) - expect(fee).toBeInstanceOf(LessThanDustThresholdError) - expect(fee).toHaveProperty( - "message", - `Use lightning to send amounts less than ${dustThreshold} sats`, - ) - }) - }) - }) - }) -}) diff --git a/test/galoy/legacy-integration/app/accounts/multiple-wallet-account.spec.ts b/test/galoy/legacy-integration/app/accounts/multiple-wallet-account.spec.ts deleted file mode 100644 index 4b4cc5594..000000000 --- a/test/galoy/legacy-integration/app/accounts/multiple-wallet-account.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Accounts } from "@app" -import { getDefaultAccountsConfig } from "@config" -import { WalletCurrency } from "@domain/shared" -import { WalletType } from "@domain/wallets" -import { WalletsRepository } from "@services/mongoose" -import mongoose from "mongoose" - -import { - createUserAndWalletFromPhone, - getAccountByPhone, - getUsdWalletIdByPhone, - randomPhone, - randomUserId, -} from "test/galoy/helpers" - -it("change default walletId of account", async () => { - const phone = randomPhone() - - const kratosUserId = randomUserId() - - const account = await Accounts.createAccountWithPhoneIdentifier({ - newAccountInfo: { phone, kratosUserId }, - config: getDefaultAccountsConfig(), - }) - if (account instanceof Error) throw account - - const accountId = account.id - - const newWallet = await WalletsRepository().persistNew({ - accountId, - type: WalletType.Checking, - currency: WalletCurrency.Btc, - }) - if (newWallet instanceof Error) throw newWallet - - const newAccount = await Accounts.updateDefaultWalletId({ - accountId: accountId, - walletId: newWallet.id, - }) - - if (newAccount instanceof Error) throw newAccount - - expect(newAccount.defaultWalletId).toBe(newWallet.id) -}) - -it("fail to create an invalid account", async () => { - const id = new mongoose.Types.ObjectId() - - const newWallet = await WalletsRepository().persistNew({ - accountId: id as unknown as AccountId, - type: WalletType.Checking, - currency: WalletCurrency.Btc, - }) - - expect(newWallet).toBeInstanceOf(Error) -}) - -it("does not add a usd wallet if one exists", async () => { - const phone = randomPhone() - - await createUserAndWalletFromPhone(phone) - const account = await getAccountByPhone(phone) - const usdWalletId = await getUsdWalletIdByPhone(phone) - expect(usdWalletId).toBeDefined() - - const newWallet = await Accounts.addWalletIfNonexistent({ - accountId: account.id, - type: WalletType.Checking, - currency: WalletCurrency.Usd, - }) - if (newWallet instanceof Error) throw newWallet - - expect(newWallet.id).toBe(usdWalletId) -}) diff --git a/test/galoy/legacy-integration/app/cron/delete-payments.spec.ts b/test/galoy/legacy-integration/app/cron/delete-payments.spec.ts deleted file mode 100644 index 0922cd97f..000000000 --- a/test/galoy/legacy-integration/app/cron/delete-payments.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Lightning, Payments } from "@app" - -import { TWO_MONTHS_IN_MS } from "@config" - -import { toSats } from "@domain/bitcoin" -import { - PaymentNotFoundError, - PaymentSendStatus, - PaymentStatus, -} from "@domain/bitcoin/lightning" - -import { LndService } from "@services/lnd" - -import { - bitcoindClient, - checkIsBalanced, - createInvoice, - createUserAndWalletFromPhone, - fundWalletIdFromOnchain, - getAccountByPhone, - getDefaultWalletIdByPhone, - lnd1, - lndOutside1, - randomPhone, -} from "test/galoy/helpers" - -let accountB: Account -let walletIdB: WalletId - -const phone = randomPhone() - -beforeAll(async () => { - await bitcoindClient.loadWallet({ filename: "outside" }) - - await createUserAndWalletFromPhone(phone) - accountB = await getAccountByPhone(phone) - walletIdB = await getDefaultWalletIdByPhone(phone) - - await fundWalletIdFromOnchain({ - walletId: walletIdB, - amountInBitcoin: 0.02, - lnd: lnd1, - }) -}) - -afterEach(async () => { - await checkIsBalanced() -}) - -afterAll(async () => { - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -describe("Delete payments from Lnd - Lightning Pay", () => { - it("runs delete-payment cronjob", async () => { - // Create payment - const { request, secret, id } = await createInvoice({ lnd: lndOutside1 }) - const paymentHash = id as PaymentHash - const revealedPreImage = secret as RevealedPreImage - - const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - uncheckedPaymentRequest: request, - memo: null, - amount: toSats(1000), - senderWalletId: walletIdB, - senderAccount: accountB, - }) - if (paymentResult instanceof Error) throw paymentResult - expect(paymentResult).toBe(PaymentSendStatus.Success) - - const lndService = LndService() - if (lndService instanceof Error) return lndService - - // Confirm payment exists in lnd - let retrievedPayment = await lndService.lookupPayment({ paymentHash }) - expect(retrievedPayment).not.toBeInstanceOf(Error) - if (retrievedPayment instanceof Error) return retrievedPayment - expect(retrievedPayment.status).toBe(PaymentStatus.Settled) - if (retrievedPayment.status !== PaymentStatus.Settled) return - expect(retrievedPayment.confirmedDetails?.revealedPreImage).toBe(revealedPreImage) - - // Run delete-payments cronjob - const timestamp2Months = new Date(Date.now() - TWO_MONTHS_IN_MS) - expect(Number(timestamp2Months)).toBeLessThan(Number(retrievedPayment.createdAt)) - const deleteLnPayments1Hour = await Lightning.deleteLnPaymentsBefore(timestamp2Months) - if (deleteLnPayments1Hour instanceof Error) throw deleteLnPayments1Hour - - // Confirm payment still exists - retrievedPayment = await lndService.lookupPayment({ paymentHash }) - expect(retrievedPayment).not.toBeInstanceOf(Error) - if (retrievedPayment instanceof Error) return retrievedPayment - expect(retrievedPayment.status).toBe(PaymentStatus.Settled) - if (retrievedPayment.status !== PaymentStatus.Settled) return - expect(retrievedPayment.confirmedDetails?.revealedPreImage).toBe(revealedPreImage) - - // Run updateLnPayments task - const lnPaymentUpdateOnPending = await Lightning.updateLnPayments() - if (lnPaymentUpdateOnPending instanceof Error) throw lnPaymentUpdateOnPending - - // Run delete-payments cronjob again for payments before 2 weeks ago - const deleteLnPayments1HourRetry = - await Lightning.deleteLnPaymentsBefore(timestamp2Months) - if (deleteLnPayments1HourRetry instanceof Error) throw deleteLnPayments1HourRetry - - // Confirm payment still exists - retrievedPayment = await lndService.lookupPayment({ paymentHash }) - expect(retrievedPayment).not.toBeInstanceOf(Error) - if (retrievedPayment instanceof Error) return retrievedPayment - expect(retrievedPayment.status).toBe(PaymentStatus.Settled) - if (retrievedPayment.status !== PaymentStatus.Settled) return - expect(retrievedPayment.confirmedDetails?.revealedPreImage).toBe(revealedPreImage) - - // Run delete-payments cronjob again for all payments - const timestampNow = new Date() - expect(Number(timestampNow)).toBeGreaterThan(Number(retrievedPayment.createdAt)) - const deleteLnPayments = await Lightning.deleteLnPaymentsBefore(timestampNow) - if (deleteLnPayments instanceof Error) throw deleteLnPayments - - // Confirm payment was deleted - const retrievedDeletedPayment = await lndService.lookupPayment({ paymentHash }) - expect(retrievedDeletedPayment).toBeInstanceOf(PaymentNotFoundError) - }) -}) diff --git a/test/galoy/legacy-integration/app/swap/swap-listener.spec.ts b/test/galoy/legacy-integration/app/swap/swap-listener.spec.ts deleted file mode 100644 index 74a9f9813..000000000 --- a/test/galoy/legacy-integration/app/swap/swap-listener.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable jest/no-conditional-expect */ -import { handleSwapOutCompleted } from "@app/swap" -import { lnd1LoopConfig } from "@app/swap/get-active-loopd" -import { WalletCurrency } from "@domain/shared" -import { LoopService } from "@services/loopd" -import { sleep } from "@utils" - -import { mineBlockAndSyncAll } from "test/galoy/helpers" - -describe("Swap", () => { - it("Initiate Swap out, then listen for events", async () => { - const amount = { amount: BigInt(250000), currency: WalletCurrency.Btc } - const swapService = LoopService(lnd1LoopConfig()) - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - await new Promise(async (resolve) => { - // 1) Start Swap Listener - const listener = swapService.swapListener() - listener.on("data", async (response) => { - await handleSwapOutCompleted(response) - expect(response).toBeDefined() - resolve(true) - }) - // 2) Trigger Swap Out - await swapService.swapOut({ amount }) - await sleep(1000) - // 3) Mine blocks and wait a few seconds between rounds - await mineBlockAndSyncAll() - await sleep(1000) - await mineBlockAndSyncAll() - await sleep(1000) - await mineBlockAndSyncAll() - // 4) The swap out should have been picked up by the listener by now - // and triggered the handleSwapOutCompleted above - // 5) Cancel the listener - listener.cancel() - expect.assertions(1) - resolve(true) - }) - } else { - const msg = "Swap Server not running...skipping" - expect(msg).toEqual(msg) - } - }) -}) diff --git a/test/galoy/legacy-integration/app/swap/swap-out.spec.ts b/test/galoy/legacy-integration/app/swap/swap-out.spec.ts deleted file mode 100644 index 15ed86ebe..000000000 --- a/test/galoy/legacy-integration/app/swap/swap-out.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint-disable jest/no-conditional-expect */ -import { swapOut } from "@app/swap" -import { - getActiveLoopd, - lnd1LoopConfig, - lnd2LoopConfig, -} from "@app/swap/get-active-loopd" -import { OnChainService } from "@services/bria" - -import { - SwapClientNotResponding, - SwapErrorChannelBalanceTooLow, -} from "@domain/swap/errors" -import { SwapOutChecker } from "@domain/swap" -import { WalletCurrency, ZERO_SATS } from "@domain/shared" - -import { baseLogger } from "@services/logger" -import { LoopService } from "@services/loopd" -import { lndsBalances } from "@services/lnd/utils" - -describe("Swap", () => { - const activeLoopd = getActiveLoopd() - const swapService = LoopService(activeLoopd ?? lnd1LoopConfig()) - const amount: BtcPaymentAmount = { amount: 250000n, currency: WalletCurrency.Btc } - - it("Swap out app returns a SwapOutResult or NoSwapAction", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const swapResult = await swapOut() - if (swapResult instanceof Error) throw swapResult - expect(swapResult).not.toBeInstanceOf(Error) - if (swapResult.noOp) { - expect(swapResult.noOp).toBe(true) - } else { - expect(swapResult).toEqual( - expect.objectContaining({ - swapId: expect.any(String), - }), - ) - } - } - }) - - it("Swap out for default active loop node or lnd1-loop node returns successful swap result", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const swapDestAddress = await OnChainService().getAddressForSwap() - if (swapDestAddress instanceof Error) return swapDestAddress - const swapResult = await swapService.swapOut({ amount, swapDestAddress }) - if (swapResult instanceof SwapClientNotResponding) { - baseLogger.info("Swap Client is not running, skipping") - return - } - if (swapResult instanceof Error) throw swapResult - expect(swapResult).not.toBeInstanceOf(Error) - expect(swapResult).toEqual( - expect.objectContaining({ - swapId: expect.any(String), - }), - ) - } - }) - - it("Swap out for lnd2-loop node returns successful swap result or error if not enough funds", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const loopService = LoopService(lnd2LoopConfig()) - const swapServiceLnd2 = loopService - const isSwapServerUp2 = await swapServiceLnd2.healthCheck() - if (isSwapServerUp2) { - const swapDestAddress = await OnChainService().getAddressForSwap() - if (swapDestAddress instanceof Error) return swapDestAddress - // this might fail in not enough funds in LND2 in regtest - const swapResult = await swapServiceLnd2.swapOut({ amount, swapDestAddress }) - if (swapResult instanceof SwapClientNotResponding) { - baseLogger.info("Swap Client is not running, skipping") - return - } - if (swapResult instanceof Error) { - if (swapResult instanceof SwapErrorChannelBalanceTooLow) { - expect(swapResult).toBeInstanceOf(SwapErrorChannelBalanceTooLow) - } else { - expect(swapResult).not.toBeInstanceOf(Error) - } - } else { - expect(swapResult).toEqual( - expect.objectContaining({ - swapId: expect.any(String), - }), - ) - } - } - } - }) - - it("Swap out without enough funds returns an error", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const btc = { amount: 5_000_000_000n, currency: WalletCurrency.Btc } - const swapResult = await swapService.swapOut({ amount: btc }) - if (swapResult instanceof SwapClientNotResponding) { - return - } - expect(swapResult).toBeInstanceOf(Error) - } - }) - - it("Swap out if on chain wallet is depleted returns a swap result", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - // thresholds - const { onChain } = await lndsBalances() - const loopOutWhenHotWalletLessThanConfig = { - amount: BigInt(onChain + 50000), - currency: WalletCurrency.Btc, - } - - // check if wallet is depleted - const swapOutChecker = SwapOutChecker({ - loopOutWhenHotWalletLessThanConfig, - swapOutAmount: amount, - }) - const amountToSwapOut = swapOutChecker.getSwapOutAmount({ - currentOnChainHotWalletBalance: ZERO_SATS, - currentOutboundLiquidityBalance: ZERO_SATS, - }) - if (amountToSwapOut.amount > 0) { - const swapResult = await swapService.swapOut({ amount: amountToSwapOut }) - if (swapResult instanceof SwapClientNotResponding) { - return - } - - expect(swapResult).not.toBeInstanceOf(Error) - expect(swapResult).toEqual( - expect.objectContaining({ - swapId: expect.any(String), - }), - ) - } else { - expect("No swap Needed").toEqual("No swap Needed") - } - } - }) - - it("Swap out quote return quote result", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const btc: BtcPaymentAmount = { amount: 250000n, currency: WalletCurrency.Btc } - const quoteResult = await swapService.swapOutQuote(btc) - if (quoteResult instanceof Error) throw quoteResult - expect(quoteResult).not.toBeInstanceOf(Error) - expect(quoteResult.swapFeeSat).toBeDefined() - } - }) - - it("Swap out terms return terms result", async () => { - const isSwapServerUp = await swapService.healthCheck() - if (isSwapServerUp) { - const termsResult = await swapService.swapOutTerms() - if (termsResult instanceof Error) throw termsResult - expect(termsResult).not.toBeInstanceOf(Error) - expect(termsResult.maxSwapAmount).toBeDefined() - } - }) -}) diff --git a/test/galoy/legacy-integration/app/swap/swap-record-ledger-fee.spec.ts b/test/galoy/legacy-integration/app/swap/swap-record-ledger-fee.spec.ts deleted file mode 100644 index 8134e2e2f..000000000 --- a/test/galoy/legacy-integration/app/swap/swap-record-ledger-fee.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DuplicateError } from "@domain/errors" -import { SwapState, SwapType } from "@domain/swap" -import { admin as LedgerAdmin } from "@services/ledger/admin" - -describe("Swap Record ledger Fee", () => { - let entryId: LedgerTransactionId[] = [] - const swapId = `test-swapid-${Date.now()}` as SwapId - const mockSwapData: SwapStatusResult = { - id: swapId, - amt: 5000000n, - htlcAddress: `test-htlc-addr-${Date.now()}` as OnChainAddress, - onchainMinerFee: 50n, - offchainRoutingFee: 50n, - serviceProviderFee: 50n, - message: "", - state: SwapState.Success, - swapType: SwapType.Swapout, - } - - it("Record fee to ledger", async () => { - const entry = await LedgerAdmin.addSwapFeeTxSend(mockSwapData) - if (entry instanceof Error) throw entry - expect(entry).not.toBeInstanceOf(Error) - entryId = (entry as LedgerJournal).transactionIds - expect(entryId[0]).toBeDefined() - }) - - it("Do not record duplicate fee to ledger", async () => { - const entry = await LedgerAdmin.addSwapFeeTxSend(mockSwapData) - expect(entry).toBeInstanceOf(DuplicateError) - }) -}) diff --git a/test/galoy/legacy-integration/app/trigger/trigger.fn.spec.ts b/test/galoy/legacy-integration/app/trigger/trigger.fn.spec.ts deleted file mode 100644 index 7263fc401..000000000 --- a/test/galoy/legacy-integration/app/trigger/trigger.fn.spec.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { getFeesConfig, ONCHAIN_MIN_CONFIRMATIONS } from "@config" - -import { Wallets } from "@app" - -import { TxStatus } from "@domain/wallets" -import { sat2btc, toSats } from "@domain/bitcoin" -import { paymentAmountFromNumber, WalletCurrency } from "@domain/shared" - -import { onchainBlockEventHandler } from "@servers/trigger" - -import { baseLogger } from "@services/logger" - -import { sleep } from "@utils" - -import { - amountAfterFeeDeduction, - bitcoindClient, - bitcoindOutside, - createMandatoryUsers, - lndCreateOnChainAddress, - createUserAndWalletFromPhone, - getDefaultWalletIdByPhone, - lnd1, - mineBlockAndSyncAll, - RANDOM_ADDRESS, - randomPhone, - subscribeToBlocks, - waitFor, - waitUntilSyncAll, -} from "test/galoy/helpers" -import { getBalanceHelper, getTransactionsForWalletId } from "test/galoy/helpers/wallet" - -let walletIdA: WalletId -let walletIdD: WalletId -let walletIdF: WalletId - -const phoneA = randomPhone() -const phoneD = randomPhone() -const phoneF = randomPhone() - -beforeAll(async () => { - await createMandatoryUsers() - - await bitcoindClient.loadWallet({ filename: "outside" }) - - await createUserAndWalletFromPhone(phoneA) - await createUserAndWalletFromPhone(phoneD) - await createUserAndWalletFromPhone(phoneF) - - walletIdA = await getDefaultWalletIdByPhone(phoneA) - walletIdD = await getDefaultWalletIdByPhone(phoneD) - walletIdF = await getDefaultWalletIdByPhone(phoneF) -}) - -beforeEach(() => { - jest.resetAllMocks() -}) - -afterAll(async () => { - jest.restoreAllMocks() - await bitcoindClient.unloadWallet({ walletName: "outside" }) -}) - -type WalletState = { - balance: CurrencyBaseAmount - transactions: WalletTransaction[] - onchainAddress: OnChainAddress -} - -const getWalletState = async (walletId: WalletId): Promise => { - const balance = await getBalanceHelper(walletId) - const { result, error } = await getTransactionsForWalletId(walletId) - if (error instanceof Error || !result?.slice) { - throw error - } - const onchainAddress = await Wallets.getLastOnChainAddress(walletId) - if (onchainAddress instanceof Error) { - throw onchainAddress - } - return { - balance, - transactions: result.slice, - onchainAddress, - } -} - -describe("onchainBlockEventHandler", () => { - it("should process block for incoming transactions from lnd", async () => { - const amount = toSats(10_000) - const amount2 = toSats(20_000) - const amountBria = toSats(21_000) - const blocksToMine = ONCHAIN_MIN_CONFIRMATIONS - const scanDepth = (ONCHAIN_MIN_CONFIRMATIONS + 1) as ScanDepth - - await mineBlockAndSyncAll() - const result = await Wallets.updateLegacyOnChainReceipt({ - scanDepth, - logger: baseLogger, - }) - if (result instanceof Error) throw result - - const initialBlock = await bitcoindClient.getBlockCount() - let isFinalBlock = false - let lastHeight = initialBlock - const subBlocks = subscribeToBlocks({ lnd: lnd1 }) - subBlocks.on("block", async ({ height }: { height: number }) => { - if (height > lastHeight) { - lastHeight = height - await onchainBlockEventHandler(height) - } - isFinalBlock = lastHeight >= initialBlock + blocksToMine - }) - - const address = await lndCreateOnChainAddress(walletIdA) - if (address instanceof Error) throw address - - const output0 = {} - output0[address] = sat2btc(amount) - - const address2 = await lndCreateOnChainAddress(walletIdD) - if (address2 instanceof Error) throw address2 - - const output1 = {} - output1[address2] = sat2btc(amount2) - - const addressBria = await Wallets.createOnChainAddress({ - walletId: walletIdF, - }) - if (addressBria instanceof Error) throw addressBria - - const outputBria = { [addressBria]: sat2btc(amountBria) } - - const outputs = [output0, output1, outputBria] - - const { psbt } = await bitcoindOutside.walletCreateFundedPsbt({ inputs: [], outputs }) - const walletProcessPsbt = await bitcoindOutside.walletProcessPsbt({ psbt }) - const finalizedPsbt = await bitcoindOutside.finalizePsbt({ - psbt: walletProcessPsbt.psbt, - }) - - const initWalletAState = await getWalletState(walletIdA) - const initWalletDState = await getWalletState(walletIdD) - const initWalletFState = await getWalletState(walletIdF) - await bitcoindOutside.sendRawTransaction({ hexstring: finalizedPsbt.hex }) - await bitcoindOutside.generateToAddress({ - nblocks: blocksToMine, - address: RANDOM_ADDRESS, - }) - - await Promise.all([waitFor(() => isFinalBlock), waitUntilSyncAll()]) - - // this sleep seems necessary on the CI server. otherwise all the events may not have propagated - // also some event are being trigger asynchronously without an awaitt, ie the notifications - await sleep(500) - - subBlocks.removeAllListeners() - - const validateWalletState = async ({ - walletId, - initialState, - amount, - address, - }: { - walletId: WalletId - initialState: WalletState - amount: Satoshis - address: string - }) => { - const btcAmount = paymentAmountFromNumber({ - amount, - currency: WalletCurrency.Btc, - }) - if (btcAmount instanceof Error) throw btcAmount - - const { balance, transactions, onchainAddress } = await getWalletState(walletId) - const feeConfig = getFeesConfig() - const minBankFee = feeConfig.depositDefaultMin - const minBankFeeThreshold = feeConfig.depositThreshold - const depositFeeRatio = feeConfig.depositRatioAsBasisPoints - const finalAmount = amountAfterFeeDeduction({ - amount: btcAmount, - minBankFee, - minBankFeeThreshold, - depositFeeRatio, - }) - const lastTransaction = transactions[0] - - expect(transactions.length).toBe(initialState.transactions.length + 1) - expect(lastTransaction.status).toBe(TxStatus.Success) - expect(lastTransaction.settlementFee).toBe( - Math.round(lastTransaction.settlementFee), - ) - expect(lastTransaction.settlementAmount).toBe(finalAmount) - expect((lastTransaction as WalletOnChainTransaction).initiationVia.address).toBe( - address, - ) - expect(balance).toBe(initialState.balance + finalAmount) - expect(onchainAddress).not.toBe(initialState.onchainAddress) - } - - await validateWalletState({ - walletId: walletIdA, - initialState: initWalletAState, - amount: amount, - address: address, - }) - await validateWalletState({ - walletId: walletIdD, - initialState: initWalletDState, - amount: amount2, - address: address2, - }) - - // Wallet with bria address should not be processed by onchainBlockEventHandler - const { balance, transactions, onchainAddress } = await getWalletState(walletIdF) - expect(balance).toBe(initWalletFState.balance) - expect(onchainAddress).toBe(initWalletFState.onchainAddress) - expect(transactions.length).toBe(initWalletFState.transactions.length) - }) -}) diff --git a/test/galoy/legacy-integration/app/users/update-language.spec.ts b/test/galoy/legacy-integration/app/users/update-language.spec.ts deleted file mode 100644 index 6be68c8e7..000000000 --- a/test/galoy/legacy-integration/app/users/update-language.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Users } from "@app" -import { InvalidLanguageError } from "@domain/errors" -import { UsersRepository } from "@services/mongoose" - -import { createUserAndWalletFromPhone, getUserIdByPhone, randomPhone } from "test/galoy/helpers" - -let userId: UserId -const phone = randomPhone() - -beforeAll(async () => { - await createUserAndWalletFromPhone(phone) - userId = await getUserIdByPhone(phone) -}) - -describe("Accounts - updateLanguage", () => { - it("updates successfully", async () => { - const result = await Users.updateLanguage({ - userId: userId, - language: "es", - }) - expect(result).not.toBeInstanceOf(Error) - expect(result).toEqual( - expect.objectContaining({ - id: userId, - language: "es", - }), - ) - - await Users.updateLanguage({ userId, language: "de" }) - - const user = await UsersRepository().findById(userId) - expect(user).not.toBeInstanceOf(Error) - expect(user).toEqual( - expect.objectContaining({ - id: userId, - language: "de", - }), - ) - }) - - it("fails with invalid language", async () => { - const result = await Users.updateLanguage({ - userId: userId, - language: "Klingon", - }) - expect(result).toBeInstanceOf(InvalidLanguageError) - }) -}) diff --git a/test/galoy/legacy-integration/app/users/user-role.spec.ts b/test/galoy/legacy-integration/app/users/user-role.spec.ts deleted file mode 100644 index 160028148..000000000 --- a/test/galoy/legacy-integration/app/users/user-role.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AccountsRepository } from "@services/mongoose" - -import { createUserAndWalletFromPhone, getUserIdByPhone } from "test/galoy/helpers" - -describe("Users - role", () => { - it("persists a role set in test accounts", async () => { - const phone = "+16505554336" as PhoneNumber - await createUserAndWalletFromPhone(phone) - const userId = await getUserIdByPhone(phone) - const account = await AccountsRepository().findByUserId(userId) - expect(account).not.toBeInstanceOf(Error) - if (account instanceof Error) throw account - expect(account.isEditor).toEqual(true) - }) -}) diff --git a/test/galoy/legacy-integration/app/users/user-wallets.spec.ts b/test/galoy/legacy-integration/app/users/user-wallets.spec.ts deleted file mode 100644 index 8db8f6170..000000000 --- a/test/galoy/legacy-integration/app/users/user-wallets.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { WalletCurrency } from "@domain/shared" -import { AccountsRepository, WalletsRepository } from "@services/mongoose" - -import { createAccount } from "test/galoy/helpers" - -describe("Users - wallets", () => { - describe("with 'createUser'", () => { - it("adds a USD wallet for new user if config is set to true", async () => { - const initialWallets = [WalletCurrency.Btc, WalletCurrency.Usd] - - let account: Account | RepositoryError = await createAccount({ - initialWallets, - }) - - // Check all wallets were created - const wallets = await WalletsRepository().listByAccountId(account.id) - if (wallets instanceof Error) throw wallets - const walletCurrencies = wallets.map((wallet) => wallet.currency) - expect(walletCurrencies).toHaveLength(2) - expect(walletCurrencies).toContain(WalletCurrency.Btc) - expect(walletCurrencies).toContain(WalletCurrency.Usd) - - // Check expected default wallet was set - account = await AccountsRepository().findById(wallets[0].accountId) - if (account instanceof Error) throw account - - const btcWallet = wallets.filter( - (wallet) => wallet.currency === WalletCurrency.Btc, - )[0] - if (btcWallet === undefined) { - throw new Error(`${WalletCurrency.Btc} wallet not found`) - } - expect(account.defaultWalletId).toEqual(btcWallet.id) - }) - - it("does not add a USD wallet for new user if config is set to false", async () => { - const initialWallets = [WalletCurrency.Btc] - - let account: Account | RepositoryError = await createAccount({ - initialWallets, - }) - - // Check all wallets were created - const wallets = await WalletsRepository().listByAccountId(account.id) - if (wallets instanceof Error) throw wallets - const walletCurrencies = wallets.map((wallet) => wallet.currency) - expect(walletCurrencies).toHaveLength(1) - expect(walletCurrencies).toContain(WalletCurrency.Btc) - expect(walletCurrencies).not.toContain(WalletCurrency.Usd) - - // Check expected default wallet was set - account = await AccountsRepository().findById(wallets[0].accountId) - if (account instanceof Error) throw account - - const btcWallet = wallets.filter( - (wallet) => wallet.currency === WalletCurrency.Btc, - )[0] - if (btcWallet === undefined) { - throw new Error(`${WalletCurrency.Btc} wallet not found`) - } - expect(account.defaultWalletId).toEqual(btcWallet.id) - }) - - it("sets USD wallet as default if BTC wallet does not exist", async () => { - const initialWallets = [WalletCurrency.Usd] - - let account: Account | RepositoryError = await createAccount({ - initialWallets, - }) - - // Check all wallets were created - const wallets = await WalletsRepository().listByAccountId(account.id) - if (wallets instanceof Error) throw wallets - const walletCurrencies = wallets.map((wallet) => wallet.currency) - expect(walletCurrencies).toHaveLength(1) - expect(walletCurrencies).not.toContain(WalletCurrency.Btc) - expect(walletCurrencies).toContain(WalletCurrency.Usd) - - // Check expected default wallet was set - account = await AccountsRepository().findById(wallets[0].accountId) - if (account instanceof Error) throw account - - const usdWallet = wallets.filter( - (wallet) => wallet.currency === WalletCurrency.Usd, - )[0] - if (usdWallet === undefined) { - throw new Error(`${WalletCurrency.Usd} wallet not found`) - } - expect(account.defaultWalletId).toEqual(usdWallet.id) - }) - }) -}) diff --git a/test/galoy/legacy-integration/app/wallets/invoice.spec.ts b/test/galoy/legacy-integration/app/wallets/invoice.spec.ts deleted file mode 100644 index e5e568db7..000000000 --- a/test/galoy/legacy-integration/app/wallets/invoice.spec.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { Wallets } from "@app" -import { addWallet } from "@app/accounts/add-wallet" -import { - getInvoiceCreateAttemptLimits, - getInvoiceCreateForRecipientAttemptLimits, -} from "@config" -import { decodeInvoice } from "@domain/bitcoin/lightning" -import { toCents } from "@domain/fiat" -import { - InvoiceCreateForRecipientRateLimiterExceededError, - InvoiceCreateRateLimiterExceededError, -} from "@domain/rate-limit/errors" -import { WalletCurrency } from "@domain/shared" -import { WalletType } from "@domain/wallets" -import { DealerPriceService } from "@services/dealer-price" -import { WalletInvoicesRepository } from "@services/mongoose" - -import { - createUserAndWalletFromPhone, - getAccountIdByPhone, - getDefaultWalletIdByPhone, - getHash, - randomPhone, -} from "test/galoy/helpers" -import { - resetRecipientAccountIdLimits, - resetSelfAccountIdLimits, -} from "test/galoy/helpers/rate-limit" - -let walletIdBtc: WalletId -let walletIdUsd: WalletId -let accountIdB: AccountId - -const walletInvoices = WalletInvoicesRepository() - -const dealerFns = DealerPriceService() - -beforeAll(async () => { - const phoneB = randomPhone() - await createUserAndWalletFromPhone(phoneB) - - walletIdBtc = await getDefaultWalletIdByPhone(phoneB) - accountIdB = await getAccountIdByPhone(phoneB) - - const wallet = await addWallet({ - accountId: accountIdB, - type: WalletType.Checking, - currency: WalletCurrency.Usd, - }) - if (wallet instanceof Error) return wallet - walletIdUsd = wallet.id -}) - -afterAll(() => { - jest.restoreAllMocks() -}) - -describe("Wallet - addInvoice BTC", () => { - it("add a self generated invoice", async () => { - const amountInput = 1000 - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdBtc, - amount: amountInput, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - expect(request.startsWith("lnbcrt10")).toBeTruthy() - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - - const { - recipientWalletDescriptor: { id: walletId, currency }, - } = result - - expect(walletId).toBe(walletIdBtc) - expect(currency).toBe(WalletCurrency.Btc) - - const decodedInvoice = decodeInvoice(request) - if (decodedInvoice instanceof Error) throw decodedInvoice - - const { amount } = decodedInvoice - expect(amount).toBe(amountInput) - }) - - it("add a self generated invoice without amount", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: walletIdBtc, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - - const { - recipientWalletDescriptor: { id: walletId, currency }, - } = result - - expect(walletId).toBe(walletIdBtc) - expect(currency).toBe(WalletCurrency.Btc) - }) - - it("adds a public with amount invoice", async () => { - const amountInput = 10 - - const lnInvoice = await Wallets.addInvoiceForRecipientForBtcWallet({ - recipientWalletId: walletIdBtc, - amount: amountInput, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - expect(request.startsWith("lnbcrt1")).toBeTruthy() - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - - const { - recipientWalletDescriptor: { id: walletId }, - selfGenerated, - } = result - - expect(String(walletId)).toBe(String(walletIdBtc)) - expect(selfGenerated).toBe(false) - - const decodedInvoice = decodeInvoice(request) - if (decodedInvoice instanceof Error) throw decodedInvoice - - const { amount } = decodedInvoice - expect(amount).toBe(amountInput) - }) - - it("adds a public no amount invoice", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: walletIdBtc, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - expect(request.startsWith("lnbcrt1")).toBeTruthy() - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - const { - recipientWalletDescriptor: { id: walletId }, - selfGenerated, - } = result - expect(String(walletId)).toBe(String(walletIdBtc)) - expect(selfGenerated).toBe(false) - }) -}) - -describe("Wallet - addInvoice USD", () => { - it("add a self generated USD invoice", async () => { - const centsInput = 10000 - - const btcAmount = await dealerFns.getSatsFromCentsForFutureBuy({ - amount: BigInt(centsInput), - currency: WalletCurrency.Usd, - }) - if (btcAmount instanceof Error) throw btcAmount - const sats = Number(btcAmount.amount) - - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: walletIdUsd, - amount: toCents(centsInput), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - expect(request.startsWith("lnbcrt")).toBeTruthy() - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - const { - recipientWalletDescriptor: { id: walletId, currency }, - usdAmount, - } = result - - expect(String(walletId)).toBe(String(walletIdUsd)) - expect(usdAmount?.amount).toBe(BigInt(centsInput)) - expect(currency).toBe(WalletCurrency.Usd) - - const decodedInvoice = decodeInvoice(request) - if (decodedInvoice instanceof Error) throw decodedInvoice - - const { amount } = decodedInvoice - expect(amount).toBe(sats) - }) - - it("add a self generated invoice without amount", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: walletIdUsd, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - const { - recipientWalletDescriptor: { id: walletId }, - usdAmount, - } = result - expect(String(walletId)).toBe(String(walletIdUsd)) - expect(usdAmount?.amount).toBe(undefined) - }) - - it("adds a public with amount invoice", async () => { - const centsInput = 10000 - - const btcAmount = await dealerFns.getSatsFromCentsForFutureBuy({ - amount: BigInt(centsInput), - currency: WalletCurrency.Usd, - }) - if (btcAmount instanceof Error) throw btcAmount - const sats = Number(btcAmount.amount) - - const lnInvoice = await Wallets.addInvoiceForRecipientForUsdWallet({ - recipientWalletId: walletIdUsd, - amount: toCents(centsInput), - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - expect(request.startsWith("lnbcrt")).toBeTruthy() - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - const { - recipientWalletDescriptor: { id: walletId }, - usdAmount, - selfGenerated, - } = result - expect(usdAmount?.amount).toBe(BigInt(centsInput)) - - expect(String(walletId)).toBe(String(walletIdUsd)) - expect(selfGenerated).toBe(false) - - const decodedInvoice = decodeInvoice(request) - if (decodedInvoice instanceof Error) throw decodedInvoice - - const { amount } = decodedInvoice - expect(amount).toBe(sats) - }) - - it("adds a public invoice", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: walletIdUsd, - }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice - expect(request.startsWith("lnbcrt1")).toBeTruthy() - - const result = await walletInvoices.findByPaymentHash(getHash(request)) - if (result instanceof Error) throw result - const { - recipientWalletDescriptor: { id: walletId }, - usdAmount, - selfGenerated, - } = result - expect(String(walletId)).toBe(String(walletIdUsd)) - expect(selfGenerated).toBe(false) - expect(usdAmount?.amount).toBe(undefined) - }) -}) - -describe("Wallet - rate limiting test", () => { - it("fails to add invoice past rate limit", async () => { - // Reset limits before starting - const resetOk = await resetSelfAccountIdLimits(accountIdB) - expect(resetOk).not.toBeInstanceOf(Error) - if (resetOk instanceof Error) throw resetOk - - // Create max number of invoices - const limitsNum = getInvoiceCreateAttemptLimits().points - - const promises: Promise[] = [] - for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceForSelfForBtcWallet({ - walletId: walletIdBtc, - amount: 1000, - }) - promises.push(lnInvoicePromise) - } - const lnInvoices = await Promise.all(promises) - const isNotError = (item) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) - - return testPastSelfInvoiceLimits({ walletId: walletIdBtc, accountId: accountIdB }) - }) - - it("fails to add no amount invoice past rate limit", async () => { - // Reset limits before starting - const resetOk = await resetSelfAccountIdLimits(accountIdB) - expect(resetOk).not.toBeInstanceOf(Error) - if (resetOk instanceof Error) throw resetOk - - // Create max number of invoices - const limitsNum = getInvoiceCreateAttemptLimits().points - const promises: Promise[] = [] - for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceNoAmountForSelf({ - walletId: walletIdBtc, - }) - promises.push(lnInvoicePromise) - } - const lnInvoices = await Promise.all(promises) - const isNotError = (item) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) - - return testPastSelfInvoiceLimits({ walletId: walletIdBtc, accountId: accountIdB }) - }) - - it("fails to add public invoice past rate limit", async () => { - // Reset limits before starting - const resetOk = await resetRecipientAccountIdLimits(accountIdB) - expect(resetOk).not.toBeInstanceOf(Error) - if (resetOk instanceof Error) throw resetOk - - // Create max number of invoices - const limitsNum = getInvoiceCreateForRecipientAttemptLimits().points - const promises: Promise[] = [] - for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceForRecipientForBtcWallet({ - recipientWalletId: walletIdBtc, - amount: 1000, - }) - promises.push(lnInvoicePromise) - } - const lnInvoices = await Promise.all(promises) - const isNotError = (item) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) - - return testPastRecipientInvoiceLimits({ - walletId: walletIdBtc, - accountId: accountIdB, - }) - }) - - it("fails to add no amount public invoice past rate limit", async () => { - // Reset limits before starting - const resetOk = await resetRecipientAccountIdLimits(accountIdB) - expect(resetOk).not.toBeInstanceOf(Error) - if (resetOk instanceof Error) throw resetOk - - // Create max number of invoices - const limitsNum = getInvoiceCreateForRecipientAttemptLimits().points - const promises: Promise[] = [] - for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: walletIdBtc, - }) - promises.push(lnInvoicePromise) - } - const lnInvoices = await Promise.all(promises) - const isNotError = (item) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) - - return testPastRecipientInvoiceLimits({ - walletId: walletIdBtc, - accountId: accountIdB, - }) - }) -}) - -const testPastSelfInvoiceLimits = async ({ - walletId, - accountId, -}: { - walletId: WalletId - accountId: AccountId -}) => { - // Test that first invoice past the limit fails - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId, - amount: 1000, - }) - expect(lnInvoice).toBeInstanceOf(InvoiceCreateRateLimiterExceededError) - - const lnNoAmountInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId, - }) - expect(lnNoAmountInvoice).toBeInstanceOf(InvoiceCreateRateLimiterExceededError) - - // Test that recipient invoices still work - const lnRecipientInvoice = await Wallets.addInvoiceForRecipientForBtcWallet({ - recipientWalletId: walletId, - amount: 1000, - }) - expect(lnRecipientInvoice).not.toBeInstanceOf(Error) - expect(lnRecipientInvoice).toHaveProperty("paymentRequest") - - const lnNoAmountRecipientInvoice = await Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: walletId, - }) - expect(lnNoAmountRecipientInvoice).not.toBeInstanceOf(Error) - expect(lnNoAmountRecipientInvoice).toHaveProperty("paymentRequest") - - // Reset limits when done for other tests - let resetOk = await resetSelfAccountIdLimits(accountId) - expect(resetOk).not.toBeInstanceOf(Error) - resetOk = await resetRecipientAccountIdLimits(accountId) - expect(resetOk).not.toBeInstanceOf(Error) -} - -const testPastRecipientInvoiceLimits = async ({ - walletId, - accountId, -}: { - walletId: WalletId - accountId: AccountId -}) => { - // Test that first invoice past the limit fails - const lnRecipientInvoice = await Wallets.addInvoiceForRecipientForBtcWallet({ - recipientWalletId: walletId, - amount: 1000, - }) - expect(lnRecipientInvoice).toBeInstanceOf( - InvoiceCreateForRecipientRateLimiterExceededError, - ) - - const lnNoAmountRecipientInvoice = await Wallets.addInvoiceNoAmountForRecipient({ - recipientWalletId: walletId, - }) - expect(lnNoAmountRecipientInvoice).toBeInstanceOf( - InvoiceCreateForRecipientRateLimiterExceededError, - ) - - // Test that recipient invoices still work - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId, - amount: 1000, - }) - expect(lnInvoice).not.toBeInstanceOf(Error) - expect(lnInvoice).toHaveProperty("paymentRequest") - - const lnNoAmountInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId, - }) - expect(lnNoAmountInvoice).not.toBeInstanceOf(Error) - expect(lnNoAmountInvoice).toHaveProperty("paymentRequest") - - // Reset limits when done for other tests - let resetOk = await resetSelfAccountIdLimits(accountId) - expect(resetOk).not.toBeInstanceOf(Error) - resetOk = await resetRecipientAccountIdLimits(accountId) - expect(resetOk).not.toBeInstanceOf(Error) -} diff --git a/test/galoy/legacy-integration/jest-test-sequencer.js b/test/galoy/legacy-integration/jest-test-sequencer.js deleted file mode 100644 index 7a8b745a5..000000000 --- a/test/galoy/legacy-integration/jest-test-sequencer.js +++ /dev/null @@ -1,12 +0,0 @@ -const Sequencer = require("@jest/test-sequencer").default - -class CustomSequencer extends Sequencer { - sort(tests) { - // Test structure information - // https://github.com/facebook/jest/blob/6b8b1404a1d9254e7d5d90a8934087a9c9899dab/packages/jest-runner/src/types.ts#L17-L21 - const copyTests = Array.from(tests) - return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)) - } -} - -module.exports = CustomSequencer diff --git a/test/galoy/legacy-integration/jest.config.js b/test/galoy/legacy-integration/jest.config.js deleted file mode 100644 index fb5eb3e2e..000000000 --- a/test/galoy/legacy-integration/jest.config.js +++ /dev/null @@ -1,27 +0,0 @@ -const swcConfig = require("../swc-config.json") - -module.exports = { - moduleFileExtensions: ["js", "json", "ts", "cjs", "mjs"], - rootDir: "../../", - roots: ["/test/legacy-integration"], - transform: { - "^.+\\.(t|j)sx?$": ["@swc/jest", swcConfig], - }, - testRegex: ".*\\.spec\\.ts$", - testSequencer: "/test/legacy-integration/jest-test-sequencer.js", - setupFilesAfterEnv: ["/test/legacy-integration/jest.setup.js"], - testEnvironment: "node", - moduleNameMapper: { - "^@config$": ["src/config/index"], - "^@app$": ["src/app/index"], - "^@utils$": ["src/utils/index"], - - "^@core/(.*)$": ["src/core/$1"], - "^@app/(.*)$": ["src/app/$1"], - "^@domain/(.*)$": ["src/domain/$1"], - "^@services/(.*)$": ["src/services/$1"], - "^@servers/(.*)$": ["src/servers/$1"], - "^@graphql/(.*)$": ["src/graphql/$1"], - "^test/(.*)$": ["test/$1"], - }, -} diff --git a/test/galoy/legacy-integration/jest.setup.js b/test/galoy/legacy-integration/jest.setup.js deleted file mode 100644 index 5203f1049..000000000 --- a/test/galoy/legacy-integration/jest.setup.js +++ /dev/null @@ -1,29 +0,0 @@ -const { disconnectAll } = require("@services/redis") -const { setupMongoConnection } = require("@services/mongodb") - -jest.mock("@services/lnd/auth", () => { - const module = jest.requireActual("@services/lnd/auth") - const lndsConnect = module.lndsConnect.map((p) => ({ ...p, active: true })) - return { ...module, lndsConnect } -}) - -jest.mock("@app/prices/get-current-price", () => require("test/mocks/get-current-price")) -jest.mock("@services/twilio", () => require("test/mocks/twilio")) -jest.mock("@services/price", () => require("test/mocks/price")) - - -let mongoose - -beforeAll(async () => { - mongoose = await setupMongoConnection(true) -}) - -afterAll(async () => { - // avoids to use --forceExit - disconnectAll() - if (mongoose) { - await mongoose.connection.close() - } -}) - -jest.setTimeout(process.env.JEST_TIMEOUT || 30000) diff --git a/test/galoy/legacy-integration/notifications/notification.spec.ts b/test/galoy/legacy-integration/notifications/notification.spec.ts deleted file mode 100644 index d5df3ccae..000000000 --- a/test/galoy/legacy-integration/notifications/notification.spec.ts +++ /dev/null @@ -1,429 +0,0 @@ -import { getRecentlyActiveAccounts } from "@app/accounts/active-accounts" -import { sendDefaultWalletBalanceToAccounts } from "@app/accounts/send-default-wallet-balance-to-users" - -import { toSats } from "@domain/bitcoin" -import { UsdDisplayCurrency } from "@domain/fiat" -import { LedgerService } from "@services/ledger" -import * as serviceLedger from "@services/ledger" -import { - WalletsRepository, - UsersRepository, - AccountsRepository, -} from "@services/mongoose" -import { createPushNotificationContent } from "@services/notifications/create-push-notification-content" -import * as PushNotificationsServiceImpl from "@services/notifications/push-notifications" -import { NotificationsService } from "@services/notifications" -import { - getCurrentPriceAsWalletPriceRatio, - getCurrentPriceAsDisplayPriceRatio, -} from "@app/prices" -import { WalletCurrency } from "@domain/shared" -import { GaloyNotificationCategories } from "@domain/notifications" - -let spy -let displayPriceRatios: Record> - -const accountId = "accountId" as AccountId -const walletId = "walletId" as WalletId -const paymentHash = "paymentHash" as PaymentHash -const txHash = "txHash" as OnChainTxHash -const deviceTokens = ["token" as DeviceToken] -const language = "" as UserLanguageOrEmpty -const paymentAmount = { - amount: 1000n, - currency: WalletCurrency.Btc, -} -const usdPaymentAmount = { - amount: 5n, - currency: WalletCurrency.Usd, -} - -const crcDisplayPaymentAmount = { - amountInMinor: 350050n, - currency: "CRC" as DisplayCurrency, - displayInMajor: "3500.50", -} - -const unfilteredNotificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: [], - }, -} - -beforeAll(async () => { - const usdDisplayPriceRatio = await getCurrentPriceAsDisplayPriceRatio({ - currency: UsdDisplayCurrency, - }) - if (usdDisplayPriceRatio instanceof Error) throw usdDisplayPriceRatio - - const eurDisplayPriceRatio = await getCurrentPriceAsDisplayPriceRatio({ - currency: "EUR" as DisplayCurrency, - }) - if (eurDisplayPriceRatio instanceof Error) throw eurDisplayPriceRatio - - const crcDisplayPriceRatio = await getCurrentPriceAsDisplayPriceRatio({ - currency: "CRC" as DisplayCurrency, - }) - if (crcDisplayPriceRatio instanceof Error) throw crcDisplayPriceRatio - - displayPriceRatios = { - USD: usdDisplayPriceRatio, - EUR: eurDisplayPriceRatio, - CRC: crcDisplayPriceRatio, - } - - const ledgerService = serviceLedger.LedgerService() - - spy = jest.spyOn(serviceLedger, "LedgerService").mockImplementation(() => ({ - ...ledgerService, - allTxBaseVolumeSince: async () => ({ - outgoingBaseAmount: toSats(10_000), - incomingBaseAmount: toSats(10_000), - }), - })) -}) - -afterAll(async () => { - spy.mockClear() - // jest.restoreAllMocks() -}) - -async function toArray(gen: AsyncIterable): Promise { - const out: T[] = [] - for await (const x of gen) { - out.push(x) - } - return out -} - -describe("notification", () => { - describe("sendFilteredNotification", () => { - // FIXME - // 1/ we don't use this code in production any more - // 2/ this is a very convoluted test that relies on other tests as an artefact. - // It's hard to debug. it's probably something we'll want to refactor with more cleaner/independant integration tests. - it.skip("sends daily balance to active users", async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementation(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await sendDefaultWalletBalanceToAccounts() - const activeAccounts = getRecentlyActiveAccounts() - if (activeAccounts instanceof Error) throw activeAccounts - - const activeAccountsArray = await toArray(activeAccounts) - - expect(activeAccountsArray.length).toBeGreaterThan(0) - expect(sendFilteredNotification.mock.calls.length).toBeGreaterThan(0) - - let usersWithDeviceTokens = 0 - for (const { kratosUserId } of activeAccountsArray) { - const user = await UsersRepository().findById(kratosUserId) - if (user instanceof Error) throw user - - if (user.deviceTokens.length > 0) usersWithDeviceTokens++ - } - - expect(sendFilteredNotification.mock.calls.length).toBe(usersWithDeviceTokens) - - for (let i = 0; i < sendFilteredNotification.mock.calls.length; i++) { - const [call] = sendFilteredNotification.mock.calls[i] - const { defaultWalletId, kratosUserId } = activeAccountsArray[i] - - const user = await UsersRepository().findById(kratosUserId) - if (user instanceof Error) throw user - - const wallet = await WalletsRepository().findById(defaultWalletId) - if (wallet instanceof Error) { - continue - // FIXME: need to improve the tests: - // on some tests, we just create account and user, no wallet - // - // need to make integration tests independent the one to the others - } - - const account = await AccountsRepository().findById(wallet.accountId) - if (account instanceof Error) throw account - const { displayCurrency } = account - const displayPriceRatio = displayPriceRatios[displayCurrency] - - const balance = await LedgerService().getWalletBalance(defaultWalletId) - if (balance instanceof Error) throw balance - const balanceAmount = { amount: BigInt(balance), currency: wallet.currency } - - let displayPaymentAmount: DisplayAmount | undefined = undefined - if (balanceAmount.currency === WalletCurrency.Btc) { - const displayAmount = displayPriceRatio.convertFromWallet( - balanceAmount as BtcPaymentAmount, - ) - - displayPaymentAmount = displayAmount - } else { - const walletPriceRatio = await getCurrentPriceAsWalletPriceRatio({ - currency: UsdDisplayCurrency, - }) - if (walletPriceRatio instanceof Error) throw walletPriceRatio - const btcBalanceAmount = walletPriceRatio.convertFromUsd( - balanceAmount as UsdPaymentAmount, - ) - - const displayAmount = displayPriceRatio.convertFromWallet(btcBalanceAmount) - if (displayAmount instanceof Error) throw displayAmount - - displayPaymentAmount = displayAmount - } - - const { title, body } = createPushNotificationContent({ - type: "balance", - userLanguage: user.language, - amount: balanceAmount, - displayAmount: displayPaymentAmount, - }) - - expect(call.title).toBe(title) - expect(call.body).toBe(body) - } - }) - - describe("lightningTxReceived", () => { - const tests = [ - { - name: "btc", - paymentAmount, - title: "BTC Transaction", - body: "+₡3,500.50 | 1,000 sats", - }, - { - name: "usd", - paymentAmount: usdPaymentAmount, - title: "USD Transaction", - body: "+₡3,500.50 | $0.05", - }, - ] - tests.forEach(({ name, paymentAmount, title, body }) => - it(`${name}`, async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await NotificationsService().lightningTxReceived({ - paymentAmount, - - recipientAccountId: accountId, - recipientWalletId: walletId, - displayPaymentAmount: crcDisplayPaymentAmount, - paymentHash, - recipientDeviceTokens: deviceTokens, - recipientNotificationSettings: unfilteredNotificationSettings, - recipientLanguage: language, - }) - - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBe(title) - expect(sendFilteredNotification.mock.calls[0][0].body).toBe(body) - expect(sendFilteredNotification.mock.calls[0][0].notificationCategory).toBe( - GaloyNotificationCategories.Payments, - ) - }), - ) - }) - - describe("intraLedgerTxReceived", () => { - const tests = [ - { - name: "btc", - paymentAmount, - title: "BTC Transaction", - body: "+₡3,500.50 | 1,000 sats", - }, - { - name: "usd", - paymentAmount: usdPaymentAmount, - title: "USD Transaction", - body: "+₡3,500.50 | $0.05", - }, - ] - - tests.forEach(({ name, paymentAmount, title, body }) => - it(`${name}`, async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await NotificationsService().intraLedgerTxReceived({ - paymentAmount, - - recipientAccountId: accountId, - recipientWalletId: walletId, - displayPaymentAmount: crcDisplayPaymentAmount, - recipientDeviceTokens: deviceTokens, - recipientNotificationSettings: unfilteredNotificationSettings, - recipientLanguage: language, - }) - - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBe(title) - expect(sendFilteredNotification.mock.calls[0][0].body).toBe(body) - expect(sendFilteredNotification.mock.calls[0][0].notificationCategory).toBe( - GaloyNotificationCategories.Payments, - ) - }), - ) - }) - - describe("onChainTxReceived", () => { - const tests = [ - { - name: "btc", - paymentAmount, - title: "BTC Transaction", - body: "+₡3,500.50 | 1,000 sats", - }, - { - name: "usd", - paymentAmount: usdPaymentAmount, - title: "USD Transaction", - body: "+₡3,500.50 | $0.05", - }, - ] - - tests.forEach(({ name, paymentAmount, title, body }) => - it(`${name}`, async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await NotificationsService().onChainTxReceived({ - paymentAmount, - - recipientAccountId: accountId, - recipientWalletId: walletId, - displayPaymentAmount: crcDisplayPaymentAmount, - txHash, - recipientDeviceTokens: deviceTokens, - recipientNotificationSettings: unfilteredNotificationSettings, - recipientLanguage: language, - }) - - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBe(title) - expect(sendFilteredNotification.mock.calls[0][0].body).toBe(body) - expect(sendFilteredNotification.mock.calls[0][0].notificationCategory).toBe( - GaloyNotificationCategories.Payments, - ) - }), - ) - }) - - describe("onChainTxReceivedPending", () => { - const tests = [ - { - name: "btc", - paymentAmount, - title: "BTC Transaction | Pending", - body: "pending +₡3,500.50 | 1,000 sats", - }, - { - name: "usd", - paymentAmount: usdPaymentAmount, - title: "USD Transaction | Pending", - body: "pending +₡3,500.50 | $0.05", - }, - ] - - tests.forEach(({ name, paymentAmount, title, body }) => - it(`${name}`, async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await NotificationsService().onChainTxReceivedPending({ - recipientAccountId: accountId, - recipientWalletId: walletId, - paymentAmount, - txHash, - displayPaymentAmount: crcDisplayPaymentAmount, - recipientDeviceTokens: deviceTokens, - recipientNotificationSettings: unfilteredNotificationSettings, - recipientLanguage: language, - }) - - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBe(title) - expect(sendFilteredNotification.mock.calls[0][0].body).toBe(body) - expect(sendFilteredNotification.mock.calls[0][0].notificationCategory).toBe( - GaloyNotificationCategories.Payments, - ) - }), - ) - }) - - describe("onChainTxSent", () => { - const tests = [ - { - name: "btc", - paymentAmount, - title: "BTC Transaction", - body: "Sent onchain payment of +₡3,500.50 | 1,000 sats confirmed", - }, - { - name: "usd", - paymentAmount: usdPaymentAmount, - title: "USD Transaction", - body: "Sent onchain payment of +₡3,500.50 | $0.05 confirmed", - }, - ] - - tests.forEach(({ name, paymentAmount, title, body }) => - it(`${name}`, async () => { - const sendFilteredNotification = jest.fn() - jest - .spyOn(PushNotificationsServiceImpl, "PushNotificationsService") - .mockImplementationOnce(() => ({ - sendFilteredNotification, - sendNotification: jest.fn(), - })) - - await NotificationsService().onChainTxSent({ - senderAccountId: accountId, - senderWalletId: walletId, - paymentAmount, - txHash, - displayPaymentAmount: crcDisplayPaymentAmount, - senderDeviceTokens: deviceTokens, - senderNotificationSettings: unfilteredNotificationSettings, - senderLanguage: language, - }) - - expect(sendFilteredNotification.mock.calls.length).toBe(1) - expect(sendFilteredNotification.mock.calls[0][0].title).toBe(title) - expect(sendFilteredNotification.mock.calls[0][0].body).toBe(body) - expect(sendFilteredNotification.mock.calls[0][0].notificationCategory).toBe( - GaloyNotificationCategories.Payments, - ) - }), - ) - }) - }) -}) diff --git a/test/galoy/legacy-integration/notifications/push-notification.spec.ts b/test/galoy/legacy-integration/notifications/push-notification.spec.ts deleted file mode 100644 index bcc71a2e1..000000000 --- a/test/galoy/legacy-integration/notifications/push-notification.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - PushNotificationsService, - SendFilteredPushNotificationStatus, -} from "@services/notifications/push-notifications" - -describe("push notification", () => { - it("should filter a notification", async () => { - const notificationCategory = "transaction" as NotificationCategory - const notificationSettings = { - push: { - enabled: true, - disabledCategories: [notificationCategory], - }, - } - - const result = await PushNotificationsService().sendFilteredNotification({ - body: "body", - title: "title", - deviceTokens: ["deviceToken" as DeviceToken], - notificationCategory, - notificationSettings, - }) - - expect(result).toEqual({ - status: SendFilteredPushNotificationStatus.Filtered, - }) - }) -}) diff --git a/test/galoy/legacy-integration/services/cache/index.spec.ts b/test/galoy/legacy-integration/services/cache/index.spec.ts deleted file mode 100644 index 47aa31ee9..000000000 --- a/test/galoy/legacy-integration/services/cache/index.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { createHash, randomBytes } from "crypto" - -import { SECS_PER_10_MINS } from "@config" - -import { toSats } from "@domain/bitcoin" -import { uniqueAddressesForTxn } from "@domain/bitcoin/onchain" - -import { RedisCacheService } from "@services/cache" - -const randomString = (length) => { - const sha256 = (buffer: Buffer) => createHash("sha256").update(buffer).digest("hex") - return sha256(randomBytes(32)).slice(0, length) -} - -const redis = RedisCacheService() - -const nonCacheCounts = { - getOrSet: 0, -} - -const incomingTxns: IncomingOnChainTransaction[] = [ - { - confirmations: 0, - rawTx: { - txHash: "txHash1" as OnChainTxHash, - outs: [ - { - address: "walletId0-address1" as OnChainAddress, - sats: toSats(100), - vout: 0 as OnChainTxVout, - }, - { - sats: toSats(200), - address: "change-address1" as OnChainAddress, - vout: 1 as OnChainTxVout, - }, - ], - }, - fee: toSats(0), - createdAt: new Date(), - uniqueAddresses: () => [] as OnChainAddress[], - }, -] - -const getOnChainTxs = async (key): Promise => - redis.getOrSet({ - key, - ttlSecs: SECS_PER_10_MINS, - getForCaching: async (): Promise => { - nonCacheCounts.getOrSet++ - return incomingTxns - }, - inflate: async (txnsPromise: Promise) => - (await txnsPromise).map(inflateIncomingOnChainTxFromCache), - }) - -const inflateIncomingOnChainTxFromCache = ( - txn: IncomingOnChainTransactionFromCache | IncomingOnChainTransaction, -): IncomingOnChainTransaction => ({ - ...txn, - createdAt: new Date(txn.createdAt), - uniqueAddresses: () => uniqueAddressesForTxn(txn.rawTx), -}) - -describe("Redis Cache", () => { - it("getOrSet", async () => { - const key = `bitcoin:${randomString(8)}` - - expect(nonCacheCounts.getOrSet).toEqual(0) - - const txns1 = await getOnChainTxs(key) - expect(nonCacheCounts.getOrSet).toEqual(1) - expect(txns1).toStrictEqual(incomingTxns) - - const txns2 = await getOnChainTxs(key) - expect(nonCacheCounts.getOrSet).toEqual(1) - // Non-stringify 'txns2' returns "Received: serializes to the same string" - expect(JSON.stringify(txns2)).toStrictEqual(JSON.stringify(incomingTxns)) - - const newKey = `bitcoin:${randomString(8)}` - const txns3 = await getOnChainTxs(newKey) - expect(nonCacheCounts.getOrSet).toEqual(2) - expect(txns3).toStrictEqual(incomingTxns) - - const txns4 = await getOnChainTxs(key) - expect(nonCacheCounts.getOrSet).toEqual(2) - // Non-stringify 'txns4' returns "Received: serializes to the same string" - expect(JSON.stringify(txns4)).toStrictEqual(JSON.stringify(incomingTxns)) - }) -}) diff --git a/test/galoy/legacy-integration/services/dealer/hedge-price.spec.ts b/test/galoy/legacy-integration/services/dealer/hedge-price.spec.ts deleted file mode 100644 index 1a423d151..000000000 --- a/test/galoy/legacy-integration/services/dealer/hedge-price.spec.ts +++ /dev/null @@ -1,1901 +0,0 @@ -import { Payments, Wallets } from "@app" -import { getMidPriceRatio } from "@app/prices" -import { getDealerConfig } from "@config" - -import { toSats } from "@domain/bitcoin" -import { toCents } from "@domain/fiat" -import { AmountCalculator, paymentAmountFromNumber, WalletCurrency } from "@domain/shared" -import { baseLogger } from "@services/logger" -import { AccountsRepository } from "@services/mongoose" - -import { createAccount, createAndFundNewWallet, getBalanceHelper } from "test/galoy/helpers" - -class ZeroAmountForUsdRecipientError extends Error {} - -jest.mock("@config", () => { - const config = jest.requireActual("@config") - return { - ...config, - getInvoiceCreateAttemptLimits: jest.fn().mockReturnValue({ - ...config.getInvoiceCreateAttemptLimits(), - points: 1000, - }), - } -}) - -const calc = AmountCalculator() -const usdHedgeEnabled = getDealerConfig().usd.hedgingEnabled - -const USD_STARTING_BALANCE = 100 as UsdCents - -const ONE_CENT = { amount: 1n, currency: WalletCurrency.Usd } as UsdPaymentAmount -const ONE_SAT = { amount: 1n, currency: WalletCurrency.Btc } as BtcPaymentAmount - -type AccountAndWallets = { - newBtcWallet: Wallet - newUsdWallet: Wallet - newAccount: Account -} - -const btcAmountFromUsdNumber = async ( - centsAmount: number | bigint, -): Promise => { - const usdPaymentAmount = paymentAmountFromNumber({ - amount: Number(centsAmount), - currency: WalletCurrency.Usd, - }) - if (usdPaymentAmount instanceof Error) throw usdPaymentAmount - - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - return midPriceRatio.convertFromUsd(usdPaymentAmount) -} - -const usdFundingAmount = paymentAmountFromNumber({ - amount: USD_STARTING_BALANCE, - currency: WalletCurrency.Usd, -}) -if (usdFundingAmount instanceof Error) throw usdFundingAmount - -const newAccountAndWallets = async () => { - const initialWallets = [WalletCurrency.Btc] - const account: Account | RepositoryError = await createAccount({ - initialWallets, - }) - - const accountId = account.id - - const newBtcWallet = await createAndFundNewWallet({ - accountId, - balanceAmount: await btcAmountFromUsdNumber(usdFundingAmount.amount), - }) - - const newUsdWallet = await createAndFundNewWallet({ - accountId, - balanceAmount: usdFundingAmount, - }) - - const newAccount = await AccountsRepository().findById(newBtcWallet.accountId) - if (newAccount instanceof Error) throw newAccount - - return { newBtcWallet, newUsdWallet, newAccount } -} - -const getBtcEquivalentForIntraledgerSendToUsd = async ({ - btcPaymentAmount, - accountAndWallets, -}: { - btcPaymentAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets -}): Promise => { - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - const sendArgs = { - recipientWalletId: newUsdWallet.id, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - } - - const beforeBtc = await getBalanceHelper(newBtcWallet.id) - const beforeRecipientUsd = await getBalanceHelper(newUsdWallet.id) - - const result = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - amount: Number(btcPaymentAmount.amount), - ...sendArgs, - }) - if (result instanceof Error) throw result - - const afterRecipientUsd = await getBalanceHelper(newUsdWallet.id) - if (beforeRecipientUsd === afterRecipientUsd) { - return new ZeroAmountForUsdRecipientError() - } - - const afterBtc = await getBalanceHelper(newBtcWallet.id) - return (beforeBtc - afterBtc) as CurrencyBaseAmount -} - -const getUsdEquivalentForWithAmountInvoiceSendToBtc = async ({ - btcPaymentAmount, - accountAndWallets, -}: { - btcPaymentAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets -}): Promise => { - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(btcPaymentAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const beforeUsd = await getBalanceHelper(newUsdWallet.id) - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - const afterUsd = await getBalanceHelper(newUsdWallet.id) - return (beforeUsd - afterUsd) as CurrencyBaseAmount -} - -const getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc = async ({ - btcPaymentAmount, - accountAndWallets, -}: { - btcPaymentAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets -}): Promise => { - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(btcPaymentAmount.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const beforeUsd = await getBalanceHelper(newUsdWallet.id) - const probeResult = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeResult instanceof Error) throw probeResult - const payResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (payResult instanceof Error) throw payResult - const afterUsd = await getBalanceHelper(newUsdWallet.id) - return (beforeUsd - afterUsd) as CurrencyBaseAmount -} - -const getBtcEquivalentForNoAmountInvoiceSendToUsd = async ({ - btcPaymentAmount, - accountAndWallets, -}: { - btcPaymentAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets -}): Promise => { - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - const beforeRecipientUsd = await getBalanceHelper(newUsdWallet.id) - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const beforeBtc = await getBalanceHelper(newBtcWallet.id) - const result = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - const afterRecipientUsd = await getBalanceHelper(newUsdWallet.id) - if (beforeRecipientUsd === afterRecipientUsd) { - return new ZeroAmountForUsdRecipientError() - } - - const afterBtc = await getBalanceHelper(newBtcWallet.id) - return (beforeBtc - afterBtc) as CurrencyBaseAmount -} - -const getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd = async ({ - btcPaymentAmount, - accountAndWallets, -}: { - btcPaymentAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets -}): Promise => { - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - const beforeRecipientUsd = await getBalanceHelper(newUsdWallet.id) - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const beforeBtc = await getBalanceHelper(newBtcWallet.id) - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) { - if (!(probe instanceof ZeroAmountForUsdRecipientError)) throw probe - return probe - } - - const result = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - const afterRecipientUsd = await getBalanceHelper(newUsdWallet.id) - if (beforeRecipientUsd === afterRecipientUsd) { - return new ZeroAmountForUsdRecipientError() - } - - const afterBtc = await getBalanceHelper(newBtcWallet.id) - return (beforeBtc - afterBtc) as CurrencyBaseAmount -} - -const getMaxBtcAmountToEarn = async ({ - startingBtcAmount, - accountAndWallets, - sentAmountFn, -}: { - startingBtcAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets - sentAmountFn -}): Promise => { - // 3 steps here to: - // - check sentAmount is greater than 1 to start (push up if not) - // - bring the sentAmount down, in case starting sentAmount is already past max - // - push up to find max, from place where we are sure sentAmount is 1 - - let maxBtcAmountToEarn = startingBtcAmount - - // Ensure sentAmount is '> 1' for starting amount - let sentAmount = await sentAmountFn({ - btcPaymentAmount: maxBtcAmountToEarn, - accountAndWallets, - }) - while (sentAmount <= 1) { - maxBtcAmountToEarn = calc.add(maxBtcAmountToEarn, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: maxBtcAmountToEarn, - accountAndWallets, - }) - } - // Decrement until sentAmount is 1 - while (sentAmount > 1) { - maxBtcAmountToEarn = calc.sub(maxBtcAmountToEarn, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: maxBtcAmountToEarn, - accountAndWallets, - }) - } - expect(sentAmount).toEqual(1) - // Increment to discover max BTC amount to buy for $0.01 - while (sentAmount === 1) { - maxBtcAmountToEarn = calc.add(maxBtcAmountToEarn, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: maxBtcAmountToEarn, - accountAndWallets, - }) - } - maxBtcAmountToEarn = calc.sub(maxBtcAmountToEarn, ONE_SAT) - - return maxBtcAmountToEarn -} - -const getMinBtcAmountToSpend = async ({ - startingBtcAmount, - accountAndWallets, - sentAmountFn, -}: { - startingBtcAmount: BtcPaymentAmount - accountAndWallets: AccountAndWallets - sentAmountFn -}): Promise => { - let minBtcAmountToSpend = startingBtcAmount - let sentAmount = await sentAmountFn({ - btcPaymentAmount: minBtcAmountToSpend, - accountAndWallets, - }) - // Ensure sentAmount is 'Success' for starting amount - while (sentAmount instanceof ZeroAmountForUsdRecipientError) { - minBtcAmountToSpend = calc.add(minBtcAmountToSpend, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: minBtcAmountToSpend, - accountAndWallets, - }) - if ( - sentAmount instanceof Error && - !(sentAmount instanceof ZeroAmountForUsdRecipientError) - ) { - throw sentAmount - } - } - // Decrement until sentAmount fails - while ( - !(sentAmount instanceof ZeroAmountForUsdRecipientError) && - minBtcAmountToSpend.amount > 1n - ) { - minBtcAmountToSpend = calc.sub(minBtcAmountToSpend, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: minBtcAmountToSpend, - accountAndWallets, - }) - if ( - sentAmount instanceof Error && - !(sentAmount instanceof ZeroAmountForUsdRecipientError) - ) { - throw sentAmount - } - } - // Increment to discover min BTC amount to sell for $0.01 - while (sentAmount instanceof ZeroAmountForUsdRecipientError) { - minBtcAmountToSpend = calc.add(minBtcAmountToSpend, ONE_SAT) - sentAmount = await sentAmountFn({ - btcPaymentAmount: minBtcAmountToSpend, - accountAndWallets, - }) - if ( - sentAmount instanceof Error && - !(sentAmount instanceof ZeroAmountForUsdRecipientError) - ) { - throw sentAmount - } - } - - return minBtcAmountToSpend -} - -describe("arbitrage strategies", () => { - describe("can pay high sats invoice with $0.01", () => { - // This strategy is to see if we can generate an invoice for an amount of - // BTC that when paid from our USD wallet: - // - rounds to $0.01 - // - is more than the amount of sats to send to USD wallet to get $0.01 - // - // In the original discovery, the opportunity was possible because we could pay - // an invoice up to 78 sats for $0.01 from a USD wallet and then convert back - // to $0.01 for 53 sats. - - describe("pay max btc-amount invoice pull from usd wallet", () => { - describe("replenish with $0.01 pull back to usd wallet", () => { - it("via usd-denominated invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: newUsdWallet.id, - amount: toCents(1), - }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd - - const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (resultUsd instanceof Error) throw resultUsd - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via usd-denominated fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: newUsdWallet.id, - amount: toCents(1), - }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd - - const probe = await Payments.getLightningFeeEstimationForBtcWallet({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (resultUsd instanceof Error) throw resultUsd - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - - describe("replenish with min-btc-for-1-cent push from btc wallet", () => { - it("via intraledger payment", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForIntraledgerSendToUsd, - }) - - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const paid = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: newUsdWallet.id, - memo: null, - amount: toSats(minBtcAmountToSpend.amount), - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if ( - paid instanceof Error && - !(paid instanceof ZeroAmountForUsdRecipientError) - ) { - throw paid - } - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - }) - - describe("pay max btc-amount fee probe pull from usd wallet", () => { - describe("replenish with $0.01 pull back to usd wallet", () => { - it("via usd-denominated invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probe instanceof Error) throw probe - - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: newUsdWallet.id, - amount: toCents(1), - }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd - - const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (resultUsd instanceof Error) throw resultUsd - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via usd-denominated fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probe instanceof Error) throw probe - - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ - walletId: newUsdWallet.id, - amount: toCents(1), - }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd - - const probeUsd = await Payments.getLightningFeeEstimationForBtcWallet({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probeUsd instanceof Error) throw probeUsd - - const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (resultUsd instanceof Error) throw resultUsd - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - - describe("replenish with min-btc-for-1-cent push from btc wallet", () => { - it("via intraledger payment", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForIntraledgerSendToUsd, - }) - - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const probeResult = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeResult instanceof Error) throw probeResult - - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const paid = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - recipientWalletId: newUsdWallet.id, - memo: null, - amount: toSats(minBtcAmountToSpend.amount), - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if ( - paid instanceof Error && - !(paid instanceof ZeroAmountForUsdRecipientError) - ) { - throw paid - } - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount min btc invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probe instanceof Error) throw probe - - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount min btc fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoice instanceof Error) throw lnInvoice - - // Step 2: Pay invoice from USD wallet at favourable rate - const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probe instanceof Error) throw probe - - const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (result instanceof Error) throw result - - // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd - - const probeUsd = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probeUsd instanceof Error) throw probeUsd - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 4: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - }) - }) - - describe("can pay 1 sat and receive $0.01", () => { - // This strategy is to see if we can send 1 sat from a BTC wallet in such - // a way that in a USD wallet it: - // - rounds up to $0.01 - // - can be converted back to BTC to get back more than 1 sat - - describe("pay 1-sat to intraledger usd wallet push from btc wallet", () => { - describe("replenish with $0.01 push from usd wallet", () => { - it("via intraledger payment", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - const sendArgs = { - recipientWalletId: newUsdWallet.id, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - } - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForIntraledgerSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats intraledger to USD wallet - const paid = await Payments.intraledgerPaymentSendWalletIdForBtcWallet({ - amount: toSats(minBtcAmountToSpend.amount), - ...sendArgs, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const repaid = await Payments.intraledgerPaymentSendWalletIdForUsdWallet({ - amount: toCents(1), - recipientWalletId: newBtcWallet.id, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - }) - - describe("pay 1-sat to no-amount usd invoice push from btc wallet", () => { - describe("replenish with $0.01 push from usd wallet", () => { - it("via intraledger payment", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const repaid = await Payments.intraledgerPaymentSendWalletIdForUsdWallet({ - amount: toCents(1), - recipientWalletId: newBtcWallet.id, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newBtcWallet.id, - }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newBtcWallet.id, - }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc - - const probeBtc = await Payments.getNoAmountLightningFeeEstimationForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeBtc instanceof Error) throw probeBtc - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - - describe("replenish with min-btc-for-1-cent pull back to btc wallet", () => { - it("via invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc - - const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc - - const probeUsd = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeUsd instanceof Error) throw probeUsd - - const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - }) - - describe("pay 1-sat to no-amount usd fee probe push from btc wallet", () => { - describe("replenish with $0.01 push from usd wallet", () => { - it("via intraledger payment", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const repaid = await Payments.intraledgerPaymentSendWalletIdForUsdWallet({ - amount: toCents(1), - recipientWalletId: newBtcWallet.id, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newBtcWallet.id, - }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via no-amount fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newBtcWallet.id, - }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc - - const probeBtc = await Payments.getNoAmountLightningFeeEstimationForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeBtc instanceof Error) throw probeBtc - - const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ - amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - - describe("replenish with min-btc-for-1-cent pull back to btc wallet", () => { - it("via invoice", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc - - const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - - it("via fee probe", async () => { - const accountAndWallets = await newAccountAndWallets() - const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - - // DISCOVER ARBITRAGE AMOUNTS FOR STRATEGY - // ===== - const midPriceRatio = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatio instanceof Error) throw midPriceRatio - const startingBtcAmount = midPriceRatio.convertFromUsd(ONE_CENT) - - // Validate btc starting amount for min btc discovery - const minBtcAmountToSpend = await getMinBtcAmountToSpend({ - startingBtcAmount: ONE_SAT, - accountAndWallets, - sentAmountFn: getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd, - }) - baseLogger.info("Discovered:", { minBtcAmountToSpend }) - - // Validate btc starting amount for max btc discovery - const maxBtcAmountToEarn = await getMaxBtcAmountToEarn({ - startingBtcAmount, - accountAndWallets, - sentAmountFn: getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc, - }) - baseLogger.info("Discovered:", { maxBtcAmountToEarn }) - - // EXECUTE ARBITRAGE - // ===== - const btcBalanceBefore = await getBalanceHelper(newBtcWallet.id) - const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) - - // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ - walletId: newUsdWallet.id, - }) - if (lnInvoice instanceof Error) throw lnInvoice - - const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - walletId: newBtcWallet.id, - }) - if (probe instanceof Error) throw probe - - const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ - amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, - memo: null, - senderWalletId: newBtcWallet.id, - senderAccount: newAccount, - }) - if (paid instanceof Error) throw paid - - // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ - walletId: newBtcWallet.id, - amount: toSats(maxBtcAmountToEarn.amount), - }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc - - const probeUsd = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - walletId: newUsdWallet.id, - }) - if (probeUsd instanceof Error) throw probeUsd - - const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, - memo: null, - senderWalletId: newUsdWallet.id, - senderAccount: newAccount, - }) - if (repaid instanceof Error) throw repaid - - // Step 3: Check that no profit was made in the process - const btcBalanceAfter = await getBalanceHelper(newBtcWallet.id) - const sentAmountBtc = btcBalanceAfter - btcBalanceBefore - expect(sentAmountBtc).toBeLessThanOrEqual(0) - - const usdBalanceAfter = await getBalanceHelper(newUsdWallet.id) - const sentAmountUsd = usdBalanceAfter - usdBalanceBefore - expect(sentAmountUsd).toBeLessThanOrEqual(0) - }) - }) - }) - }) -}) diff --git a/test/galoy/legacy-integration/services/dealer/mid-price.spec.ts b/test/galoy/legacy-integration/services/dealer/mid-price.spec.ts deleted file mode 100644 index 64a240494..000000000 --- a/test/galoy/legacy-integration/services/dealer/mid-price.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { getCurrentPriceAsWalletPriceRatio, getMidPriceRatio } from "@app/prices" -import { UsdDisplayCurrency } from "@domain/fiat" - -import { DealerPriceService } from "@services/dealer-price" - -describe("getMidPriceRatio", () => { - const fetchPrices = async () => { - const dealerMidPriceRatio = - await DealerPriceService().getCentsPerSatsExchangeMidRate() - if (dealerMidPriceRatio instanceof Error) throw dealerMidPriceRatio - - const priceMidPriceRatio = await getCurrentPriceAsWalletPriceRatio({ - currency: UsdDisplayCurrency, - }) - if (priceMidPriceRatio instanceof Error) throw priceMidPriceRatio - - return { - dealerMidPriceRatio: dealerMidPriceRatio.usdPerSat(), - priceMidPriceRatio: priceMidPriceRatio.usdPerSat(), - } - } - - it("fetches mid price from price server", async () => { - const usdHedgeEnabled = false - const midPriceRatioResult = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatioResult instanceof Error) throw midPriceRatioResult - const midPriceRatio = midPriceRatioResult.usdPerSat() - - const priceRatios = await fetchPrices() - const { dealerMidPriceRatio, priceMidPriceRatio } = priceRatios - - expect(dealerMidPriceRatio).not.toEqual(priceMidPriceRatio) - expect(midPriceRatio).toEqual(priceMidPriceRatio) - }) - - it("fetches mid price from dealer server", async () => { - const usdHedgeEnabled = true - const midPriceRatioResult = await getMidPriceRatio(usdHedgeEnabled) - if (midPriceRatioResult instanceof Error) throw midPriceRatioResult - const midPriceRatio = midPriceRatioResult.usdPerSat() - - const priceRatios = await fetchPrices() - const { dealerMidPriceRatio, priceMidPriceRatio } = priceRatios - - expect(dealerMidPriceRatio).not.toEqual(priceMidPriceRatio) - expect(midPriceRatio).toEqual(dealerMidPriceRatio) - }) -}) diff --git a/test/galoy/legacy-integration/services/ledger/service.spec.ts b/test/galoy/legacy-integration/services/ledger/service.spec.ts deleted file mode 100644 index bf1c354c8..000000000 --- a/test/galoy/legacy-integration/services/ledger/service.spec.ts +++ /dev/null @@ -1,595 +0,0 @@ -/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "testInternalTx", "testExternalTx"] }] */ -import crypto from "crypto" - -import { ONE_DAY } from "@config" - -import { - AmountCalculator, - BtcWalletDescriptor, - UsdWalletDescriptor, - WalletCurrency, -} from "@domain/shared" -import { LedgerTransactionType } from "@domain/ledger" - -import { LedgerService } from "@services/ledger" - -import { ModifiedSet, timestampDaysAgo } from "@utils" - -import { - recordLnIntraLedgerPayment, - recordLnFeeReimbursement, - recordLnFailedPayment, - recordReceiveLnPayment, - recordSendLnPayment, - recordSendOnChainPayment, - recordReceiveOnChainPayment, - recordWalletIdIntraLedgerPayment, - recordOnChainIntraLedgerPayment, - recordLnChannelOpenOrClosingFee, - recordLndEscrowCredit, - recordLndEscrowDebit, - recordLnRoutingRevenue, - recordColdStorageTxReceive, - recordColdStorageTxSend, - recordWalletIdTradeIntraAccountTxn, - recordOnChainTradeIntraAccountTxn, - recordLnTradeIntraAccountTxn, -} from "test/galoy/helpers/ledger" - -const ledgerService = LedgerService() -const calc = AmountCalculator() - -const FullLedgerTransactionType = { - ...LedgerTransactionType, - IntraLedgerSend: LedgerTransactionType.IntraLedger, - IntraLedgerReceive: LedgerTransactionType.IntraLedger, - LnIntraLedgerSend: LedgerTransactionType.LnIntraLedger, - LnIntraLedgerReceive: LedgerTransactionType.LnIntraLedger, - OnchainIntraLedgerSend: LedgerTransactionType.OnchainIntraLedger, - OnchainIntraLedgerReceive: LedgerTransactionType.OnchainIntraLedger, - - WalletIdTradeIntraAccountOut: LedgerTransactionType.WalletIdTradeIntraAccount, - WalletIdTradeIntraAccountIn: LedgerTransactionType.WalletIdTradeIntraAccount, - LnTradeIntraAccountOut: LedgerTransactionType.LnTradeIntraAccount, - LnTradeIntraAccountIn: LedgerTransactionType.LnTradeIntraAccount, - OnChainTradeIntraAccountOut: LedgerTransactionType.OnChainTradeIntraAccount, - OnChainTradeIntraAccountIn: LedgerTransactionType.OnChainTradeIntraAccount, - - EscrowCredit: LedgerTransactionType.Escrow, - EscrowDebit: LedgerTransactionType.Escrow, -} as const - -const { - IntraLedger, // eslint-disable-line @typescript-eslint/no-unused-vars - LnIntraLedger, // eslint-disable-line @typescript-eslint/no-unused-vars - OnchainIntraLedger, // eslint-disable-line @typescript-eslint/no-unused-vars - WalletIdTradeIntraAccount, // eslint-disable-line @typescript-eslint/no-unused-vars - LnTradeIntraAccount, // eslint-disable-line @typescript-eslint/no-unused-vars - OnChainTradeIntraAccount, // eslint-disable-line @typescript-eslint/no-unused-vars - Escrow, // eslint-disable-line @typescript-eslint/no-unused-vars - - ...ExtendedLedgerTransactionType -} = FullLedgerTransactionType - -const { - Fee, // eslint-disable-line @typescript-eslint/no-unused-vars - RoutingRevenue, // eslint-disable-line @typescript-eslint/no-unused-vars - ToColdStorage, // eslint-disable-line @typescript-eslint/no-unused-vars - ToHotWallet, // eslint-disable-line @typescript-eslint/no-unused-vars - EscrowCredit, // eslint-disable-line @typescript-eslint/no-unused-vars - EscrowDebit, // eslint-disable-line @typescript-eslint/no-unused-vars - - ...UserLedgerTransactionType -} = ExtendedLedgerTransactionType - -describe("Volumes", () => { - const timestamp1DayAgo = timestampDaysAgo(ONE_DAY) - if (timestamp1DayAgo instanceof Error) return timestamp1DayAgo - - const walletDescriptor = BtcWalletDescriptor(crypto.randomUUID() as WalletId) - const walletDescriptorOther = UsdWalletDescriptor(crypto.randomUUID() as WalletId) - - const paymentAmount = { - usd: { amount: 200n, currency: WalletCurrency.Usd }, - btc: { amount: 400n, currency: WalletCurrency.Btc }, - } - const bankFee = { - usd: { amount: 10n, currency: WalletCurrency.Usd }, - btc: { amount: 20n, currency: WalletCurrency.Btc }, - } - - const displayAmounts = { - amountDisplayCurrency: 240 as DisplayCurrencyBaseAmount, - feeDisplayCurrency: 24 as DisplayCurrencyBaseAmount, - displayCurrency: "EUR" as DisplayCurrency, - } - - const senderDisplayAmounts = { - senderAmountDisplayCurrency: displayAmounts.amountDisplayCurrency, - senderFeeDisplayCurrency: displayAmounts.feeDisplayCurrency, - senderDisplayCurrency: displayAmounts.displayCurrency, - } - - const recipientDisplayAmounts = { - recipientAmountDisplayCurrency: displayAmounts.amountDisplayCurrency, - recipientFeeDisplayCurrency: displayAmounts.feeDisplayCurrency, - recipientDisplayCurrency: displayAmounts.displayCurrency, - } - - type fetchVolumeAmountType = ( - walletDescriptor: WalletDescriptor, - ) => Promise> - - // Each "TxFn" executes a transaction for a given type and then checks if - // the respective tx volume has been affected or not. - const prepareTxFns = ( - fetchVolumeAmount: fetchVolumeAmountType, - ) => { - // Base function for extra-ledger transactions (onchain/ln) - const testExternalTx = async ({ - recordTx, - calcFn, - }: { - recordTx: RecordExternalTxTestFn - calcFn: (a, b) => PaymentAmount - }) => { - const currentVolumeAmount = await fetchVolumeAmount( - walletDescriptor as WalletDescriptor, - ) - if (currentVolumeAmount instanceof Error) throw currentVolumeAmount - const expected = calcFn(currentVolumeAmount, paymentAmount.btc) - - const result = await recordTx({ - walletDescriptor, - paymentAmount, - bankFee, - displayAmounts, - }) - expect(result).not.toBeInstanceOf(Error) - - const actual = await fetchVolumeAmount(walletDescriptor as WalletDescriptor) - expect(expected).toStrictEqual(actual) - } - - // Base function for intra-ledger transactions - const testInternalTx = async ({ - recordTx, - sender, - recipient, - calcFn, - }: { - recordTx: RecordInternalTxTestFn - sender: WalletDescriptor - recipient: WalletDescriptor - calcFn: (a, b) => PaymentAmount - }) => { - const currentVolumeAmount = await fetchVolumeAmount( - walletDescriptor as WalletDescriptor, - ) - const expected = calcFn(currentVolumeAmount, paymentAmount.btc) - - const result = await recordTx({ - senderWalletDescriptor: sender, - recipientWalletDescriptor: recipient, - paymentAmount, - senderDisplayAmounts, - recipientDisplayAmounts, - }) - expect(result).not.toBeInstanceOf(Error) - - const actual = await fetchVolumeAmount(walletDescriptor as WalletDescriptor) - expect(expected).toStrictEqual(actual) - } - - const sendVolumeCalc = calc.add - const receiveVolumeCalc = calc.sub - const noVolumeCalc = (a) => a - - return { - withVolumeEffect: { - testExternalTxSendWLE: (args) => - it(`${args.recordTx.name}`, async () => - testExternalTx({ ...args, calcFn: sendVolumeCalc })), - testExternalTxReceiveWLE: (args) => - it(`${args.recordTx.name}`, async () => - testExternalTx({ ...args, calcFn: receiveVolumeCalc })), - testInternalTxSendWLE: (args) => - it(`send ${args.recordTx.name}`, async () => - testInternalTx({ - ...args, - sender: walletDescriptor, - recipient: walletDescriptorOther, - calcFn: sendVolumeCalc, - })), - testInternalTxReceiveWLE: (args) => - it(`receive ${args.recordTx.name}`, async () => - testInternalTx({ - ...args, - sender: walletDescriptorOther, - recipient: walletDescriptor, - calcFn: receiveVolumeCalc, - })), - }, - noVolumeEffect: { - testExternalTxNLE: (args) => - it(`${args.recordTx.name}`, async () => - testExternalTx({ ...args, calcFn: noVolumeCalc })), - testInternalTxSendNLE: (args) => - it(`send ${args.recordTx.name}`, async () => - testInternalTx({ - ...args, - sender: walletDescriptor, - recipient: walletDescriptorOther, - calcFn: noVolumeCalc, - })), - testInternalTxReceiveNLE: (args) => - it(`receive ${args.recordTx.name}`, async () => - testInternalTx({ - ...args, - sender: walletDescriptorOther, - recipient: walletDescriptor, - calcFn: noVolumeCalc, - })), - }, - } - } - - const txTypesForVolumes = ( - includedTypes: (keyof typeof ExtendedLedgerTransactionType)[], - ) => { - const excludedTypes = Object.keys(ExtendedLedgerTransactionType) - .map((key) => key as keyof typeof ExtendedLedgerTransactionType) - .filter( - (key: keyof typeof ExtendedLedgerTransactionType) => !includedTypes.includes(key), - ) - - it("prepares volume tx types", () => { - const includedTypesSet = new ModifiedSet(includedTypes) - const excludedTypesSet = new ModifiedSet(excludedTypes) - expect(includedTypesSet.intersect(excludedTypesSet).size).toEqual(0) - }) - - return { includedTypes, excludedTypes } - } - - // Used to manage how 'outgoing'/'incoming' from volumes is applied - const VolumeType = { - Out: "out", - NetOut: "netOut", - In: "in", - } as const - - // Used to construct the 'fetchVolumeAmount' fn for a specific volume type - const getFetchVolumeAmountFn = ({ - volumeFn, - volumeAmountFn, - volumeType, - }: { - volumeFn: GetVolumeSinceFn - volumeAmountFn: GetVolumeAmountSinceFn - volumeType - }): fetchVolumeAmountType => { - const fetchVolumeAmountFn: fetchVolumeAmountType = async ( - walletDescriptor: WalletDescriptor, - ): Promise> => { - const walletVolume = await volumeFn({ - walletId: walletDescriptor.id, - timestamp: timestamp1DayAgo, - }) - expect(walletVolume).not.toBeInstanceOf(Error) - if (walletVolume instanceof Error) throw walletVolume - - const walletVolumeAmount = await volumeAmountFn({ - walletDescriptor, - timestamp: timestamp1DayAgo, - }) - expect(walletVolumeAmount).not.toBeInstanceOf(Error) - if (walletVolumeAmount instanceof Error) throw walletVolumeAmount - - const { outgoingBaseAmount: outgoingBase } = walletVolume - const { outgoingBaseAmount } = walletVolumeAmount - expect(outgoingBase).toEqual(Number(outgoingBaseAmount.amount)) - - const { incomingBaseAmount: incomingBase } = walletVolume - const { incomingBaseAmount } = walletVolumeAmount - expect(incomingBase).toEqual(Number(incomingBaseAmount.amount)) - - // FIXME: change in code to couple this method to actual implementation - // return calc.sub(outgoingBaseAmount, incomingBaseAmount) - - switch (volumeType) { - case VolumeType.Out: - return walletVolumeAmount.outgoingBaseAmount - case VolumeType.In: - return walletVolumeAmount.incomingBaseAmount - case VolumeType.NetOut: - return calc.sub( - walletVolumeAmount.outgoingBaseAmount, - walletVolumeAmount.incomingBaseAmount, - ) - default: - throw new Error("Invalid 'volumeType' arg") - } - } - - return fetchVolumeAmountFn - } - - // Executes the tests for each 'describe' for volume types below - const executeVolumeTests = ({ - includedTxTypes, - fetchVolumeAmount, - }: { - includedTxTypes: (keyof typeof UserLedgerTransactionType)[] - fetchVolumeAmount: fetchVolumeAmountType - }) => { - const { includedTypes, excludedTypes } = txTypesForVolumes(includedTxTypes) - - const { - withVolumeEffect: { - testExternalTxSendWLE, - testExternalTxReceiveWLE, - testInternalTxSendWLE, - testInternalTxReceiveWLE, - }, - noVolumeEffect: { - testExternalTxNLE, - testInternalTxSendNLE, - testInternalTxReceiveNLE, - }, - } = prepareTxFns(fetchVolumeAmount) - - // Setting up all 'it' tests for each txn type, to check volume is affected - const txFnsForIncludedTypes = { - Invoice: () => testExternalTxReceiveWLE({ recordTx: recordReceiveLnPayment }), - OnchainReceipt: () => - testExternalTxReceiveWLE({ recordTx: recordReceiveOnChainPayment }), - Payment: () => testExternalTxSendWLE({ recordTx: recordSendLnPayment }), - OnchainPayment: () => testExternalTxSendWLE({ recordTx: recordSendOnChainPayment }), - LnFeeReimbursement: () => - testExternalTxReceiveWLE({ - recordTx: recordLnFeeReimbursement, - }), - LnFailedPayment: () => - testExternalTxReceiveWLE({ - recordTx: recordLnFailedPayment, - }), - IntraLedgerSend: () => - testInternalTxSendWLE({ - recordTx: recordWalletIdIntraLedgerPayment, - }), - IntraLedgerReceive: () => - testInternalTxReceiveWLE({ - recordTx: recordWalletIdIntraLedgerPayment, - }), - OnchainIntraLedgerSend: () => - testInternalTxSendWLE({ - recordTx: recordOnChainIntraLedgerPayment, - }), - OnchainIntraLedgerReceive: () => - testInternalTxReceiveWLE({ - recordTx: recordOnChainIntraLedgerPayment, - }), - LnIntraLedgerSend: () => - testInternalTxSendWLE({ - recordTx: recordLnIntraLedgerPayment, - }), - LnIntraLedgerReceive: () => - testInternalTxReceiveWLE({ - recordTx: recordLnIntraLedgerPayment, - }), - WalletIdTradeIntraAccountOut: () => - testInternalTxSendWLE({ - recordTx: recordWalletIdTradeIntraAccountTxn, - }), - LnTradeIntraAccountOut: () => - testInternalTxSendWLE({ - recordTx: recordLnTradeIntraAccountTxn, - }), - OnChainTradeIntraAccountOut: () => - testInternalTxSendWLE({ - recordTx: recordOnChainTradeIntraAccountTxn, - }), - WalletIdTradeIntraAccountIn: () => - testInternalTxReceiveWLE({ - recordTx: recordWalletIdTradeIntraAccountTxn, - }), - LnTradeIntraAccountIn: () => - testInternalTxReceiveWLE({ - recordTx: recordLnTradeIntraAccountTxn, - }), - OnChainTradeIntraAccountIn: () => - testInternalTxReceiveWLE({ - recordTx: recordOnChainTradeIntraAccountTxn, - }), - - // Used, but no volume checks yet: - Fee: () => testExternalTxSendWLE({ recordTx: recordLnChannelOpenOrClosingFee }), - EscrowCredit: () => testExternalTxSendWLE({ recordTx: recordLndEscrowCredit }), - EscrowDebit: () => testExternalTxSendWLE({ recordTx: recordLndEscrowDebit }), - RoutingRevenue: () => testExternalTxSendWLE({ recordTx: recordLnRoutingRevenue }), - ToHotWallet: () => testExternalTxSendWLE({ recordTx: recordColdStorageTxSend }), - ToColdStorage: () => - testExternalTxSendWLE({ recordTx: recordColdStorageTxReceive }), - } - - // Setting up all 'it' tests for each txn type, to check volume is NOT affected - const txFnsForExcludedTypes = { - Invoice: () => testExternalTxNLE({ recordTx: recordReceiveLnPayment }), - OnchainReceipt: () => testExternalTxNLE({ recordTx: recordReceiveOnChainPayment }), - Payment: () => testExternalTxNLE({ recordTx: recordSendLnPayment }), - OnchainPayment: () => testExternalTxNLE({ recordTx: recordSendOnChainPayment }), - LnFeeReimbursement: () => testExternalTxNLE({ recordTx: recordLnFeeReimbursement }), - LnFailedPayment: () => testExternalTxNLE({ recordTx: recordLnFailedPayment }), - Fee: () => testExternalTxNLE({ recordTx: recordLnChannelOpenOrClosingFee }), - EscrowCredit: () => testExternalTxNLE({ recordTx: recordLndEscrowCredit }), - EscrowDebit: () => testExternalTxNLE({ recordTx: recordLndEscrowDebit }), - RoutingRevenue: () => testExternalTxNLE({ recordTx: recordLnRoutingRevenue }), - ToHotWallet: () => testExternalTxNLE({ recordTx: recordColdStorageTxSend }), - ToColdStorage: () => testExternalTxNLE({ recordTx: recordColdStorageTxReceive }), - IntraLedgerSend: () => - testInternalTxSendNLE({ - recordTx: recordWalletIdIntraLedgerPayment, - }), - IntraLedgerReceive: () => - testInternalTxReceiveNLE({ - recordTx: recordWalletIdIntraLedgerPayment, - }), - OnchainIntraLedgerSend: () => - testInternalTxSendNLE({ - recordTx: recordOnChainIntraLedgerPayment, - }), - OnchainIntraLedgerReceive: () => - testInternalTxReceiveNLE({ - recordTx: recordOnChainIntraLedgerPayment, - }), - LnIntraLedgerSend: () => - testInternalTxSendNLE({ - recordTx: recordLnIntraLedgerPayment, - }), - LnIntraLedgerReceive: () => - testInternalTxReceiveNLE({ - recordTx: recordLnIntraLedgerPayment, - }), - WalletIdTradeIntraAccountOut: () => - testInternalTxSendNLE({ - recordTx: recordWalletIdTradeIntraAccountTxn, - }), - LnTradeIntraAccountOut: () => - testInternalTxSendNLE({ - recordTx: recordLnTradeIntraAccountTxn, - }), - OnChainTradeIntraAccountOut: () => - testInternalTxSendNLE({ - recordTx: recordOnChainTradeIntraAccountTxn, - }), - WalletIdTradeIntraAccountIn: () => - testInternalTxReceiveNLE({ - recordTx: recordWalletIdTradeIntraAccountTxn, - }), - LnTradeIntraAccountIn: () => - testInternalTxReceiveNLE({ - recordTx: recordLnTradeIntraAccountTxn, - }), - OnChainTradeIntraAccountIn: () => - testInternalTxReceiveNLE({ - recordTx: recordOnChainTradeIntraAccountTxn, - }), - } - - // Execute tests for specific types included - describe("correctly registers transactions amount", () => { - for (const txType of includedTypes) { - it("has a valid type", () => { - expect(Object.keys(txFnsForIncludedTypes)).toContain(txType) - }) - txFnsForIncludedTypes[txType]() - } - }) - - // Execute tests for rest of types excluded - describe("correctly ignores all other transaction types", () => { - for (const txType of excludedTypes) { - it("has a valid type", () => { - expect(Object.keys(txFnsForExcludedTypes)).toContain(txType) - }) - txFnsForExcludedTypes[txType]() - } - }) - } - - // EXECUTE TESTS FOR EACH VOLUME TYPE - // ========== - - describe("All payment volumes", () => { - executeVolumeTests({ - includedTxTypes: [ - "Payment", - "OnchainPayment", - "IntraLedgerSend", - "OnchainIntraLedgerSend", - "LnIntraLedgerSend", - ], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.allPaymentVolumeSince, - volumeAmountFn: ledgerService.allPaymentVolumeAmountSince, - volumeType: VolumeType.Out, - }), - }) - }) - - describe("External payment (withdrawal) volumes", () => { - executeVolumeTests({ - includedTxTypes: ["Payment", "OnchainPayment"], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.externalPaymentVolumeSince, - volumeAmountFn: ledgerService.externalPaymentVolumeAmountSince, - volumeType: VolumeType.Out, - }), - }) - }) - - describe("Internal payment volumes", () => { - executeVolumeTests({ - includedTxTypes: ["IntraLedgerSend", "OnchainIntraLedgerSend", "LnIntraLedgerSend"], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.intraledgerTxBaseVolumeSince, - volumeAmountFn: ledgerService.intraledgerTxBaseVolumeAmountSince, - volumeType: VolumeType.Out, - }), - }) - }) - - describe("Intra-account trade volumes", () => { - executeVolumeTests({ - // Note: Including '...In' types as well would double-count volumes - includedTxTypes: [ - "WalletIdTradeIntraAccountOut", - "OnChainTradeIntraAccountOut", - "LnTradeIntraAccountOut", - ], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.tradeIntraAccountTxBaseVolumeSince, - volumeAmountFn: ledgerService.tradeIntraAccountTxBaseVolumeAmountSince, - volumeType: VolumeType.Out, - }), - }) - }) - - describe("All activity", () => { - const includedTxTypes = Object.keys( - UserLedgerTransactionType, - ) as (keyof typeof UserLedgerTransactionType)[] - - executeVolumeTests({ - includedTxTypes, - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.allTxBaseVolumeSince, - volumeAmountFn: ledgerService.allTxBaseVolumeAmountSince, - volumeType: VolumeType.NetOut, - }), - }) - }) - - describe("All onchain activity", () => { - executeVolumeTests({ - includedTxTypes: ["OnchainPayment", "OnchainReceipt"], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.onChainTxBaseVolumeSince, - volumeAmountFn: ledgerService.onChainTxBaseVolumeAmountSince, - volumeType: VolumeType.NetOut, - }), - }) - }) - - describe("All ln activity", () => { - executeVolumeTests({ - includedTxTypes: ["Payment", "Invoice", "LnFeeReimbursement"], - fetchVolumeAmount: getFetchVolumeAmountFn({ - volumeFn: ledgerService.lightningTxBaseVolumeSince, - volumeAmountFn: ledgerService.lightningTxBaseVolumeAmountSince, - volumeType: VolumeType.NetOut, - }), - }) - }) -}) diff --git a/test/galoy/legacy-integration/services/lnd/health.spec.ts b/test/galoy/legacy-integration/services/lnd/health.spec.ts deleted file mode 100644 index d5799c82d..000000000 --- a/test/galoy/legacy-integration/services/lnd/health.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { isUp, lndStatusEvent } from "@services/lnd/health" - -import { lndsConnect } from "@services/lnd/auth" - -import { clearAccountLocks } from "test/galoy/helpers/redis" - -beforeAll(async () => { - await clearAccountLocks() -}) - -describe("lndHealth", () => { - // this is a test health checks on lnd - it("should emit on started", async () => { - const handler = jest.fn() - const node = lndsConnect[0] - node.active = false - - lndStatusEvent.on("started", handler) - await isUp(node) - lndStatusEvent.removeAllListeners() - - expect(handler).toHaveBeenCalledTimes(1) - - const { active, lnd, type } = handler.mock.calls[0][0] - expect(active).toBe(true) - expect(type).toStrictEqual(["offchain", "onchain"]) - // validates if it is an authenticated lnd - expect(lnd.unlocker).toBeUndefined() - expect(lnd.wallet).toBeDefined() - }) -}) diff --git a/test/galoy/legacy-integration/services/lnd/parse-errors.spec.ts b/test/galoy/legacy-integration/services/lnd/parse-errors.spec.ts deleted file mode 100644 index 0334d3237..000000000 --- a/test/galoy/legacy-integration/services/lnd/parse-errors.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { decodeInvoice } from "@domain/bitcoin/lightning" -import { getActiveLnd, parseLndErrorDetails } from "@services/lnd/config" - -import { - createInvoice, - payViaPaymentDetails, - payViaRoutes, - LightningError as LnError, -} from "lightning" - -import { getError, lndOutside1 } from "test/galoy/helpers" - -describe("'lightning' library error handling", () => { - const activeNode = getActiveLnd() - if (activeNode instanceof Error) throw activeNode - - const lnd = activeNode.lnd - - // Test construction taken from: - // https://github.com/alexbosworth/lightning/blob/edcaf671e6a0bd2d8f8aa39b51ef816b2a633560/test/lnd_methods/offchain/test_pay_via_routes.js#L28 - it("parses error message when no additional details are found", async () => { - const payArgs = { id: "id", lnd, routes: [] } - const err = await getError(() => payViaRoutes(payArgs)) - expect(err).toHaveLength(2) - expect(err[0]).toEqual(400) - - const parsedErr = parseLndErrorDetails(err) - expect(parsedErr).toBe("ExpectedStandardHexPaymentHashId") - }) - - it("parses error message from err object", async () => { - const { request } = await createInvoice({ - lnd: lndOutside1, - tokens: 1000, - }) - const decodedInvoice = decodeInvoice(request as EncodedPaymentRequest) - expect(decodedInvoice).not.toBeInstanceOf(Error) - if (decodedInvoice instanceof Error) throw decodedInvoice - - const paymentDetailsArgs = { - lnd, - id: decodedInvoice.paymentHash, - destination: decodedInvoice.destination, - mtokens: decodedInvoice.milliSatsAmount.toString(), - payment: decodedInvoice.paymentSecret as string, - cltv_delta: decodedInvoice.cltvDelta || undefined, - features: decodedInvoice.features - ? decodedInvoice.features.map((f) => ({ - bit: f.bit, - is_required: f.isRequired, - type: f.type, - })) - : undefined, - routes: [], - } - - await payViaPaymentDetails(paymentDetailsArgs) - - const err = await getError(() => payViaPaymentDetails(paymentDetailsArgs)) - expect(err).toHaveLength(3) - expect(err[0]).toEqual(503) - expect(err[1]).toBe("UnexpectedPaymentError") - expect(err[2]).toHaveProperty("err") - expect(err[2]).not.toHaveProperty("failures") - - const nestedErrObj = err[2].err - expect(nestedErrObj).toBeInstanceOf(Error) - expect(nestedErrObj).toHaveProperty("code") - expect(nestedErrObj).toHaveProperty("metadata") - - const expectedDetails = "invoice is already paid" - expect(nestedErrObj).toHaveProperty("details", expectedDetails) - - const parsedErr = parseLndErrorDetails(err) - expect(parsedErr).toBe(expectedDetails) - }) - - it("parses error message from failures object", async () => { - const payArgs = { - lnd, - routes: [ - { - fee: 1, - fee_mtokens: "1000", - hops: [ - { - channel: "1x1x1", - channel_capacity: 1, - fee: 1, - fee_mtokens: "1000", - forward: 1, - forward_mtokens: "1000", - public_key: Buffer.alloc(33).toString("hex"), - timeout: 100, - }, - ], - mtokens: "1000", - timeout: 100, - tokens: 1, - }, - ], - } - - const err = await getError>(() => - payViaRoutes(payArgs), - ) - expect(err).toHaveLength(3) - expect(err[0]).toEqual(503) - expect(err[1]).toBe("UnexpectedErrorWhenPayingViaRoute") - - const nestedFailureErr = err[2].failures[0] - expect(nestedFailureErr).toHaveLength(3) - expect(nestedFailureErr[0]).toEqual(err[0]) - expect(nestedFailureErr[1]).toBe(err[1]) - - const nestedErrObj = nestedFailureErr[2].err - expect(nestedErrObj).toBeInstanceOf(Error) - expect(nestedErrObj).toHaveProperty("code") - expect(nestedErrObj).toHaveProperty("metadata") - - const expectedDetails = "invalid public key: unsupported format: 0" - expect(nestedErrObj).toHaveProperty("details", expectedDetails) - - const parsedErr = parseLndErrorDetails(err) - expect(parsedErr).toBe(expectedDetails) - }) -}) diff --git a/test/galoy/legacy-integration/services/lnd/utils.spec.ts b/test/galoy/legacy-integration/services/lnd/utils.spec.ts deleted file mode 100644 index aaca9ce60..000000000 --- a/test/galoy/legacy-integration/services/lnd/utils.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { MS_PER_DAY, ONE_DAY } from "@config" -import { deleteExpiredWalletInvoice, updateRoutingRevenues } from "@services/lnd/utils" -import { baseLogger } from "@services/logger" -import { ledgerAdmin } from "@services/mongodb" -import { DbMetadata, WalletInvoice } from "@services/mongoose/schema" - -import { sleep, timestampDaysAgo } from "@utils" - -import { - cancelHodlInvoice, - clearAccountLocks, - createInvoice, - getForwards, - getInvoiceAttempt, - lnd1, - lndOutside1, - lndOutside2, - pay, - subscribeToInvoice, - waitFor, -} from "test/galoy/helpers" - -beforeAll(async () => { - await clearAccountLocks() -}) - -afterEach(() => { - jest.restoreAllMocks() -}) - -describe("lndUtils", () => { - // this is a test for gc-canceled-invoices-on-the-fly=true settings - it("test cancelling invoice effect", async () => { - const lnd = lndOutside2 - - const { id } = await createInvoice({ lnd, tokens: 10000 }) - - { - const invoice = await getInvoiceAttempt({ lnd, id }) - expect(invoice).toBeTruthy() - } - - await cancelHodlInvoice({ lnd, id }) - - { - const invoice = await getInvoiceAttempt({ lnd, id }) - expect(invoice).toBeNull() - } - }) - - it("test expiring invoice effect", async () => { - const lnd = lndOutside2 - - // expire in 1 second - const expires_at = new Date(Date.now() + 1000).toISOString() - - const { id } = await createInvoice({ lnd, tokens: 10000, expires_at }) - - { - const invoice = await getInvoiceAttempt({ lnd, id }) - expect(invoice).toBeTruthy() - } - - let isCanceled = false - const sub = subscribeToInvoice({ lnd, id }) - sub.on("invoice_updated", async (invoice) => { - await sleep(1000) - isCanceled = invoice.is_canceled - }) - - await waitFor(() => isCanceled) - - sub.removeAllListeners() - - { - const invoice = await getInvoiceAttempt({ lnd, id }) - expect(invoice).toBeNull() - } - }) - - it("sets routing fee correctly", async () => { - const { request } = await createInvoice({ lnd: lndOutside2, tokens: 10000 }) - - const initBalance = await ledgerAdmin.getBankOwnerBalance() - - await waitFor(async () => { - try { - return pay({ lnd: lndOutside1, request }) - } catch (error) { - baseLogger.warn({ error }, "pay failed. trying again.") - return null - } - }) - - const date = Date.now() + MS_PER_DAY * 2 - jest.spyOn(global.Date, "now").mockImplementation(() => new Date(date).valueOf()) - - const startDate = new Date(0) - startDate.setUTCHours(0, 0, 0, 0) - - const endDate = timestampDaysAgo(ONE_DAY) - if (endDate instanceof Error) return endDate - endDate.setUTCHours(0, 0, 0, 0) - - const after = startDate.toISOString() - const before = endDate.toISOString() - - const totalFees = (await getForwards({ lnd: lnd1, after, before })).forwards.reduce( - (acc, val) => acc + Number(val.fee_mtokens), - 0, - ) - - baseLogger.debug( - (await getForwards({ lnd: lnd1, after, before })).forwards, - "forwards", - ) - - await DbMetadata.findOneAndUpdate( - {}, - { $set: { routingFeeLastEntry: null } }, - { upsert: true }, - ) - - await updateRoutingRevenues() - - const endBalance = await ledgerAdmin.getBankOwnerBalance() - - expect((endBalance - initBalance) * 1000).toBeCloseTo(totalFees, 0) - }) - - it("deletes expired WalletInvoice without throw an exception", async () => { - const delta = 90 // days - const mockDate = new Date() - mockDate.setDate(mockDate.getDate() + delta) - jest.spyOn(global.Date, "now").mockImplementation(() => new Date(mockDate).valueOf()) - - const queryDate = new Date() - queryDate.setDate(queryDate.getDate() - delta) - - const invoicesCount = await WalletInvoice.countDocuments({ - timestamp: { $lt: queryDate }, - paid: false, - }) - const result = await deleteExpiredWalletInvoice() - expect(result).toBe(invoicesCount) - }) -}) diff --git a/test/galoy/legacy-integration/services/lock.spec.ts b/test/galoy/legacy-integration/services/lock.spec.ts deleted file mode 100644 index 5e73846d7..000000000 --- a/test/galoy/legacy-integration/services/lock.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { sleep } from "@utils" -import { LockService, redlock } from "@services/lock" -import { ResourceAttemptsLockServiceError } from "@domain/lock" - -import { redis } from "@services/redis" -import { baseLogger } from "@services/logger" - -describe("Lock", () => { - describe("lockWalletId", () => { - it("returns ResourceAttemptsLockServiceError when exceed attempts", async () => { - const walletId = "walletId" as WalletId - const lockService = LockService() - - const lock1 = lockService.lockWalletId(walletId, async () => { - await sleep(5000) - return 1 - }) - - const lock2 = lockService.lockWalletId(walletId, async () => { - return 2 - }) - - const result = await Promise.race([lock1, lock2]) - expect(result).toBeInstanceOf(ResourceAttemptsLockServiceError) - }) - }) -}) - -const walletId = "1234" - -const checkLockExist = (client) => - new Promise((resolve) => - client.get(walletId, (err, res) => { - resolve(!!res) - }), - ) - -describe("Redlock", () => { - it("return value is passed with a promise", async () => { - const result = await redlock({ - path: walletId, - asyncFn: async () => { - return "r" - }, - }) - - expect(result).toBe("r") - }) - - it("use signal if this exist", async () => { - const result = await redlock({ - path: walletId, - asyncFn: async (signal) => { - return redlock({ - path: walletId, - signal, - asyncFn: async () => { - return "r" - }, - }) - }, - }) - - expect(result).toBe("r") - }) - - it("relocking fail if signal is not passed down the tree", async () => { - await expect( - redlock({ - path: walletId, - asyncFn: async () => { - return redlock({ - path: walletId, - asyncFn: async () => { - return "r" - }, - }) - }, - }), - ).resolves.toBeInstanceOf(ResourceAttemptsLockServiceError) - }) - - it("second loop start after first loop has ended", async () => { - const order: number[] = [] - - await Promise.all([ - redlock({ - path: walletId, - asyncFn: async () => { - order.push(1) - await sleep(500) - order.push(2) - }, - }), - redlock({ - path: walletId, - asyncFn: async () => { - order.push(3) - await sleep(500) - order.push(4) - }, - }), - ]) - - expect(order).toStrictEqual([1, 2, 3, 4]) - }) - - it("throwing error releases the lock", async () => { - try { - await redlock({ - path: walletId, - asyncFn: async () => { - expect(await checkLockExist(redis)).toBeTruthy() - await sleep(500) - throw Error("dummy error") - }, - }) - } catch (err) { - baseLogger.info(`error is being caught ${err}`) - } - - expect(await checkLockExist(redis)).toBeFalsy() - }) -}) diff --git a/test/galoy/legacy-integration/services/mongoose/user-repository.spec.ts b/test/galoy/legacy-integration/services/mongoose/user-repository.spec.ts deleted file mode 100644 index f49b41a29..000000000 --- a/test/galoy/legacy-integration/services/mongoose/user-repository.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { DuplicateKeyForPersistError } from "@domain/errors" -import { UsersRepository } from "@services/mongoose" - -import { randomUserId, randomPhone } from "test/galoy/helpers" - -const users = UsersRepository() - -describe("Testing Users Repository", () => { - it("return default value if userId doesn't exist", async () => { - const userId = randomUserId() - - const user = await users.findById(userId) - if (user instanceof Error) throw user - expect(user.id).toBe(userId) - }) - - it("can create entity with update", async () => { - const userId = randomUserId() - - const user = await users.update({ id: userId }) - if (user instanceof Error) throw user - expect(user.id).toBe(userId) - - const user2 = await users.findById(userId) - if (user2 instanceof Error) throw user - expect(user2.id).toBe(userId) - }) - - it("can't create 2 entities with same phone", async () => { - const userId1 = randomUserId() - const userId2 = randomUserId() - const phone = randomPhone() - - const user = await users.update({ id: userId1, phone }) - if (user instanceof Error) throw user - expect(user.id).toBe(userId1) - - const user2 = await users.update({ id: userId2, phone }) - expect(user2).toBeInstanceOf(DuplicateKeyForPersistError) - }) - - it("updating one field doesn't change the other fields", async () => { - const userId = randomUserId() - - const deviceTokens = ["token"] as DeviceToken[] - - const user0 = await users.findById(userId) - if (user0 instanceof Error) throw user0 - expect(user0.id).toBe(userId) - - const user = await users.update({ id: userId, deviceTokens }) - if (user instanceof Error) throw user - expect(user.deviceTokens).toStrictEqual(deviceTokens) - expect(user.id).toBe(userId) - - expect(user.language).toBe(user0.language) - expect(user.phoneMetadata).toBe(user0.phoneMetadata) - - const user2 = await users.update({ ...user, language: "es" }) - if (user2 instanceof Error) throw user2 - expect(user2.deviceTokens).toStrictEqual(deviceTokens) - expect(user2.language).toBe("es") - }) -}) diff --git a/test/galoy/legacy-integration/services/mongoose/wallet-invoices.spec.ts b/test/galoy/legacy-integration/services/mongoose/wallet-invoices.spec.ts deleted file mode 100644 index 403fa5e1b..000000000 --- a/test/galoy/legacy-integration/services/mongoose/wallet-invoices.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import crypto from "crypto" - -import { WalletCurrency } from "@domain/shared" -import { getSecretAndPaymentHash } from "@domain/bitcoin/lightning" -import { WalletInvoicesRepository } from "@services/mongoose" - -import { createUserAndWalletFromPhone, randomPhone } from "test/galoy/helpers" - -const phoneB = randomPhone() - -beforeAll(async () => { - await createUserAndWalletFromPhone(phoneB) -}) - -const createTestWalletInvoice = (): WalletInvoice => { - return { - ...getSecretAndPaymentHash(), - selfGenerated: false, - pubkey: "pubkey" as Pubkey, - paid: false, - recipientWalletDescriptor: { - currency: WalletCurrency.Btc, - id: crypto.randomUUID() as WalletId, - }, - usdAmount: { - currency: WalletCurrency.Usd, - amount: 10n, - }, - createdAt: new Date(), - } -} - -describe("WalletInvoices", () => { - it("persists and finds an invoice", async () => { - const repo = WalletInvoicesRepository() - const invoiceToPersist = createTestWalletInvoice() - const persistResult = await repo.persistNew(invoiceToPersist) - expect(persistResult).not.toBeInstanceOf(Error) - - const { paymentHash } = persistResult as WalletInvoice - const lookedUpInvoice = await repo.findByPaymentHash(paymentHash) - if (lookedUpInvoice instanceof Error) throw lookedUpInvoice - - const dateDifference = Math.abs( - lookedUpInvoice.createdAt.getTime() - invoiceToPersist.createdAt.getTime(), - ) - expect(dateDifference).toBeLessThanOrEqual(10) // 10ms - - lookedUpInvoice.createdAt = invoiceToPersist.createdAt = new Date() - expect(lookedUpInvoice).toEqual(invoiceToPersist) - }) - - it("updates an invoice", async () => { - const repo = WalletInvoicesRepository() - const invoiceToPersist = createTestWalletInvoice() - const persistResult = await repo.persistNew(invoiceToPersist) - expect(persistResult).not.toBeInstanceOf(Error) - - const invoiceToUpdate = persistResult as WalletInvoice - const updatedResult = await repo.markAsPaid(invoiceToUpdate.paymentHash) - expect(updatedResult).not.toBeInstanceOf(Error) - expect(updatedResult).toHaveProperty("paid", true) - - const { paymentHash } = updatedResult as WalletInvoice - const lookedUpInvoice = await repo.findByPaymentHash(paymentHash) - expect(lookedUpInvoice).not.toBeInstanceOf(Error) - expect(lookedUpInvoice).toEqual(updatedResult) - expect(lookedUpInvoice).toHaveProperty("paid", true) - }) - - it("deletes an invoice by hash", async () => { - const repo = WalletInvoicesRepository() - const invoiceToPersist = createTestWalletInvoice() - const persistResult = await repo.persistNew(invoiceToPersist) - expect(persistResult).not.toBeInstanceOf(Error) - - const { paymentHash } = persistResult as WalletInvoice - const isDeleted = await repo.deleteByPaymentHash(paymentHash) - expect(isDeleted).not.toBeInstanceOf(Error) - expect(isDeleted).toEqual(true) - }) -}) diff --git a/test/galoy/legacy-integration/services/mongoose/wallet-onchain-pending-receive.spec.ts b/test/galoy/legacy-integration/services/mongoose/wallet-onchain-pending-receive.spec.ts deleted file mode 100644 index 3dd6dccad..000000000 --- a/test/galoy/legacy-integration/services/mongoose/wallet-onchain-pending-receive.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { UsdDisplayCurrency } from "@domain/fiat" -import { WalletCurrency } from "@domain/shared" -import { - CouldNotFindWalletOnChainPendingReceiveError, - UnknownRepositoryError, -} from "@domain/errors" - -import { WalletOnChainPendingReceiveRepository } from "@services/mongoose" -import { WalletOnChainPendingReceive } from "@services/mongoose/schema" - -import { generateHash } from "test/galoy/helpers" - -// TODO: Remove this when we unify amounts in PartialBaseWalletTransaction and -// BaseWalletTransaction types -const translatePendingToSettledTx = (pendingTx) => ({ - ...pendingTx, - settlementAmount: Number(pendingTx.settlementAmount.amount), - settlementFee: Number(pendingTx.settlementFee.amount), - settlementDisplayAmount: pendingTx.settlementDisplayAmount.displayInMajor, - settlementDisplayFee: pendingTx.settlementDisplayFee.displayInMajor, -}) - -describe("WalletOnChainPendingReceiveRepository", () => { - const repo = WalletOnChainPendingReceiveRepository() - const walletId = "wallet1" as WalletId - const newPendingIncomingTransaction: PersistWalletOnChainPendingReceiveArgs = { - walletId, - initiationVia: { - type: "onchain", - address: "address1" as OnChainAddress, - }, - settlementVia: { - type: "onchain", - transactionHash: "txHash1" as OnChainTxHash, - vout: 1 as OnChainTxVout, - }, - settlementAmount: { amount: 10000n, currency: WalletCurrency.Btc }, - settlementFee: { amount: 2000n, currency: WalletCurrency.Btc }, - settlementCurrency: WalletCurrency.Btc, - settlementDisplayAmount: { - amountInMinor: 100n, - currency: UsdDisplayCurrency, - displayInMajor: "1.00", - }, - settlementDisplayFee: { - amountInMinor: 2n, - currency: UsdDisplayCurrency, - displayInMajor: "0.02", - }, - settlementDisplayPrice: { - base: 27454545454n, - offset: 12n, - displayCurrency: UsdDisplayCurrency, - walletCurrency: WalletCurrency.Btc, - }, - createdAt: new Date(), - } - - describe("persistNew", () => { - it("should persist a new pending tx and return it", async () => { - const newPendingTx = { - ...newPendingIncomingTransaction, - settlementVia: { - ...newPendingIncomingTransaction.settlementVia, - transactionHash: generateHash() as OnChainTxHash, - }, - } - - const result = await repo.persistNew(newPendingTx) - if (result instanceof Error) throw result - const translatedPendingTx = translatePendingToSettledTx(newPendingTx) - - expect(result).toMatchObject(translatedPendingTx) - expect(result.id).toBeTruthy() - expect(result.createdAt).toBeInstanceOf(Date) - - const savedRecords = await repo.listByWalletIds({ walletIds: [walletId] }) - if (savedRecords instanceof Error) throw savedRecords - expect(savedRecords.length).toBe(1) - expect(savedRecords[0]).toMatchObject(translatedPendingTx) - }) - - it("should return UnknownRepositoryError when an error occurs during persistence", async () => { - jest - .spyOn(WalletOnChainPendingReceive.prototype, "save") - .mockRejectedValueOnce(new Error("Test error")) - const result = await repo.persistNew(newPendingIncomingTransaction) - - expect(result).toBeInstanceOf(UnknownRepositoryError) - }) - }) - - describe("listByAddresses", () => { - const walletId = "walletlListByAddresses1" as WalletId - it("should return a list of PendingIncomingOnChainTransactions for given wallet", async () => { - const newPendingIncomingTransaction1: PersistWalletOnChainPendingReceiveArgs = { - ...newPendingIncomingTransaction, - walletId, - settlementVia: { - ...newPendingIncomingTransaction.settlementVia, - transactionHash: generateHash() as OnChainTxHash, - }, - } - const newPendingIncomingTransaction2: PersistWalletOnChainPendingReceiveArgs = { - ...newPendingIncomingTransaction, - walletId, - initiationVia: { - type: "onchain", - address: "address2" as OnChainAddress, - }, - settlementVia: { - type: "onchain", - transactionHash: generateHash() as OnChainTxHash, - vout: 2 as OnChainTxVout, - }, - createdAt: new Date(), - } - - const result1 = await repo.persistNew(newPendingIncomingTransaction1) - if (result1 instanceof Error) throw result1 - const result2 = await repo.persistNew(newPendingIncomingTransaction2) - if (result2 instanceof Error) throw result2 - - const results = await repo.listByWalletIds({ walletIds: [walletId] }) - if (results instanceof Error) throw results - - expect(results.length).toBe(2) - expect(results).toEqual( - expect.arrayContaining([ - expect.objectContaining( - translatePendingToSettledTx(newPendingIncomingTransaction1), - ), - expect.objectContaining( - translatePendingToSettledTx(newPendingIncomingTransaction2), - ), - ]), - ) - }) - - it("should return CouldNotFindWalletOnChainPendingReceiveError when not found for the given wallet", async () => { - const results = await repo.listByWalletIds({ - walletIds: ["walletWithoutTxs" as WalletId], - }) - expect(results).toBeInstanceOf(CouldNotFindWalletOnChainPendingReceiveError) - }) - }) - - describe("remove", () => { - const walletId = "walletlRemove1" as WalletId - it("should remove the pending transaction and return true", async () => { - const newPendingIncomingTransaction1: PersistWalletOnChainPendingReceiveArgs = { - ...newPendingIncomingTransaction, - walletId, - settlementVia: { - ...newPendingIncomingTransaction.settlementVia, - transactionHash: generateHash() as OnChainTxHash, - }, - } - const result = await repo.persistNew(newPendingIncomingTransaction1) - if (result instanceof Error) throw result - - const removeResult = await repo.remove({ - walletId, - transactionHash: result.settlementVia.transactionHash, - vout: 1, - }) - if (removeResult instanceof Error) throw removeResult - - expect(removeResult).toBe(true) - - const listResult = await repo.listByWalletIds({ walletIds: [walletId] }) - expect(listResult).toBeInstanceOf(CouldNotFindWalletOnChainPendingReceiveError) - }) - - it("should return CouldNotFindWalletOnChainPendingReceiveError if the transaction to be removed is not found", async () => { - const result = await repo.remove({ - walletId, - transactionHash: "nonexistent_txHash" as OnChainTxHash, - vout: 1, - }) - - expect(result).toBeInstanceOf(CouldNotFindWalletOnChainPendingReceiveError) - }) - - it("should return UnknownRepositoryError when an error occurs during the removal", async () => { - const newPendingIncomingTransaction1: PersistWalletOnChainPendingReceiveArgs = { - ...newPendingIncomingTransaction, - walletId, - settlementVia: { - ...newPendingIncomingTransaction.settlementVia, - transactionHash: generateHash() as OnChainTxHash, - }, - } - - const result = await repo.persistNew(newPendingIncomingTransaction1) - if (result instanceof Error) throw result - - jest - .spyOn(WalletOnChainPendingReceive, "deleteOne") - .mockRejectedValueOnce(new Error("Test error")) - - const removeResult = await repo.remove({ - walletId, - transactionHash: result.settlementVia.transactionHash, - vout: 1, - }) - expect(removeResult).toBeInstanceOf(UnknownRepositoryError) - }) - }) -}) diff --git a/test/galoy/mocks/index.ts b/test/galoy/mocks/index.ts index d9d61a456..4892f31bd 100644 --- a/test/galoy/mocks/index.ts +++ b/test/galoy/mocks/index.ts @@ -1,4 +1,4 @@ -import SendToAddressV2 from "./ibex/send-to-address-v2" +import SendToAddressV2 from "../../flash/mocks/ibex/send-to-address-v2" export default { ibex: { diff --git a/test/galoy/unit/app/auth/auth-test-checker.ts b/test/galoy/unit/app/auth/auth-test-checker.ts deleted file mode 100644 index 661c92dc9..000000000 --- a/test/galoy/unit/app/auth/auth-test-checker.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getTestAccounts } from "@config" -import { TestAccountsChecker } from "@domain/accounts/test-accounts-checker" - -describe("test-accounts", () => { - const testAccounts = getTestAccounts() - - it("valid user account", () => - expect( - TestAccountsChecker(testAccounts).isPhoneAndCodeValid({ - code: testAccounts[0].code, - phone: testAccounts[0].phone, - }), - ).toBeTruthy()) - - it("mix (invalid) user account", () => - expect( - TestAccountsChecker(testAccounts).isPhoneAndCodeValid({ - code: testAccounts[1].code, - phone: testAccounts[0].phone, - }), - ).toBeFalsy()) - - it("wrong phone", () => - expect( - TestAccountsChecker(testAccounts).isPhoneAndCodeValid({ - code: testAccounts[1].code, - phone: "+19999999999" as PhoneNumber, - }), - ).toBeFalsy()) - - it("empty code", () => - expect( - TestAccountsChecker(testAccounts).isPhoneAndCodeValid({ - code: "" as PhoneCode, - phone: "+19999999999" as PhoneNumber, - }), - ).toBeFalsy()) -}) diff --git a/test/galoy/unit/app/payments/translations.spec.ts b/test/galoy/unit/app/payments/translations.spec.ts deleted file mode 100644 index d0124f319..000000000 --- a/test/galoy/unit/app/payments/translations.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { toCents } from "@domain/fiat" -import { LedgerTransactionType } from "@domain/ledger" -import { - MissingPropsInTransactionForPaymentFlowError, - NonLnPaymentTransactionForPaymentFlowError, -} from "@domain/payments" -import { WalletCurrency } from "@domain/shared" -import { PaymentInitiationMethod, SettlementMethod } from "@domain/wallets" - -import { PaymentFlowFromLedgerTransaction } from "@app/payments/translations" - -describe("PaymentFlowFromLedgerTransaction", () => { - const satsAmount = toSats(20_000) - const centsAmount = toCents(1_000) - const satsFee = toSats(400) - const centsFee = toCents(20) - - const timestamp = new Date() - - const senderAccountId = "accountId" as AccountId - - const ledgerTxnBase = { - walletId: "walletId" as WalletId, - paymentHash: "paymentHash" as PaymentHash, - satsAmount, - centsAmount, - satsFee, - centsFee, - timestamp, - type: LedgerTransactionType.Payment, - } - - const expectedPaymentFlowStateBase = { - senderWalletId: "walletId" as WalletId, - settlementMethod: SettlementMethod.Lightning, - paymentInitiationMethod: PaymentInitiationMethod.Lightning, - - paymentHash: "paymentHash" as PaymentHash, - descriptionFromInvoice: "", - createdAt: timestamp, - paymentSentAndPending: true, - - btcPaymentAmount: { - amount: BigInt(satsAmount), - currency: WalletCurrency.Btc, - }, - usdPaymentAmount: { - amount: BigInt(centsAmount), - currency: WalletCurrency.Usd, - }, - - btcProtocolAndBankFee: { - amount: BigInt(satsFee), - currency: WalletCurrency.Btc, - }, - usdProtocolAndBankFee: { - amount: BigInt(centsFee), - currency: WalletCurrency.Usd, - }, - } - - it("builds correct PaymentFlow from btc transaction", () => { - const ledgerTxn = { - ...ledgerTxnBase, - currency: WalletCurrency.Btc, - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).not.toBeInstanceOf(Error) - - const expectedPaymentFlowState = { - ...expectedPaymentFlowStateBase, - senderWalletCurrency: WalletCurrency.Btc, - inputAmount: BigInt(satsAmount), - } - expect(paymentFlow).toEqual(expect.objectContaining(expectedPaymentFlowState)) - }) - - it("builds correct PaymentFlow from usd transaction", () => { - const ledgerTxn = { - ...ledgerTxnBase, - currency: WalletCurrency.Usd, - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).not.toBeInstanceOf(Error) - - const expectedPaymentFlowState = { - ...expectedPaymentFlowStateBase, - senderWalletCurrency: WalletCurrency.Usd, - inputAmount: BigInt(centsAmount), - } - expect(paymentFlow).toEqual(expect.objectContaining(expectedPaymentFlowState)) - }) - - it("handles zero fee btc transaction", () => { - const ledgerTxn = { - ...ledgerTxnBase, - currency: WalletCurrency.Btc, - satsFee: toSats(0), - centsFee: toCents(0), - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).not.toBeInstanceOf(Error) - - const expectedPaymentFlowState = { - ...expectedPaymentFlowStateBase, - senderWalletCurrency: WalletCurrency.Btc, - inputAmount: BigInt(satsAmount), - btcProtocolAndBankFee: { - amount: BigInt(0), - currency: WalletCurrency.Btc, - }, - usdProtocolAndBankFee: { - amount: BigInt(0), - currency: WalletCurrency.Usd, - }, - } - expect(paymentFlow).toEqual(expect.objectContaining(expectedPaymentFlowState)) - }) - - it("handles zero fee usd transaction", () => { - const ledgerTxn = { - ...ledgerTxnBase, - currency: WalletCurrency.Usd, - satsFee: toSats(0), - centsFee: toCents(0), - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).not.toBeInstanceOf(Error) - - const expectedPaymentFlowState = { - ...expectedPaymentFlowStateBase, - senderWalletCurrency: WalletCurrency.Usd, - inputAmount: BigInt(centsAmount), - btcProtocolAndBankFee: { - amount: BigInt(0), - currency: WalletCurrency.Btc, - }, - usdProtocolAndBankFee: { - amount: BigInt(0), - currency: WalletCurrency.Usd, - }, - } - expect(paymentFlow).toEqual(expect.objectContaining(expectedPaymentFlowState)) - }) - - it("returns error for transaction with missing walletId", () => { - const ledgerTxn = { - ...ledgerTxnBase, - walletId: undefined, - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).toBeInstanceOf(MissingPropsInTransactionForPaymentFlowError) - }) - - it("returns error for transaction with wrong type", () => { - const ledgerTxn = { - ...ledgerTxnBase, - type: LedgerTransactionType.LnIntraLedger, - } as LedgerTransaction - const paymentFlow = PaymentFlowFromLedgerTransaction({ ledgerTxn, senderAccountId }) - expect(paymentFlow).toBeInstanceOf(NonLnPaymentTransactionForPaymentFlowError) - }) -}) diff --git a/test/galoy/unit/app/prices/get-current-price.spec.ts b/test/galoy/unit/app/prices/get-current-price.spec.ts deleted file mode 100644 index 787e7955b..000000000 --- a/test/galoy/unit/app/prices/get-current-price.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { CacheKeys } from "@domain/cache" -import { UsdDisplayCurrency } from "@domain/fiat" -import { PriceNotAvailableError } from "@domain/price" - -import * as PriceServiceImpl from "@services/price" -import { LocalCacheService } from "@services/cache/local-cache" -import { getCurrentSatPrice, getCurrentUsdCentPrice } from "@app/prices" - -jest.mock("@services/tracing", () => ({ - wrapAsyncFunctionsToRunInSpan: ({ fns }) => fns, -})) - -const EUR = "EUR" as DisplayCurrency - -beforeEach(async () => { - await LocalCacheService().clear({ key: CacheKeys.CurrentSatPrice }) - await LocalCacheService().clear({ key: CacheKeys.CurrentUsdCentPrice }) -}) - -afterEach(() => { - jest.resetAllMocks() -}) - -describe("Prices", () => { - describe("getCurrentSatPrice", () => { - it("returns cached price when realtime fails", async () => { - jest - .spyOn(PriceServiceImpl, "PriceService") - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - getSatRealTimePrice: () => - Promise.resolve({ - timestamp: new Date(), - price: 0.05, - currency: UsdDisplayCurrency, - }), - listCurrencies: jest.fn(), - })) - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - getSatRealTimePrice: () => Promise.resolve(new PriceNotAvailableError()), - listCurrencies: jest.fn(), - })) - - let satPrice = await getCurrentSatPrice({ currency: UsdDisplayCurrency }) - if (satPrice instanceof Error) throw satPrice - expect(satPrice.price).toEqual(0.05) - - satPrice = await getCurrentSatPrice({ currency: UsdDisplayCurrency }) - if (satPrice instanceof Error) throw satPrice - expect(satPrice.price).toEqual(0.05) - }) - - it("fails when realtime fails and cache is empty", async () => { - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - getSatRealTimePrice: () => Promise.resolve(new PriceNotAvailableError()), - listCurrencies: jest.fn(), - })) - - const price = await getCurrentSatPrice({ currency: UsdDisplayCurrency }) - expect(price).toBeInstanceOf(PriceNotAvailableError) - }) - }) - - describe("getCurrentUsdCentPrice", () => { - it("returns cached price when realtime fails", async () => { - jest - .spyOn(PriceServiceImpl, "PriceService") - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: () => - Promise.resolve({ - timestamp: new Date(), - price: 0.93, - currency: EUR, - }), - listCurrencies: jest.fn(), - })) - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: () => Promise.resolve(new PriceNotAvailableError()), - listCurrencies: jest.fn(), - })) - - let satPrice = await getCurrentUsdCentPrice({ currency: EUR }) - if (satPrice instanceof Error) throw satPrice - expect(satPrice.price).toEqual(0.93) - - satPrice = await getCurrentUsdCentPrice({ currency: EUR }) - if (satPrice instanceof Error) throw satPrice - expect(satPrice.price).toEqual(0.93) - }) - - it("fails when realtime fails and cache is empty", async () => { - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: () => Promise.resolve(new PriceNotAvailableError()), - listCurrencies: jest.fn(), - })) - - const price = await getCurrentUsdCentPrice({ currency: EUR }) - expect(price).toBeInstanceOf(PriceNotAvailableError) - }) - }) -}) diff --git a/test/galoy/unit/app/prices/get-price-history.spec.ts b/test/galoy/unit/app/prices/get-price-history.spec.ts deleted file mode 100644 index 3da1a4441..000000000 --- a/test/galoy/unit/app/prices/get-price-history.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - PriceHistoryNotAvailableError, - PriceInterval, - PriceRange, - UnknownPriceServiceError, -} from "@domain/price" -import { CacheKeys } from "@domain/cache" -import * as PriceServiceImpl from "@services/price" -import { LocalCacheService } from "@services/cache/local-cache" - -import { getPriceHistory } from "@app/prices" - -import { generateSatoshiPriceHistory } from "test/galoy/helpers/price" - -beforeEach(async () => { - await LocalCacheService().clear({ - key: `${CacheKeys.PriceHistory}:${PriceRange.OneDay}-${PriceInterval.OneHour}`, - }) -}) - -jest.mock("@services/tracing", () => ({ - wrapAsyncFunctionsToRunInSpan: ({ fns }) => fns, -})) - -afterAll(() => { - jest.resetAllMocks() -}) - -describe("Prices", () => { - describe("getPriceHistory", () => { - it(`returns cached history prices`, async () => { - const range = PriceRange.OneDay - const interval = PriceInterval.OneHour - - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: () => - Promise.resolve( - generateSatoshiPriceHistory(1, 50000) - .map((p) => ({ - date: new Date(p.date), - price: p.price as DisplayCurrencyPerSat, - })) - .slice(-24), - ), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: jest.fn(), - })) - - const prices = await getPriceHistory({ range, interval }) - if (prices instanceof Error) throw prices - - expect(prices.length).toBe(24) - - expect(prices).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - date: expect.any(Date), - price: expect.any(Number), - }), - ]), - ) - - const cachedPrices = await getPriceHistory({ range, interval }) - expect(cachedPrices).toEqual(prices) - }) - - it("fails when cache is empty and db fails", async () => { - const range = PriceRange.OneDay - const interval = PriceInterval.OneHour - - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: () => Promise.resolve(new UnknownPriceServiceError()), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: jest.fn(), - })) - - const prices = await getPriceHistory({ range, interval }) - expect(prices).toBeInstanceOf(UnknownPriceServiceError) - }) - - it("fails when cache and db history are empty", async () => { - const range = PriceRange.OneDay - const interval = PriceInterval.OneHour - - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: () => Promise.resolve([]), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: jest.fn(), - })) - - const prices = await getPriceHistory({ range, interval }) - expect(prices).toBeInstanceOf(PriceHistoryNotAvailableError) - }) - }) -}) diff --git a/test/galoy/unit/app/prices/list-currencies.spec.ts b/test/galoy/unit/app/prices/list-currencies.spec.ts deleted file mode 100644 index 31157b5e0..000000000 --- a/test/galoy/unit/app/prices/list-currencies.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { CacheKeys } from "@domain/cache" -import * as PriceServiceImpl from "@services/price" -import { LocalCacheService } from "@services/cache/local-cache" -import { PriceCurrenciesNotAvailableError } from "@domain/price" -import { listCurrencies } from "@app/prices" - -jest.mock("@services/tracing", () => ({ - wrapAsyncFunctionsToRunInSpan: ({ fns }) => fns, -})) - -beforeEach(async () => { - await LocalCacheService().clear({ key: CacheKeys.PriceCurrencies }) -}) - -afterEach(() => { - jest.resetAllMocks() -}) - -describe("Prices", () => { - describe("listCurrencies", () => { - it("returns cached currencies", async () => { - jest - .spyOn(PriceServiceImpl, "PriceService") - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: () => - Promise.resolve([ - { - code: "USD", - symbol: "$", - name: "US Dollar", - flag: "🇺🇸", - fractionDigits: 2, - }, - ]), - })) - .mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: () => Promise.resolve(new PriceCurrenciesNotAvailableError()), - })) - - const currencies = await listCurrencies() - expect(currencies).not.toBeInstanceOf(Error) - - if (currencies instanceof Error) throw currencies - expect(currencies.length).toEqual(1) - expect(currencies).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: expect.any(String), - symbol: expect.any(String), - name: expect.any(String), - flag: expect.any(String), - fractionDigits: expect.any(Number), - }), - ]), - ) - - const cachedCurrencies = await listCurrencies() - expect(cachedCurrencies).toEqual(currencies) - }) - - it("fails when listCurrencies fails and cache is empty", async () => { - jest.spyOn(PriceServiceImpl, "PriceService").mockImplementationOnce(() => ({ - listHistory: jest.fn(), - getSatRealTimePrice: jest.fn(), - getUsdCentRealTimePrice: jest.fn(), - listCurrencies: () => Promise.resolve(new PriceCurrenciesNotAvailableError()), - })) - - const currencies = await listCurrencies() - expect(currencies).toBeInstanceOf(PriceCurrenciesNotAvailableError) - }) - }) -}) diff --git a/test/galoy/unit/app/wallets/get-transactions-for-wallets.spec.ts b/test/galoy/unit/app/wallets/get-transactions-for-wallets.spec.ts deleted file mode 100644 index a5ec2c2cd..000000000 --- a/test/galoy/unit/app/wallets/get-transactions-for-wallets.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { toWalletTransactions } from "@app/wallets" - -const ibex_data = [ - { - "id": "f2fa0473-43b4-4101-8e19-11f1caaeb011", - "createdAt": "2024-01-31T17:27:20.718984Z", - "settledAt": "2024-01-31T17:27:21.422794Z", - "accountId": "e24b85d1-9f61-47da-acb9-fe9d069de2fc", - "amount": 0.045584045584, - "networkFee": 0.000898969976, - "onChainSendFee": 0, - "exchangeRateCurrencySats": 2281.5, - "currencyId": 3, - "transactionTypeId": 2 - }, - { - "id": "d3a61722-c212-4232-9af0-6c6360ee3ad9", - "createdAt": "2024-01-31T17:24:23.446563Z", - "settledAt": "2024-01-31T17:24:44.423267Z", - "accountId": "e24b85d1-9f61-47da-acb9-fe9d069de2fc", - "amount": 0.100425509811, - "networkFee": 0, - "onChainSendFee": 0, - "exchangeRateCurrencySats": 2310.17, - "currencyId": 3, - "transactionTypeId": 1 - } -] - -describe("Test transformation of IbexResponse to WalletTransaction[]", () => { - it("should set the settlementAmount to negative on send", async () => { - const result: WalletTransaction[] = toWalletTransactions(ibex_data) - - expect(result[0].settlementAmount).toEqual(-0.045584045584) - expect(result[1].settlementAmount).toEqual(0.100425509811) - }) -}) \ No newline at end of file diff --git a/test/galoy/unit/config.spec.ts b/test/galoy/unit/config.spec.ts deleted file mode 100644 index 09c75047a..000000000 --- a/test/galoy/unit/config.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import fs from "fs" - -import { configSchema, getAccountLimits, yamlConfig } from "@config" -import { toCents } from "@domain/fiat" -import Ajv from "ajv" -import yaml from "js-yaml" - -import mergeWith from "lodash.mergewith" - -const ajv = new Ajv() -let validate - -const merge = (defaultConfig: unknown, customConfig: unknown) => - mergeWith(defaultConfig, customConfig, (a, b) => (Array.isArray(b) ? b : undefined)) - -const accountLimits = { - withdrawal: { - level: { - 1: toCents(2_000_000), - 2: toCents(100_000_000), - }, - }, - intraLedger: { - level: { - 1: toCents(5_000_000), - 2: toCents(100_000_000), - }, - }, - tradeIntraAccount: { - level: { - 1: toCents(5_000_000), - 2: toCents(100_000_000), - }, - }, -} - -describe("config.ts", () => { - describe("yml config validation", () => { - beforeAll(() => { - validate = ajv.compile(configSchema) - }) - - it("passes validation with valid config", () => { - const valid = validate(yamlConfig) - expect(valid).toBeTruthy() - - const contentOrg = fs.readFileSync("./galoy.yaml", "utf8") - - fs.writeFileSync( - "./galoy.yaml", - yaml.dump(yamlConfig, { quotingType: '"' }), - "utf8", - ) - - const contentNew = fs.readFileSync("./galoy.yaml", "utf8") - - expect(contentOrg).toEqual(contentNew) - }) - - it("passes with custom yaml", () => { - const freshYamlConfig = JSON.parse(JSON.stringify(yamlConfig)) - const customYamlConfig = { - test_accounts: [ - { - phone: "+50365055543", - code: "182731", - }, - ], - } - - const updatedYamlConfig = merge(freshYamlConfig, customYamlConfig) - const valid = validate(updatedYamlConfig) - expect(valid).toBeTruthy() - }) - - it("fails with incomplete custom yaml", () => { - const freshYamlConfig = JSON.parse(JSON.stringify(yamlConfig)) - const customYamlConfig = { - test_accounts: [ - { - phone: "+50365055543", - }, - ], - } - - const updatedYamlConfig = merge(freshYamlConfig, customYamlConfig) - const valid = validate(updatedYamlConfig) - expect(valid).toBeFalsy() - }) - - it("fails validation missing required property", () => { - const clonedConfig = JSON.parse(JSON.stringify(yamlConfig)) - delete clonedConfig.buildVersion - const valid = validate(clonedConfig) - expect(valid).toBeFalsy() - }) - - it("fails validation missing conditional required", () => { - const clonedConfig = JSON.parse(JSON.stringify(yamlConfig)) - clonedConfig.cronConfig.swapEnabled = true - delete clonedConfig.cronConfig.swapEnabled - - const valid = validate(clonedConfig) - expect(valid).toBeFalsy() - }) - - it("fails validation with additional property", () => { - const clonedConfig = JSON.parse(JSON.stringify(yamlConfig)) - clonedConfig.newProperty = "NEW PROPERTY" - const valid = validate(clonedConfig) - expect(valid).toBeFalsy() - }) - - it("fails validation with wrong type", () => { - const clonedConfig = JSON.parse(JSON.stringify(yamlConfig)) - clonedConfig.buildVersion.android.minBuildNumber = "WRONG TYPE" - const valid = validate(clonedConfig) - expect(valid).toBeFalsy() - }) - }) - - describe("generates expected constants from a limits config object", () => { - it("selects user limits for level 1", () => { - const userLimits = getAccountLimits({ level: 1, accountLimits }) - expect(userLimits.tradeIntraAccountLimit).toEqual(5_000_000) - expect(userLimits.intraLedgerLimit).toEqual(5_000_000) - expect(userLimits.withdrawalLimit).toEqual(2_000_000) - }) - - it("selects user limits for level 2", () => { - const userLimits = getAccountLimits({ level: 2, accountLimits }) - expect(userLimits.tradeIntraAccountLimit).toEqual(100_000_000) - expect(userLimits.intraLedgerLimit).toEqual(100_000_000) - expect(userLimits.withdrawalLimit).toEqual(100_000_000) - }) - }) -}) diff --git a/test/galoy/unit/domain/accounts-ips/index.spec.ts b/test/galoy/unit/domain/accounts-ips/index.spec.ts deleted file mode 100644 index e00fc9951..000000000 --- a/test/galoy/unit/domain/accounts-ips/index.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isPrivateIp } from "@domain/accounts-ips" - -describe("isPrivateIp", () => { - it("check ipv6 private address validation", () => { - const localhost = "::ffff:127.0.0.1" - expect(isPrivateIp(localhost)).toBeTruthy() - }) - - it("check ipv4 localhost address validation", () => { - const localhost = "127.0.0.1" - expect(isPrivateIp(localhost)).toBeTruthy() - }) - - it("check ipv4 private address validation", () => { - const localhost = "192.168.1.1" - expect(isPrivateIp(localhost)).toBeTruthy() - }) - - it("check non private IP", () => { - const localhost = "152.231.190.229" - expect(isPrivateIp(localhost)).toBeFalsy() - }) -}) diff --git a/test/galoy/unit/domain/accounts/limits-checker.spec.ts b/test/galoy/unit/domain/accounts/limits-checker.spec.ts deleted file mode 100644 index 71e66f28e..000000000 --- a/test/galoy/unit/domain/accounts/limits-checker.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { getAccountLimits } from "@config" -import { AccountLimitsChecker } from "@domain/accounts" -import { LimitsExceededError } from "@domain/errors" -import { WalletPriceRatio } from "@domain/payments" -import { - AmountCalculator, - paymentAmountFromNumber, - WalletCurrency, - ZERO_CENTS, -} from "@domain/shared" - -let usdPaymentAmount: UsdPaymentAmount -let limitsChecker: AccountLimitsChecker -let walletVolumeIntraledger: TxBaseVolumeAmount -let walletVolumeWithdrawal: TxBaseVolumeAmount -let priceRatio: WalletPriceRatio - -const calc = AmountCalculator() - -const ONE_CENT = { amount: 1n, currency: WalletCurrency.Usd } as UsdPaymentAmount - -beforeAll(async () => { - const priceRatioResult = WalletPriceRatio({ - usd: ONE_CENT, - btc: { amount: 50n, currency: WalletCurrency.Btc }, - }) - if (priceRatioResult instanceof Error) throw priceRatioResult - priceRatio = priceRatioResult - - const level: AccountLevel = 1 - const accountLimits = getAccountLimits({ level }) - - usdPaymentAmount = { - amount: 10_000n, - currency: WalletCurrency.Usd, - } - - limitsChecker = AccountLimitsChecker({ - accountLimits, - priceRatio, - }) - - const intraLedgerOutgoingBaseAmount = paymentAmountFromNumber({ - amount: accountLimits.intraLedgerLimit - Number(usdPaymentAmount.amount), - currency: WalletCurrency.Usd, - }) - if (intraLedgerOutgoingBaseAmount instanceof Error) throw intraLedgerOutgoingBaseAmount - walletVolumeIntraledger = { - outgoingBaseAmount: intraLedgerOutgoingBaseAmount, - incomingBaseAmount: ZERO_CENTS, - } - - const withdrawalOutgoingBaseAmount = paymentAmountFromNumber({ - amount: accountLimits.withdrawalLimit - Number(usdPaymentAmount.amount), - currency: WalletCurrency.Usd, - }) - if (withdrawalOutgoingBaseAmount instanceof Error) throw withdrawalOutgoingBaseAmount - walletVolumeWithdrawal = { - outgoingBaseAmount: withdrawalOutgoingBaseAmount, - incomingBaseAmount: ZERO_CENTS, - } -}) - -describe("LimitsChecker", () => { - it("passes for exact limit amount", async () => { - const intraledgerLimitCheck = await limitsChecker.checkIntraledger({ - amount: usdPaymentAmount, - walletVolumes: [walletVolumeIntraledger], - }) - expect(intraledgerLimitCheck).not.toBeInstanceOf(Error) - - const withdrawalLimitCheck = await limitsChecker.checkWithdrawal({ - amount: usdPaymentAmount, - walletVolumes: [walletVolumeWithdrawal], - }) - expect(withdrawalLimitCheck).not.toBeInstanceOf(Error) - }) - - it("passes for amount below limit", async () => { - const intraledgerLimitCheck = await limitsChecker.checkIntraledger({ - amount: calc.sub(usdPaymentAmount, ONE_CENT), - walletVolumes: [walletVolumeIntraledger], - }) - expect(intraledgerLimitCheck).not.toBeInstanceOf(Error) - - const withdrawalLimitCheck = await limitsChecker.checkWithdrawal({ - amount: calc.sub(usdPaymentAmount, ONE_CENT), - walletVolumes: [walletVolumeWithdrawal], - }) - expect(withdrawalLimitCheck).not.toBeInstanceOf(Error) - }) - - it("returns an error for exceeded intraledger amount", async () => { - const intraledgerLimitCheck = await limitsChecker.checkIntraledger({ - amount: calc.add(usdPaymentAmount, ONE_CENT), - walletVolumes: [walletVolumeIntraledger], - }) - expect(intraledgerLimitCheck).toBeInstanceOf(LimitsExceededError) - }) - - it("returns an error for exceeded withdrawal amount", async () => { - const withdrawalLimitCheck = await limitsChecker.checkWithdrawal({ - amount: calc.add(usdPaymentAmount, ONE_CENT), - walletVolumes: [walletVolumeWithdrawal], - }) - expect(withdrawalLimitCheck).toBeInstanceOf(LimitsExceededError) - }) -}) diff --git a/test/galoy/unit/domain/accounts/new-account.ts b/test/galoy/unit/domain/accounts/new-account.ts deleted file mode 100644 index e009f9851..000000000 --- a/test/galoy/unit/domain/accounts/new-account.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Account } from "@services/mongoose/schema" -import { getDefaultAccountsConfig } from "@config" - -describe("New Account", () => { - it("uses the default account status", async () => { - const account = new Account() - await account.save() - expect(account.statusHistory).toEqual({ - status: getDefaultAccountsConfig().initialStatus, - comment: "Initial Status", - }) - }) -}) diff --git a/test/galoy/unit/domain/authentication/index.spec.ts b/test/galoy/unit/domain/authentication/index.spec.ts deleted file mode 100644 index 02c86866c..000000000 --- a/test/galoy/unit/domain/authentication/index.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { getSupportedCountries } from "@domain/authentication" - -describe("getSupportedCountries", () => { - it("returns supported countries", () => { - const countries = getSupportedCountries({ - allCountries: ["CA", "US", "SV"] as CountryCode[], - unsupportedSmsCountries: ["CA", "SV"] as CountryCode[], - unsupportedWhatsAppCountries: ["US", "SV"] as CountryCode[], - }) - - expect(countries).toEqual([ - { - id: "CA", - supportedAuthChannels: ["whatsapp"], - }, - { - id: "US", - supportedAuthChannels: ["sms"], - }, - ]) - }) -}) diff --git a/test/galoy/unit/domain/authentication/registration-payload-validator.spec.ts b/test/galoy/unit/domain/authentication/registration-payload-validator.spec.ts deleted file mode 100644 index 12af6861d..000000000 --- a/test/galoy/unit/domain/authentication/registration-payload-validator.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - MissingRegistrationPayloadPropertiesError, - UnsupportedSchemaTypeError, -} from "@domain/authentication/errors" -import { RegistrationPayloadValidator } from "@domain/authentication/registration-payload-validator" -import { InvalidPhoneNumber, InvalidUserId } from "@domain/errors" - -import { SchemaIdType } from "@services/kratos" - -import { randomPhone, randomUserId } from "test/galoy/helpers/random" - -describe("RegistrationPayloadValidator", () => { - const validator = RegistrationPayloadValidator(SchemaIdType.PhoneNoPasswordV0) - - it("validates valid body", () => { - const rawUserId = randomUserId() - const rawPhone = randomPhone() - const expectedPayload: RegistrationPayload = { - userId: rawUserId as UserId, - phone: rawPhone, - phoneMetadata: undefined, - } - - const validated = validator.validate({ - identity_id: rawUserId, - phone: rawPhone, - schema_id: SchemaIdType.PhoneNoPasswordV0, - }) - - expect(validated).toStrictEqual(expectedPayload) - }) - - it("returns missing inputs error", () => { - const identity_id = "identity_id" - const phone = "phone" - const schema_id = "schema_id" - - const validatedEmptyBody = validator.validate({}) - expect(validatedEmptyBody).toBeInstanceOf(MissingRegistrationPayloadPropertiesError) - - const validatedMissingUserId = validator.validate({ phone, schema_id }) - expect(validatedMissingUserId).toBeInstanceOf( - MissingRegistrationPayloadPropertiesError, - ) - - const validatedMissingPhone = validator.validate({ identity_id, schema_id }) - expect(validatedMissingPhone).toBeInstanceOf( - MissingRegistrationPayloadPropertiesError, - ) - - const validatedMissingSchemaId = validator.validate({ identity_id, phone }) - expect(validatedMissingSchemaId).toBeInstanceOf( - MissingRegistrationPayloadPropertiesError, - ) - }) - - it("return invalid schema_id error", () => { - const invalidSchemaId = "invalid-schema-id" - const validatedBadSchemaId = validator.validate({ - identity_id: randomUserId(), - phone: randomPhone(), - schema_id: invalidSchemaId, - }) - expect(invalidSchemaId).not.toBe(SchemaIdType.PhoneNoPasswordV0) - expect(validatedBadSchemaId).toBeInstanceOf(UnsupportedSchemaTypeError) - }) - - it("return invalid identity_id error", () => { - const validatedBadUserId = validator.validate({ - identity_id: "invalid-user-id", - phone: randomPhone(), - schema_id: SchemaIdType.PhoneNoPasswordV0, - }) - expect(validatedBadUserId).toBeInstanceOf(InvalidUserId) - }) - - it("return invalid phone error", () => { - const validatedBadPhone = validator.validate({ - identity_id: randomUserId(), - phone: "invalid-phone", - schema_id: SchemaIdType.PhoneNoPasswordV0, - }) - expect(validatedBadPhone).toBeInstanceOf(InvalidPhoneNumber) - }) -}) diff --git a/test/galoy/unit/domain/authentication/secret-validator.spec.ts b/test/galoy/unit/domain/authentication/secret-validator.spec.ts deleted file mode 100644 index 2b1280fd6..000000000 --- a/test/galoy/unit/domain/authentication/secret-validator.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - InvalidSecretForAuthNCallbackError, - MissingSecretForAuthNCallbackError, -} from "@domain/authentication/errors" -import { CallbackSecretValidator } from "@domain/authentication/secret-validator" - -const DUMMY_CALLBACK_API_KEY = "dummy-callback-api-key" - -describe("CallbackSecretValidator", () => { - const validator = CallbackSecretValidator(DUMMY_CALLBACK_API_KEY) - - it("validates valid key", () => { - const validated = validator.authorize(DUMMY_CALLBACK_API_KEY) - expect(validated).toBe(true) - }) - - it("returns missing key error", () => { - const validatedEmptyString = validator.authorize("") - expect(validatedEmptyString).toBeInstanceOf(MissingSecretForAuthNCallbackError) - - const validatedUndefinedString = validator.authorize(undefined) - expect(validatedUndefinedString).toBeInstanceOf(MissingSecretForAuthNCallbackError) - }) - - it("return invalid key error", () => { - const rawKey = "invalid-key" - const validatedEmptyString = validator.authorize(rawKey) - expect(rawKey).not.toBe(DUMMY_CALLBACK_API_KEY) - expect(validatedEmptyString).toBeInstanceOf(InvalidSecretForAuthNCallbackError) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/checked-to-sats.spec.ts b/test/galoy/unit/domain/bitcoin/checked-to-sats.spec.ts deleted file mode 100644 index fe0c752b4..000000000 --- a/test/galoy/unit/domain/bitcoin/checked-to-sats.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { checkedToSats } from "@domain/bitcoin" -import { MAX_SATS } from "@domain/shared" - -describe("sats-amount-check", () => { - it("Positive amount value for satoshis passes", () => { - const sats = checkedToSats(100) - expect(sats).toEqual(100) - }) - - it("Zero amount value for satoshis fails", () => { - const sats = checkedToSats(0) - expect(sats).toBeInstanceOf(Error) - }) - - it("Negative amount value for satoshis fails", () => { - const sats = checkedToSats(-100) - expect(sats).toBeInstanceOf(Error) - }) - - it("Large amount value for satoshis fails", () => { - const sats = checkedToSats(Number(MAX_SATS.amount + 1n)) - expect(sats).toBeInstanceOf(Error) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/lightning/invoice-expiration.spec.ts b/test/galoy/unit/domain/bitcoin/lightning/invoice-expiration.spec.ts deleted file mode 100644 index 0ad37308f..000000000 --- a/test/galoy/unit/domain/bitcoin/lightning/invoice-expiration.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { SECS_PER_10_MINS, SECS_PER_DAY } from "@config" - -import { toSeconds } from "@domain/primitives" -import { WalletCurrency } from "@domain/shared" -import { invoiceExpirationForCurrency } from "@domain/bitcoin/lightning" - -describe("invoiceExpirationForCurrency", () => { - const BTC = WalletCurrency.Btc - const USD = WalletCurrency.Usd - const now = new Date("2000-01-01T00:00:00Z") - - it("should return expiration for BTC currency with default delay", () => { - const expectedExpiration = new Date("2000-01-02T00:00:00.000Z") - let expiresAt = invoiceExpirationForCurrency(BTC, now) - expect(expiresAt).toEqual(expectedExpiration) - - let delay = toSeconds(59) - expiresAt = invoiceExpirationForCurrency(BTC, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - - delay = toSeconds(0) - expiresAt = invoiceExpirationForCurrency(BTC, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - - delay = toSeconds(2 * SECS_PER_DAY) - expiresAt = invoiceExpirationForCurrency(BTC, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - }) - - it("should return expiration for USD currency with default delay", () => { - const expectedExpiration = new Date("2000-01-01T00:05:00.000Z") - let expiresAt = invoiceExpirationForCurrency(USD, now) - expect(expiresAt).toEqual(expectedExpiration) - - let delay = toSeconds(59) - expiresAt = invoiceExpirationForCurrency(USD, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - - delay = toSeconds(0) - expiresAt = invoiceExpirationForCurrency(USD, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - - delay = toSeconds(SECS_PER_10_MINS) - expiresAt = invoiceExpirationForCurrency(USD, now, delay) - expect(expiresAt).toEqual(expectedExpiration) - }) - - it("should return expiration for BTC currency with provided delay", () => { - const delay = toSeconds(30 * 60) - const currency = BTC - const expiration = invoiceExpirationForCurrency(currency, now, delay) - const expectedExpiration = new Date("2000-01-01T00:30:00Z") - expect(expiration).toEqual(expectedExpiration) - }) - - it("should return expiration for USD currency with provided delay", () => { - const delay = toSeconds(3 * 60) - const currency = USD - const expiration = invoiceExpirationForCurrency(currency, now, delay) - const expectedExpiration = new Date("2000-01-01T00:03:00Z") - expect(expiration).toEqual(expectedExpiration) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/lightning/ln-invoice.spec.ts b/test/galoy/unit/domain/bitcoin/lightning/ln-invoice.spec.ts deleted file mode 100644 index 2247805a5..000000000 --- a/test/galoy/unit/domain/bitcoin/lightning/ln-invoice.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { LnInvoiceDecodeError, decodeInvoice } from "@domain/bitcoin/lightning" -import { toSats } from "@domain/bitcoin" - -describe("decodeInvoice", () => { - const validBolt11Invoice = - "lnbc20u1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfppqw508d6qejxtdg4y5r3zarvary0c5xw7kxqrrsssp5m6kmam774klwlh4dhmhaatd7al02m0h0m6kmam774klwlh4dhmhs9qypqqqcqpf3cwux5979a8j28d4ydwahx00saa68wq3az7v9jdgzkghtxnkf3z5t7q5suyq2dl9tqwsap8j0wptc82cpyvey9gf6zyylzrm60qtcqsq7egtsq" as EncodedPaymentRequest - - it("returns an invoice", () => { - const invoice = decodeInvoice(validBolt11Invoice) - if (invoice instanceof Error) throw invoice - expect(invoice.paymentHash).toEqual( - "0001020304050607080900010203040506070809000102030405060708090102", - ) - expect(invoice.paymentSecret).toEqual( - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - ) - expect(invoice.paymentRequest).toEqual(validBolt11Invoice) - expect(invoice.amount).toEqual(toSats(2000)) - expect(invoice.destination).toEqual( - "03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad", - ) - expect(invoice.expiresAt).toEqual(new Date("2017-06-01T11:57:38.000Z")) - expect(invoice.isExpired).toBeTruthy() - expect(invoice.routeHints.length).toEqual(0) - expect(invoice.cltvDelta).toEqual(9) - }) - - it("returns a decode error", () => { - const result = decodeInvoice("bad input data" as EncodedPaymentRequest) - expect(result).toBeInstanceOf(LnInvoiceDecodeError) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/onchain/checked-to-on-chain-address.spec.ts b/test/galoy/unit/domain/bitcoin/onchain/checked-to-on-chain-address.spec.ts deleted file mode 100644 index 562393671..000000000 --- a/test/galoy/unit/domain/bitcoin/onchain/checked-to-on-chain-address.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { BtcNetwork } from "@domain/bitcoin" -import { checkedToOnChainAddress } from "@domain/bitcoin/onchain" -import { InvalidOnChainAddress } from "@domain/errors" - -const addresses = [ - { network: "mainnet", address: "17sELMebjQNf1k1CRucF67QAtegNsTjXUn" }, - { network: "mainnet", address: "3QcAYUrGoKv8tqg24VRB8TDUejmumLG8rL" }, - { network: "mainnet", address: "bc1q40aetdah8wcrsxvzl5qcft4svx696xsw0tnchd" }, - { - network: "mainnet", - address: "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcxswfvpysxf3qccfmv3", - }, - { network: "testnet", address: "mipcBbFg9gLiKh81Kj8taadgoZiY1ZJRfn" }, - { network: "testnet", address: "tb1qw508d6qejxtdg4y5r3aadgory0c5xw7kxpjzsx" }, - { - network: "testnet", - address: "tb1p84x2taadgovgnlpnxt9f39gm7r68gwtvllxqe5w2n5ru00s9aquslzggwq", - }, - { network: "testnet", address: "mipcBbFg9gLiKh81Kj8taadgoZiY1ZJRfn" }, - { network: "testnet", address: "tb1qw508d6qejxtdg4y5r3aadgory0c5xw7kxpjzsx" }, - { - network: "testnet", - address: "tb1p84x2taadgovgnlpnxt9f39gm7r68gwtvllxqe5w2n5ru00s9aquslzggwq", - }, - { network: "signet", address: "mipcBbFg9gLiKh81Kj8taadgoZiY1ZJRfn" }, - { network: "signet", address: "tb1qw508d6qejxtdg4y5r3aadgory0c5xw7kxpjzsx" }, - { - network: "signet", - address: "tb1p84x2taadgovgnlpnxt9f39gm7r68gwtvllxqe5w2n5ru00s9aquslzggwq", - }, - { network: "signet", address: "mipcBbFg9gLiKh81Kj8taadgoZiY1ZJRfn" }, - { network: "signet", address: "tb1qw508d6qejxtdg4y5r3aadgory0c5xw7kxpjzsx" }, - { - network: "signet", - address: "tb1p84x2taadgovgnlpnxt9f39gm7r68gwtvllxqe5w2n5ru00s9aquslzggwq", - }, - { network: "regtest", address: "bcrt1q6z64a43mjgkcq0ul2zaqusq3spghrlau9slefp" }, - { - network: "regtest", - address: "bcrt1q5n2k3frgpxces3aqusqfpqk4kksv0cz96pldxdwxrrw0d5ud5haqusx7zt", - }, -] - -describe("checkedToOnChainAddress", () => { - test.each(addresses)("$address is valid for $network", ({ network, address }) => { - const result = checkedToOnChainAddress({ - network: network as BtcNetwork, - value: address, - }) - expect(result).not.toBeInstanceOf(InvalidOnChainAddress) - expect(result).toBe(address) - }) - - test.each(addresses)( - "$address is invalid for other networks", - ({ network, address }) => { - let networksToCheck: Array = [ - BtcNetwork.testnet, - BtcNetwork.signet, - BtcNetwork.regtest, - ] - if (network === "testnet") - networksToCheck = [BtcNetwork.mainnet, BtcNetwork.regtest] - if (network === "signet") networksToCheck = [BtcNetwork.mainnet, BtcNetwork.regtest] - if (network === "regtest") - networksToCheck = [BtcNetwork.mainnet, BtcNetwork.testnet] - - for (const wrongNetwork of networksToCheck) { - const result = checkedToOnChainAddress({ network: wrongNetwork, value: address }) - expect(result).toBeInstanceOf(InvalidOnChainAddress) - } - }, - ) -}) diff --git a/test/galoy/unit/domain/bitcoin/onchain/incoming-tx-handler.spec.ts b/test/galoy/unit/domain/bitcoin/onchain/incoming-tx-handler.spec.ts deleted file mode 100644 index cc1872f9a..000000000 --- a/test/galoy/unit/domain/bitcoin/onchain/incoming-tx-handler.spec.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { IncomingOnChainTxHandler } from "@domain/bitcoin/onchain/incoming-tx-handler" -import { WalletCurrency } from "@domain/shared" - -describe("handleIncomingOnChainTransactions", () => { - const balancesByKey = (amountsByKey: { - [key: string]: bigint - }): { - [key: string]: BtcPaymentAmount - } => { - const balances: { - [key: string]: BtcPaymentAmount - } = {} - for (const key of Object.keys(amountsByKey)) { - balances[key] = { - amount: amountsByKey[key], - currency: WalletCurrency.Btc, - } - } - return balances - } - - const newIncomingTxns: WalletOnChainSettledTransaction[] = [ - // walletId0 1st txn: vout 0 - { - settlementAmount: toSats(100), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId0-address1" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // walletId0 1st txn: vout 1 - { - settlementAmount: toSats(200), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "change-address1" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // walletId0 2nd txn: vout 0 - { - settlementAmount: toSats(300), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId0-address2" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // walletId0 2nd txn: vout 1 - { - settlementAmount: toSats(400), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "change-address2" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // walletId1 1st txn: vout 0 - { - settlementAmount: toSats(500), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId1-address1" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // walletId1 1st txn: vout 1 - { - settlementAmount: toSats(600), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "change-address3" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // Tx with multiple outputs from walletId0 & walletId1: vout 0 - { - settlementAmount: toSats(700), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId0-address1" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // Tx with multiple outputs from walletId0 & walletId1: vout 1 - { - settlementAmount: toSats(800), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId0-address2" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - - // Tx with multiple outputs from walletId0 & walletId1: vout 2 - { - settlementAmount: toSats(900), - settlementCurrency: WalletCurrency.Btc, - initiationVia: { - address: "walletId1-address1" as OnChainAddress, - }, - } as WalletOnChainSettledTransaction, - ] - - const handler = IncomingOnChainTxHandler(newIncomingTxns) - if (handler instanceof Error) throw handler - - describe("balance by address", () => { - it("calculates balances by addresses in txns", () => { - const expectedAmountsByAddress = { - ["walletId0-address1"]: 800n, - ["change-address1"]: 200n, - ["walletId0-address2"]: 1100n, - ["change-address2"]: 400n, - ["walletId1-address1"]: 1400n, - ["change-address3"]: 600n, - } - - const balancesByAddress = handler.balancesByAddresses() - expect(balancesByAddress).toStrictEqual(balancesByKey(expectedAmountsByAddress)) - }) - }) - - describe("balance by wallet", () => { - const walletsInitial = [ - { - id: "walletId0", - onChainAddressIdentifiers: [ - { address: "walletId0-address1" }, - { address: "walletId0-address2" }, - ], - }, - { - id: "walletId1", - onChainAddressIdentifiers: [{ address: "walletId1-address1" }], - }, - { - id: "walletId2", - onChainAddressIdentifiers: [], - }, - ] - const wallets: Wallet[] = walletsInitial.map( - (wallet) => - ({ - ...wallet, - onChainAddresses: () => - wallet.onChainAddressIdentifiers.map(({ address }) => address), - }) as Wallet, - ) - - it("calculates balances for a set of wallets", () => { - const expectedAmountsByWallet = { - walletId0: 1900n, - walletId1: 1400n, - walletId2: 0n, - } - const expectedBalancesByWallet = balancesByKey(expectedAmountsByWallet) - - // Handles multiple wallets - const balancesByWallets = handler.balanceByWallet(wallets) - expect(balancesByWallets).toStrictEqual(expectedBalancesByWallet) - - // Handles a single wallet - const balancesByWallet0 = handler.balanceByWallet([wallets[0]]) - expect(balancesByWallet0).toStrictEqual({ - walletId0: expectedBalancesByWallet.walletId0, - }) - const balancesByWallet1 = handler.balanceByWallet([wallets[1]]) - expect(balancesByWallet1).toStrictEqual({ - walletId1: expectedBalancesByWallet.walletId1, - }) - const balancesByWallet2 = handler.balanceByWallet([wallets[2]]) - expect(balancesByWallet2).toStrictEqual({ - walletId2: expectedBalancesByWallet.walletId2, - }) - }) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/onchain/rebalance-checker.spec.ts b/test/galoy/unit/domain/bitcoin/onchain/rebalance-checker.spec.ts deleted file mode 100644 index 6c47beb99..000000000 --- a/test/galoy/unit/domain/bitcoin/onchain/rebalance-checker.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { RebalanceChecker } from "@domain/bitcoin/onchain" - -describe("RebalanceChecker", () => { - it("returns the amount that should be rebalanced", async () => { - const checker = RebalanceChecker({ - minOnChainHotWalletBalance: toSats(10000), - minRebalanceSize: toSats(10000), - maxHotWalletBalance: toSats(30000), - }) - expect( - checker.getWithdrawFromHotWalletAmount({ - onChainHotWalletBalance: toSats(50000), - offChainHotWalletBalance: toSats(10000), - }), - ).toEqual(toSats(40000)) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/onchain/tx-decoder.spec.ts b/test/galoy/unit/domain/bitcoin/onchain/tx-decoder.spec.ts deleted file mode 100644 index dbaf81fd3..000000000 --- a/test/galoy/unit/domain/bitcoin/onchain/tx-decoder.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { TxDecoder, TransactionDecodeError } from "@domain/bitcoin/onchain" -import { toSats } from "@domain/bitcoin" - -describe("decodeOnChainTransaction", () => { - it("decodes a tx hex", () => { - const validTxHex = - "0100000001bcc1db7faba3226e49bf4b78c70433a5afa0b0c88b2f546d0eee07b40bdf70bc010000006a4730440220378952a9acd4fc40dfafba799be975f545793cc679a01c0d552dbb3e4c29556402205b50a6c5b9a541de3cde7519cbb5a8ebfa1bc49488be1ccf898f888ee5105691012102195646c22ab419c14599106960cc8587266cc0ad2189861c44c5eb3dfa771d3cffffffff0260b10a00000000001976a9145ace538e7c29931c5645e233d327af544dfcfa2f88ac1ef6ee19000000001976a9147452552d6ca38bbfc20f3d95dd3dbb4849a33f7d88ac00000000" - - const decoder = TxDecoder("mainnet" as BtcNetwork) - let result = decoder.decode(validTxHex) - expect(result).not.toBeInstanceOf(TransactionDecodeError) - - result = result as OnChainTransaction - expect(result.txHash).toEqual( - "88b81eff6bab7070be45640d5ffc95819e671cd7d5f294a448735eb1bb980a20", - ) - expect(result.outs[0]).toEqual({ - address: "19H8z5HQ4tuuGZRYJH5xbG2P64KimjnLFx", - sats: toSats(700768), - vout: 0, - }) - }) - - it("can handle txs with OP_RETURN (utxo without address)", () => { - const txWithOpReturn = - "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4e03b4a10a045a9c79be4254432e636f6d2f6835367573fabe6d6d773e726b504adea24922b5449d207a6a190f1b6e5d811558811e009813b756fc020000008e9b20aa0a6e1fd83a8b010000000000ffffffff034d3a9c25000000001976a91474e878616bd5e5236ecb22667627eeecbff54b9f88ac0000000000000000266a24aa21a9edd78d77773f717ec2865633f8955f1344ee3645afc9021d6915819132dbd8c52e0000000000000000266a24b9e11b6dc64bb7816d47abb66e8a01770807e5e22a15a4f328526f56aac07884f364c2150120000000000000000000000000000000000000000000000000000000000000000000000000" - - const decoder = TxDecoder("mainnet" as BtcNetwork) - let result = decoder.decode(txWithOpReturn) - expect(result).not.toBeInstanceOf(TransactionDecodeError) - - result = result as OnChainTransaction - expect(result.txHash).toEqual( - "0ca352925d6afb2845c369be2f09aa12ffeb765b3dc88db05c865e8c02d642dd", - ) - expect(result.outs[0]).toEqual({ - address: "1Bf9sZvBHPFGVPX71WX2njhd1NXKv5y7v5", - sats: toSats(630995533), - vout: 0, - }) - expect(result.outs[1]).toEqual({ - address: null, - sats: toSats(0), - vout: 1, - }) - }) -}) diff --git a/test/galoy/unit/domain/bitcoin/onchain/tx-filter.spec.ts b/test/galoy/unit/domain/bitcoin/onchain/tx-filter.spec.ts deleted file mode 100644 index f89a29b53..000000000 --- a/test/galoy/unit/domain/bitcoin/onchain/tx-filter.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { TxFilter, IncomingOnChainTransaction } from "@domain/bitcoin/onchain" - -describe("TxFilter", () => { - it("filters greater than equal to confs", () => { - const filter = TxFilter({ confirmationsGreaterThanOrEqual: 2 }) - const filteredTxs = filter.apply([ - IncomingOnChainTransaction({ - confirmations: 0, - fee: toSats(1000), - rawTx: { - txHash: "txHash1" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address1" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - IncomingOnChainTransaction({ - confirmations: 2, - fee: toSats(1000), - rawTx: { - txHash: "txHash2" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address2" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - ]) - - expect(filteredTxs.length).toEqual(1) - }) - - it("filters less than confs", () => { - const filter = TxFilter({ confirmationsLessThan: 3 }) - const filteredTxs = filter.apply([ - IncomingOnChainTransaction({ - confirmations: 2, - fee: toSats(1000), - rawTx: { - txHash: "txHash1" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address1" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - IncomingOnChainTransaction({ - confirmations: 3, - fee: toSats(1000), - rawTx: { - txHash: "txHash2" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address2" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - ]) - - expect(filteredTxs[0].confirmations).toEqual(2) - }) - - it("filters including addresses", () => { - const filter = TxFilter({ addresses: ["address1" as OnChainAddress] }) - const filteredTxs = filter.apply([ - IncomingOnChainTransaction({ - confirmations: 2, - fee: toSats(1000), - rawTx: { - txHash: "txHash1" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address1" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - IncomingOnChainTransaction({ - confirmations: 3, - fee: toSats(1000), - rawTx: { - txHash: "txHash2" as OnChainTxHash, - outs: [ - { - sats: toSats(1), - address: "address2" as OnChainAddress, - vout: 0 as OnChainTxVout, - }, - ], - }, - createdAt: new Date(), - }), - ]) - - expect(filteredTxs[0].rawTx.txHash).toEqual("txHash1") - }) -}) diff --git a/test/galoy/unit/domain/errors.spec.ts b/test/galoy/unit/domain/errors.spec.ts deleted file mode 100644 index 5a09adf2b..000000000 --- a/test/galoy/unit/domain/errors.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ValidationError } from "@domain/shared" - -import { - CouldNotFindError, - RepositoryError, - UnknownRepositoryError, - InvalidSatoshiAmountError, - InvalidWalletId, -} from "@domain/errors" - -describe("errors.ts", () => { - it("validate that repository errors are valid errors", () => { - let error = new RepositoryError() - expect(error instanceof Error).toBe(true) - - error = new UnknownRepositoryError() - expect(error instanceof RepositoryError).toBe(true) - expect(error instanceof Error).toBe(true) - - error = new CouldNotFindError() - expect(error instanceof RepositoryError).toBe(true) - expect(error instanceof Error).toBe(true) - }) - - it("validate that validation errors are valid errors", () => { - let error = new ValidationError() - expect(error instanceof Error).toBe(true) - - error = new InvalidSatoshiAmountError() - expect(error instanceof ValidationError).toBe(true) - expect(error instanceof Error).toBe(true) - - error = new InvalidWalletId() - expect(error instanceof ValidationError).toBe(true) - expect(error instanceof Error).toBe(true) - }) -}) diff --git a/test/galoy/unit/domain/fiat/display-amounts-converter.spec.ts b/test/galoy/unit/domain/fiat/display-amounts-converter.spec.ts deleted file mode 100644 index 3191c1ed2..000000000 --- a/test/galoy/unit/domain/fiat/display-amounts-converter.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { DisplayAmountsConverter, UsdDisplayCurrency } from "@domain/fiat" - -import { WalletCurrency, ZERO_CENTS, ZERO_SATS } from "@domain/shared" -import { DisplayPriceRatio } from "@domain/payments" - -describe("DisplayAmountsConverter", () => { - const amounts: AmountsAndFees = { - btcPaymentAmount: { amount: 50_000n, currency: WalletCurrency.Btc }, - btcProtocolAndBankFee: { amount: 1_000n, currency: WalletCurrency.Btc }, - usdPaymentAmount: { amount: 1_250n, currency: WalletCurrency.Usd }, - usdProtocolAndBankFee: { amount: 25n, currency: WalletCurrency.Usd }, - } - - const amountsAndZeroFees: AmountsAndFees = { - ...amounts, - btcProtocolAndBankFee: ZERO_SATS, - usdProtocolAndBankFee: ZERO_CENTS, - } - - const btcQuoteAmount = { - amount: 5000n, - currency: WalletCurrency.Btc, - } - - const displayQuoteAmount = { - amountInMinor: 100n, - displayInMajor: "1.00" as DisplayCurrencyMajorAmount, - } - - const expectedResultForCurrency = (currency: DisplayCurrency) => { - // Based on ratio of `amounts` to `btcQuoteAmount` - const expectedResult = { - displayAmount: { - amountInMinor: 1000n, - currency: undefined, - displayInMajor: "10.00", - }, - displayFee: { amountInMinor: 20n, currency: undefined, displayInMajor: "0.20" }, - displayCurrency: undefined, - displayPrice: { - base: 20000000000n, - offset: 12n, - displayCurrency: undefined, - walletCurrency: "BTC", - }, - } - - return { - displayAmount: { ...expectedResult.displayAmount, currency }, - displayFee: { ...expectedResult.displayFee, currency }, - displayCurrency: currency, - displayPrice: { ...expectedResult.displayPrice, displayCurrency: currency }, - } - } - - const expectedResultForUsd = () => { - const expectedResult = expectedResultForCurrency(UsdDisplayCurrency) - return { - ...expectedResult, - displayAmount: { - ...expectedResult.displayAmount, - amountInMinor: 1250n, - displayInMajor: "12.50", - }, - displayFee: { - ...expectedResult.displayFee, - amountInMinor: 25n, - displayInMajor: "0.25", - }, - } - } - - describe("non-usd display currency", () => { - const currency = "EUR" as DisplayCurrency - - const displayEurPriceRatio = DisplayPriceRatio({ - displayAmount: { ...displayQuoteAmount, currency }, - walletAmount: btcQuoteAmount, - }) - if (displayEurPriceRatio instanceof Error) throw displayEurPriceRatio - - const expectedEurResult = expectedResultForCurrency(currency) - - it("converts amounts to EUR", async () => { - const res = DisplayAmountsConverter(displayEurPriceRatio).convert(amounts) - expect(res).toStrictEqual(expectedEurResult) - }) - - it("converts amounts with zero fees to EUR", async () => { - const expectedZeroFeeEurResult = { - ...expectedEurResult, - displayFee: { - ...expectedEurResult.displayFee, - amountInMinor: 0n, - displayInMajor: "0.00", - }, - } - - const res = - DisplayAmountsConverter(displayEurPriceRatio).convert(amountsAndZeroFees) - expect(res).toStrictEqual(expectedZeroFeeEurResult) - }) - }) - - describe("usd display currency", () => { - const currency = UsdDisplayCurrency - - const displayUsdPriceRatio = DisplayPriceRatio({ - displayAmount: { ...displayQuoteAmount, currency }, - walletAmount: btcQuoteAmount, - }) - if (displayUsdPriceRatio instanceof Error) throw displayUsdPriceRatio - - const expectedUsdResultFromDisplay = expectedResultForCurrency(currency) - const expectedUsdResultFromWallet = expectedResultForUsd() - - it("converts amounts to USD", async () => { - const res = DisplayAmountsConverter(displayUsdPriceRatio).convert(amounts) - expect(res).not.toStrictEqual(expectedUsdResultFromDisplay) - expect(res).toStrictEqual(expectedUsdResultFromWallet) - }) - - it("converts amounts with zero fees to USD", async () => { - const expectedZeroFeeUsdResult = { - ...expectedUsdResultFromWallet, - displayFee: { - ...expectedUsdResultFromWallet.displayFee, - amountInMinor: 0n, - displayInMajor: "0.00", - }, - } - - const res = - DisplayAmountsConverter(displayUsdPriceRatio).convert(amountsAndZeroFees) - expect(res).toStrictEqual(expectedZeroFeeUsdResult) - }) - }) -}) diff --git a/test/galoy/unit/domain/ledger/activity-checker.spec.ts b/test/galoy/unit/domain/ledger/activity-checker.spec.ts deleted file mode 100644 index dd1ba6870..000000000 --- a/test/galoy/unit/domain/ledger/activity-checker.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { toCents } from "@domain/fiat" -import { ActivityChecker } from "@domain/ledger/activity-checker" -import { WalletPriceRatio } from "@domain/payments" -import { WalletCurrency } from "@domain/shared" - -let btcWallet: Wallet, usdWallet: Wallet - -const priceRatio = WalletPriceRatio({ - usd: { amount: BigInt(2), currency: WalletCurrency.Usd }, - btc: { amount: BigInt(1), currency: WalletCurrency.Btc }, -}) as WalletPriceRatio - -beforeAll(() => { - btcWallet = { - id: "walletId" as WalletId, - type: "checking", - currency: WalletCurrency.Btc, - accountId: "a1" as AccountId, - onChainAddressIdentifiers: [], - onChainAddresses: () => [], - } - - usdWallet = { - id: "walletId" as WalletId, - type: "checking", - currency: WalletCurrency.Usd, - accountId: "a1" as AccountId, - onChainAddressIdentifiers: [], - onChainAddresses: () => [], - } -}) - -describe("ActivityChecker with Sats", () => { - it("aboveThreshold returns false below threshold", async () => { - const getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(11), currency: WalletCurrency.Btc }, - incomingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Btc }, - })) as GetVolumeAmountFn - const checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([btcWallet]) - expect(resultIncoming).toBe(false) - }) - - it("Returns true if outgoing or incoming sats are above threshold", async () => { - let getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(500), currency: WalletCurrency.Btc }, - incomingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Btc }, - })) as GetVolumeAmountFn - let checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - - const resultOutgoing = await checker.aboveThreshold([btcWallet]) - expect(resultOutgoing).toBe(true) - - getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Btc }, - incomingBaseAmount: { amount: BigInt(500), currency: WalletCurrency.Btc }, - })) as GetVolumeAmountFn - checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([btcWallet]) - expect(resultIncoming).toBe(true) - }) -}) - -describe("ActivityChecker with Cents", () => { - it("aboveThreshold returns false below threshold", async () => { - const getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(11), currency: WalletCurrency.Usd }, - incomingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Usd }, - })) as GetVolumeAmountFn - const checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([usdWallet]) - expect(resultIncoming).toBe(false) - }) - - it("Returns true if outgoing or incoming sats are above threshold", async () => { - let getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(500), currency: WalletCurrency.Usd }, - incomingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Usd }, - })) as GetVolumeAmountFn - let checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - - const resultOutgoing = await checker.aboveThreshold([usdWallet]) - expect(resultOutgoing).toBe(true) - - getVolumeAmountFn = (() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Usd }, - incomingBaseAmount: { amount: BigInt(500), currency: WalletCurrency.Usd }, - })) as GetVolumeAmountFn - checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([usdWallet]) - expect(resultIncoming).toBe(true) - }) -}) - -describe("ActivityChecker with Cents+Sats wallets", () => { - it("below threshold", async () => { - const getVolumeAmountFn = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(0), currency: WalletCurrency.Btc }, - incomingBaseAmount: { amount: BigInt(10), currency: WalletCurrency.Btc }, - }), - ) - .mockImplementationOnce(() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(0), currency: WalletCurrency.Usd }, - incomingBaseAmount: { amount: BigInt(50), currency: WalletCurrency.Usd }, - }), - ) - - const checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([btcWallet, usdWallet]) - expect(resultIncoming).toBe(false) - }) - it("above threshold", async () => { - const getVolumeAmountFn = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(0), currency: WalletCurrency.Btc }, - incomingBaseAmount: { amount: BigInt(30), currency: WalletCurrency.Btc }, - }), - ) - .mockImplementationOnce(() => - Promise.resolve({ - outgoingBaseAmount: { amount: BigInt(0), currency: WalletCurrency.Usd }, - incomingBaseAmount: { amount: BigInt(90), currency: WalletCurrency.Usd }, - }), - ) - - const checker = ActivityChecker({ - priceRatio, - monthlyVolumeThreshold: toCents(100), - getVolumeAmountFn, - }) - const resultIncoming = await checker.aboveThreshold([btcWallet, usdWallet]) - expect(resultIncoming).toBe(true) - }) -}) diff --git a/test/galoy/unit/domain/ledger/fee-reimbursement.spec.ts b/test/galoy/unit/domain/ledger/fee-reimbursement.spec.ts deleted file mode 100644 index 8bb353f5b..000000000 --- a/test/galoy/unit/domain/ledger/fee-reimbursement.spec.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { FeeDifferenceError } from "@domain/ledger" -import { FeeReimbursement } from "@domain/ledger/fee-reimbursement" -import { WalletPriceRatio } from "@domain/payments" -import { WalletCurrency } from "@domain/shared" - -describe("FeeReimbursement", () => { - it("returns a fee difference for reimbursement", () => { - const prepaidFeeAmount = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 5n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(prepaidFeeAmount) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 20n, currency: WalletCurrency.Btc } - const feeDifferenceAmount = feeReimbursement.getReimbursement(actualFeeAmount) - - const expectedFeeDifferenceAmount = { - btc: { amount: 80n, currency: WalletCurrency.Btc }, - usd: { amount: 4n, currency: WalletCurrency.Usd }, - } - expect(feeDifferenceAmount).toStrictEqual(expectedFeeDifferenceAmount) - }) - it("returns FeeDifferenceError zero prepaid fee", () => { - const prepaidFeeAmount = { - btc: { amount: 0n, currency: WalletCurrency.Btc }, - usd: { amount: 0n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio({ - btc: { amount: 80n, currency: WalletCurrency.Btc }, - usd: { amount: 4n, currency: WalletCurrency.Usd }, - }) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 20n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).toBeInstanceOf(FeeDifferenceError) - }) - it("returns FeeDifferenceError if actual fee is greater than prepaid fee", () => { - const prepaidFeeAmount = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 5n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(prepaidFeeAmount) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 300n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).toBeInstanceOf(FeeDifferenceError) - }) - - describe("returns rounded down amount for USD amounts", () => { - describe("ratio from fees, max fee rounds exactly", () => { - const prepaidFeeAmount = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 5n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(prepaidFeeAmount) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - - it("max fee rounds exactly", async () => { - const { usd, btc } = prepaidFeeAmount - expect(priceRatio.usdPerSat()).toEqual(0.05) - expect((usd.amount * btc.amount) % usd.amount).toEqual(0n) - }) - - it("rounds down to 'max fee - 1' if reimbursement normally rounds up to max fee", () => { - // Calculate fee reimbursement - const actualFeeAmount = { amount: 1n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).not.toBeInstanceOf(Error) - if (feeDifference instanceof Error) throw feeDifference - - // Test: fee reimbursement - expect(feeDifference).toStrictEqual({ - btc: { amount: 99n, currency: "BTC" }, - usd: { currency: "USD", amount: 4n }, - }) - expect(feeDifference.usd.amount).toEqual(prepaidFeeAmount.usd.amount - 1n) - - // Test: normal fee rounding for scenario - const roundedUsdFeeReimbursement = priceRatio.convertFromBtc(feeDifference.btc) - expect(roundedUsdFeeReimbursement.amount).toEqual(prepaidFeeAmount.usd.amount) - }) - - it("rounds down to 'max fee - 1' if fee normally rounds down to 'max fee - 1'", () => { - // Calculate fee reimbursement - const actualFeeAmount = { amount: 19n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).not.toBeInstanceOf(Error) - if (feeDifference instanceof Error) throw feeDifference - - // Test: fee reimbursement - expect(feeDifference).toStrictEqual({ - btc: { amount: 81n, currency: "BTC" }, - usd: { currency: "USD", amount: 4n }, - }) - expect(feeDifference.usd.amount).toEqual(prepaidFeeAmount.usd.amount - 1n) - - // Test: normal fee rounding for scenario - const roundedUsdFeeReimbursement = priceRatio.convertFromBtc(feeDifference.btc) - expect(roundedUsdFeeReimbursement.amount).toEqual( - prepaidFeeAmount.usd.amount - 1n, - ) - }) - - it("rounds down to 'max fee - 2' if fee normally rounds up to 'max fee - 1'", () => { - // Calculate fee reimbursement - const actualFeeAmount = { amount: 21n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).not.toBeInstanceOf(Error) - if (feeDifference instanceof Error) throw feeDifference - - // Test: fee reimbursement - expect(feeDifference).toStrictEqual({ - btc: { amount: 79n, currency: "BTC" }, - usd: { currency: "USD", amount: 3n }, - }) - expect(feeDifference.usd.amount).toEqual(prepaidFeeAmount.usd.amount - 2n) - - // Test: normal fee rounding for scenario - const roundedUsdFeeReimbursement = priceRatio.convertFromBtc(feeDifference.btc) - expect(roundedUsdFeeReimbursement.amount).toEqual( - prepaidFeeAmount.usd.amount - 1n, - ) - }) - }) - - describe("ratio from payment amounts, max fee rounds down normally", () => { - const paymentAmounts = { - btc: { amount: 4000n, currency: WalletCurrency.Btc }, - usd: { amount: 120n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(paymentAmounts) - if (priceRatio instanceof Error) throw priceRatio - - const paymentFlowConvertFromBtc = priceRatio.convertFromBtcToCeil - - it("rounds down if fee normally rounds down to 'max fee - 1'", () => { - // Construct max fees paid - const satsFee = { amount: 80n, currency: WalletCurrency.Btc } - const centsFee = paymentFlowConvertFromBtc(satsFee) - expect(centsFee).toStrictEqual({ amount: 3n, currency: WalletCurrency.Usd }) - const prepaidFeeAmount = { btc: satsFee, usd: centsFee } - - // Test: max usd fee has a remainder and rounds down to 'max fee - 1' normally - const { usd, btc } = paymentAmounts - expect((satsFee.amount * usd.amount) % btc.amount).not.toEqual(0n) - const centsFeeWithNormalRounding = priceRatio.convertFromBtc(satsFee) - expect(centsFeeWithNormalRounding.amount).toEqual(centsFee.amount - 1n) - - // Calculate fee reimbursement - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 5n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).not.toBeInstanceOf(Error) - if (feeDifference instanceof Error) throw feeDifference - - // Test: fee reimbursement - expect(feeDifference).toStrictEqual({ - btc: { amount: 75n, currency: WalletCurrency.Btc }, - usd: { amount: 2n, currency: WalletCurrency.Usd }, - }) - expect(feeDifference.usd.amount).toEqual(centsFee.amount - 1n) - }) - }) - - describe("ratio from payment amounts, max fee rounds up normally", () => { - const paymentAmounts = { - btc: { amount: 5000n, currency: WalletCurrency.Btc }, - usd: { amount: 150n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(paymentAmounts) - if (priceRatio instanceof Error) throw priceRatio - - const paymentFlowConvertFromBtc = priceRatio.convertFromBtcToCeil - - it("rounds down to 'max fee - 1' if fee normally rounds up to max fee", () => { - // Construct max fees paid - const satsFee = { amount: 90n, currency: WalletCurrency.Btc } - const centsFee = paymentFlowConvertFromBtc(satsFee) - expect(centsFee).toStrictEqual({ amount: 3n, currency: WalletCurrency.Usd }) - const prepaidFeeAmount = { btc: satsFee, usd: centsFee } - - // Test: max usd fee has a remainder and rounds up to max fee normally - const { usd, btc } = paymentAmounts - expect((satsFee.amount * usd.amount) % btc.amount).not.toEqual(0n) - const centsFeeWithNormalRounding = priceRatio.convertFromBtc(satsFee) - expect(centsFeeWithNormalRounding.amount).toEqual(centsFee.amount) - - // Calculate fee reimbursement - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 5n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).not.toBeInstanceOf(Error) - if (feeDifference instanceof Error) throw feeDifference - - // Test: fee reimbursement - expect(feeDifference).toStrictEqual({ - btc: { amount: 85n, currency: WalletCurrency.Btc }, - usd: { amount: 2n, currency: WalletCurrency.Usd }, - }) - expect(feeDifference.usd.amount).toEqual(centsFee.amount - 1n) - }) - }) - }) - - it("returns exact amount for USD amounts when no remainder", () => { - const prepaidFeeAmount = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 5n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(prepaidFeeAmount) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - - const actualFeeAmount = { amount: 20n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - expect(feeDifference).toStrictEqual({ - btc: { amount: 80n, currency: "BTC" }, - usd: { currency: "USD", amount: 4n }, - }) - }) - it("returns original amount for zero fee actual amount, ratio rounds exactly", () => { - const prepaidFeeAmount = { - btc: { amount: 100n, currency: WalletCurrency.Btc }, - usd: { amount: 5n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(prepaidFeeAmount) - if (priceRatio instanceof Error) throw priceRatio - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 0n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - - expect(feeDifference).toStrictEqual(prepaidFeeAmount) - }) - it("returns original amount for zero fee actual amount, ratio rounds with remainder", () => { - const paymentAmounts = { - btc: { amount: 2004n, currency: WalletCurrency.Btc }, - usd: { amount: 100n, currency: WalletCurrency.Usd }, - } - - const priceRatio = WalletPriceRatio(paymentAmounts) - if (priceRatio instanceof Error) throw priceRatio - - const btc = { amount: 40n, currency: WalletCurrency.Btc } - const usd = priceRatio.convertFromBtc(btc) - const prepaidFeeAmount = { btc, usd } - expect(btc.amount % paymentAmounts.btc.amount).not.toEqual(0n) - - const feeReimbursement = FeeReimbursement({ prepaidFeeAmount, priceRatio }) - const actualFeeAmount = { amount: 0n, currency: WalletCurrency.Btc } - const feeDifference = feeReimbursement.getReimbursement(actualFeeAmount) - - expect(feeDifference).toStrictEqual(prepaidFeeAmount) - }) -}) diff --git a/test/galoy/unit/domain/ledger/imbalance-calculator.spec.ts b/test/galoy/unit/domain/ledger/imbalance-calculator.spec.ts deleted file mode 100644 index 1813a69e3..000000000 --- a/test/galoy/unit/domain/ledger/imbalance-calculator.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { ONE_DAY } from "@config" -import { toSats } from "@domain/bitcoin" -import { ImbalanceCalculator } from "@domain/ledger/imbalance-calculator" -import { WalletCurrency } from "@domain/shared" -import { WithdrawalFeePriceMethod } from "@domain/wallets" - -const btcWallet: Wallet = { - id: "walletId" as WalletId, - type: "checking", - currency: WalletCurrency.Btc, - accountId: "a1" as AccountId, - onChainAddressIdentifiers: [], - onChainAddresses: () => [], -} - -const VolumeAfterLightningReceiptFn = () => - Promise.resolve({ outgoingBaseAmount: toSats(0), incomingBaseAmount: toSats(500) }) -const VolumeAfterLightningPaymentFn = () => - Promise.resolve({ outgoingBaseAmount: toSats(600), incomingBaseAmount: toSats(0) }) -const VolumeAfterOnChainReceiptFn = () => - Promise.resolve({ outgoingBaseAmount: toSats(0), incomingBaseAmount: toSats(700) }) -const VolumeAfterOnChainPaymentFn = () => - Promise.resolve({ outgoingBaseAmount: toSats(800), incomingBaseAmount: toSats(0) }) -const NoVolumeFn = () => - Promise.resolve({ outgoingBaseAmount: toSats(0), incomingBaseAmount: toSats(0) }) - -describe("ImbalanceCalculator", () => { - describe("for WithdrawalFeePriceMethod.proportionalOnImbalance", () => { - const method = WithdrawalFeePriceMethod.proportionalOnImbalance - it("return positive imbalance when receiving sats on ln", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: VolumeAfterLightningReceiptFn, - volumeOnChainFn: NoVolumeFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(500n) - }) - it("return negative imbalance when sending sats on ln", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: VolumeAfterLightningPaymentFn, - volumeOnChainFn: NoVolumeFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(-600n) - }) - it("return negative imbalance when receiving sats onchain", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: NoVolumeFn, - volumeOnChainFn: VolumeAfterOnChainReceiptFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(-700n) - }) - it("return positive imbalance when sending sats onchain", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: NoVolumeFn, - volumeOnChainFn: VolumeAfterOnChainPaymentFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(800n) - }) - it("swap out increase imbalance", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: VolumeAfterLightningReceiptFn, - volumeOnChainFn: VolumeAfterOnChainPaymentFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(800n + 500n) - }) - it("swap in reduce decrease imbalance", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: VolumeAfterLightningPaymentFn, - volumeOnChainFn: VolumeAfterOnChainReceiptFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(-700n - 600n) - }) - }) - - describe("for WithdrawalFeePriceMethod.flat", () => { - const method = WithdrawalFeePriceMethod.flat - it("no imbalance with flat fees", async () => { - const calculator = ImbalanceCalculator({ - method, - sinceDaysAgo: ONE_DAY, - volumeLightningFn: VolumeAfterLightningReceiptFn, - volumeOnChainFn: NoVolumeFn, - }) - const imbalance = await calculator.getSwapOutImbalanceAmount(btcWallet) - if (imbalance instanceof Error) throw imbalance - expect(imbalance.amount).toBe(0n) - }) - }) -}) diff --git a/test/galoy/unit/domain/ledger/index.spec.ts b/test/galoy/unit/domain/ledger/index.spec.ts deleted file mode 100644 index 35f043483..000000000 --- a/test/galoy/unit/domain/ledger/index.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { LedgerTransactionType, liabilitiesMainAccount } from "@domain/ledger" -import { translateToLedgerTx } from "@services/ledger" -import { toObjectId } from "@services/mongoose/utils" - -describe("LedgerService", () => { - it("translates ILedgerTransaction to LedgerTransaction", () => { - const sampleId = "62c7e689f846db3305b3b53b" - const sampleJournalId = "62c7e689f846db3305b3b534" - const timestamp = new Date() - - const rawTx: ILedgerTransaction = { - _id: toObjectId(sampleId), - credit: 0, - debit: 1000, - datetime: timestamp, - account_path: [liabilitiesMainAccount, "walletId"], - accounts: `${liabilitiesMainAccount}:walletId`, - book: "MainBook", - memo: "memo", - _journal: toObjectId(sampleJournalId), - timestamp, - voided: false, - - hash: "hash", - txid: "txid", - type: LedgerTransactionType.Payment, - pending: false, - currency: "BTC", - feeKnownInAdvance: true, - payee_addresses: [], - memoPayer: "memoPayer", - sats: 1000, - username: "username", - pubkey: "pubkey", - - satsAmount: 1000, - centsAmount: 20, - satsFee: 0, - centsFee: 0, - displayAmount: 20, - displayFee: 0, - displayCurrency: "USD", - } - - const expectedLedgerTx = { - id: "62c7e689f846db3305b3b53b", - walletId: "walletId", - type: "payment", - debit: 1000, - credit: 0, - currency: "BTC", - timestamp, - pendingConfirmation: false, - journalId: "62c7e689f846db3305b3b534", - lnMemo: "memo", - username: "username", - memoFromPayer: "memoPayer", - paymentHash: "hash", - pubkey: "pubkey", - address: undefined, - txHash: "hash", - feeKnownInAdvance: true, - - satsAmount: 1000, - centsAmount: 20, - satsFee: 0, - centsFee: 0, - displayAmount: 20, - displayFee: 0, - displayCurrency: "USD", - } - - expect(translateToLedgerTx(rawTx)).toEqual(expectedLedgerTx) - }) -}) diff --git a/test/galoy/unit/domain/notifications/index.spec.ts b/test/galoy/unit/domain/notifications/index.spec.ts deleted file mode 100644 index 76d9d3b23..000000000 --- a/test/galoy/unit/domain/notifications/index.spec.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { - NotificationChannel, - disableNotificationCategory, - enableNotificationChannel, - disableNotificationChannel, - shouldSendNotification, -} from "@domain/notifications" - -describe("Notifications - push notification filtering", () => { - describe("shouldSendPushNotification", () => { - it("returns false when push notifications are disabled", () => { - const notificationSettings: NotificationSettings = { - push: { - enabled: false, - disabledCategories: [], - }, - } - - const notificationCategory = "transaction" as NotificationCategory - - expect( - shouldSendNotification({ - notificationSettings, - notificationCategory, - notificationChannel: NotificationChannel.Push, - }), - ).toBe(false) - }) - - it("returns true when a notification is not disabled", () => { - const notificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: [], - }, - } - - const notificationCategory = "transaction" as NotificationCategory - - expect( - shouldSendNotification({ - notificationSettings, - notificationCategory, - notificationChannel: NotificationChannel.Push, - }), - ).toBe(true) - }) - - it("returns false when a notification is disabled", () => { - const notificationCategory = "transaction" as NotificationCategory - - const notificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: [notificationCategory], - }, - } - - expect( - shouldSendNotification({ - notificationSettings, - notificationCategory, - notificationChannel: NotificationChannel.Push, - }), - ).toBe(false) - }) - }) - - describe("enableNotificationChannel", () => { - it("clears disabled categories when enabling a channel", () => { - const notificationSettings: NotificationSettings = { - push: { - enabled: false, - disabledCategories: ["transaction" as NotificationCategory], - }, - } - - const notificationChannel = NotificationChannel.Push - - const result = enableNotificationChannel({ - notificationSettings, - notificationChannel, - }) - - expect(result).toEqual({ - push: { - enabled: true, - disabledCategories: [], - }, - }) - }) - }) - - describe("disableNotificationChannel", () => { - it("clears disabled categories when disabling a channel", () => { - const notificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: ["transaction" as NotificationCategory], - }, - } - - const notificationChannel = NotificationChannel.Push - - const result = disableNotificationChannel({ - notificationSettings, - notificationChannel, - }) - - expect(result).toEqual({ - push: { - enabled: false, - disabledCategories: [], - }, - }) - }) - }) - - describe("disableNotificationCategoryForChannel", () => { - it("adds a category to the disabled categories", () => { - const notificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: [], - }, - } - - const notificationChannel = NotificationChannel.Push - - const notificationCategory = "transaction" as NotificationCategory - - const result = disableNotificationCategory({ - notificationSettings, - notificationChannel, - notificationCategory, - }) - - expect(result).toEqual({ - push: { - enabled: true, - disabledCategories: [notificationCategory], - }, - }) - }) - - it("does not add a category to the disabled categories if it is already there", () => { - const notificationCategory = "transaction" as NotificationCategory - - const notificationSettings: NotificationSettings = { - push: { - enabled: true, - disabledCategories: [notificationCategory], - }, - } - - const notificationChannel = NotificationChannel.Push - - const result = disableNotificationCategory({ - notificationSettings, - notificationChannel, - notificationCategory, - }) - - expect(result).toEqual({ - push: { - enabled: true, - disabledCategories: [notificationCategory], - }, - }) - }) - }) -}) diff --git a/test/galoy/unit/domain/payments/index.spec.ts b/test/galoy/unit/domain/payments/index.spec.ts deleted file mode 100644 index 2aecb5119..000000000 --- a/test/galoy/unit/domain/payments/index.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - MAX_CENTS, - MAX_SATS, - WalletCurrency, - BtcAmountTooLargeError, - UsdAmountTooLargeError, - checkedToBtcPaymentAmount, - checkedToUsdPaymentAmount, - InvalidBtcPaymentAmountError, - InvalidUsdPaymentAmountError, -} from "@domain/shared" - -describe("checkedToBtcPaymentAmount", () => { - it("errors on null", () => { - expect(checkedToBtcPaymentAmount(null)).toBeInstanceOf(InvalidBtcPaymentAmountError) - }) - - it("errors on amount greater than max value", () => { - expect(checkedToBtcPaymentAmount(Number(MAX_SATS.amount + 1n))).toBeInstanceOf( - BtcAmountTooLargeError, - ) - expect(checkedToBtcPaymentAmount(Number(MAX_SATS.amount))).toStrictEqual(MAX_SATS) - }) - - it("ensures integer amount", () => { - expect(checkedToBtcPaymentAmount(0.4)).toBeInstanceOf(InvalidBtcPaymentAmountError) - }) - - it("returns the correct type", () => { - expect(checkedToBtcPaymentAmount(1)).toEqual( - expect.objectContaining({ - currency: WalletCurrency.Btc, - amount: 1n, - }), - ) - }) -}) - -describe("checkedToUsdPaymentAmount", () => { - it("errors on null", () => { - expect(checkedToUsdPaymentAmount(null)).toBeInstanceOf(InvalidUsdPaymentAmountError) - }) - - it("errors on amount greater than max value", () => { - expect(checkedToUsdPaymentAmount(Number(MAX_CENTS.amount + 1n))).toBeInstanceOf( - UsdAmountTooLargeError, - ) - expect(checkedToUsdPaymentAmount(Number(MAX_CENTS.amount))).toStrictEqual(MAX_CENTS) - }) - - it("ensures integer amount", () => { - expect(checkedToUsdPaymentAmount(0.4)).toBeInstanceOf(InvalidUsdPaymentAmountError) - }) - - it("returns the correct type", () => { - expect(checkedToUsdPaymentAmount(1)).toEqual( - expect.objectContaining({ - currency: WalletCurrency.Usd, - amount: 1n, - }), - ) - }) -}) diff --git a/test/galoy/unit/domain/payments/ln-fees.spec.ts b/test/galoy/unit/domain/payments/ln-fees.spec.ts deleted file mode 100644 index 5f9abf5e0..000000000 --- a/test/galoy/unit/domain/payments/ln-fees.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { AmountCalculator, ONE_CENT, ONE_SAT, WalletCurrency } from "@domain/shared" -import { LnFees, WalletPriceRatio } from "@domain/payments" -import { MaxFeeTooLargeForRoutelessPaymentError } from "@domain/bitcoin/lightning" - -const calc = AmountCalculator() - -describe("LnFees", () => { - describe("maxProtocolAndBankFee", () => { - it("returns the maxProtocolAndBankFee", () => { - const btcAmount = { - amount: 10_000n, - currency: WalletCurrency.Btc, - } - expect(LnFees().maxProtocolAndBankFee(btcAmount)).toEqual({ - amount: 50n, - currency: WalletCurrency.Btc, - }) - }) - - it("correctly rounds the fee", () => { - const btcAmount = { - amount: 25844n, - currency: WalletCurrency.Btc, - } - expect(LnFees().maxProtocolAndBankFee(btcAmount)).toEqual({ - amount: 129n, - currency: WalletCurrency.Btc, - }) - }) - - it("handles a small amount", () => { - const btcAmount = { - amount: 1n, - currency: WalletCurrency.Btc, - } - expect(LnFees().maxProtocolAndBankFee(btcAmount)).toEqual({ - amount: 1n, - currency: WalletCurrency.Btc, - }) - }) - }) - - const amountsToTest = [ - // Original test case - { - btc: { - amount: 4995n, - currency: WalletCurrency.Btc, - }, - usd: { - amount: 100n, - currency: WalletCurrency.Usd, - }, - }, - // Live prod error case #1 - { - btc: { - amount: 871_652n, - currency: WalletCurrency.Btc, - }, - usd: { - amount: 24_346n, - currency: WalletCurrency.Usd, - }, - }, - // Live prod error case #2 (a Usd payment, with max fee from btc passed in) - { - btc: { - amount: 1342241n, - currency: WalletCurrency.Btc, - }, - usd: { - amount: 38254n, - currency: WalletCurrency.Usd, - }, - }, - ] - for (const [i, { btc, usd }] of amountsToTest.entries()) { - describe(`verifyMaxFee - case #${i + 1}`, () => { - const priceRatio = WalletPriceRatio({ btc, usd }) - if (priceRatio instanceof Error) throw priceRatio - - const validBtcMaxFeeToVerify = LnFees().maxProtocolAndBankFee(btc) - const validUsdMaxFeeInBtcToVerify = priceRatio.convertFromUsd( - LnFees().maxProtocolAndBankFee(usd), - ) - - it("correctly verifies a valid Btc maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: validBtcMaxFeeToVerify, - btcPaymentAmount: btc, - usdPaymentAmount: usd, - priceRatio, - senderWalletCurrency: WalletCurrency.Btc, - isFromNoAmountInvoice: true, - }), - ).toBe(true) - }) - - it("correctly verifies a valid Btc maxFee from Usd wallet", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: validBtcMaxFeeToVerify, - btcPaymentAmount: btc, - usdPaymentAmount: usd, - priceRatio, - senderWalletCurrency: WalletCurrency.Usd, - isFromNoAmountInvoice: false, - }), - ).toBe(true) - }) - - it("correctly verifies a valid Usd maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: validUsdMaxFeeInBtcToVerify, - btcPaymentAmount: btc, - usdPaymentAmount: usd, - priceRatio, - senderWalletCurrency: WalletCurrency.Usd, - isFromNoAmountInvoice: true, - }), - ).toBe(true) - }) - - it("correctly verifies 1 sat Btc payment", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: ONE_SAT, - btcPaymentAmount: ONE_SAT, - usdPaymentAmount: ONE_CENT, - priceRatio, - senderWalletCurrency: WalletCurrency.Btc, - isFromNoAmountInvoice: true, - }), - ).toBe(true) - }) - - it("correctly verifies 1 sat Usd payment", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: priceRatio.convertFromUsd(ONE_CENT), - btcPaymentAmount: ONE_SAT, - usdPaymentAmount: ONE_CENT, - priceRatio, - senderWalletCurrency: WalletCurrency.Usd, - isFromNoAmountInvoice: true, - }), - ).toBe(true) - }) - - it("fails for a large Btc maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: calc.add(validBtcMaxFeeToVerify, ONE_SAT), - btcPaymentAmount: btc, - usdPaymentAmount: usd, - priceRatio, - senderWalletCurrency: WalletCurrency.Btc, - isFromNoAmountInvoice: true, - }), - ).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - }) - - it("fails for a large Usd maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: calc.add(validUsdMaxFeeInBtcToVerify, ONE_SAT), - btcPaymentAmount: btc, - usdPaymentAmount: usd, - priceRatio, - senderWalletCurrency: WalletCurrency.Usd, - isFromNoAmountInvoice: true, - }), - ).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - }) - - it("fails for a 1 sat large Btc maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: calc.add(ONE_SAT, ONE_SAT), - btcPaymentAmount: ONE_SAT, - usdPaymentAmount: ONE_CENT, - priceRatio, - senderWalletCurrency: WalletCurrency.Btc, - isFromNoAmountInvoice: true, - }), - ).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - }) - - it("fails for a 1 sat large Usd maxFee", () => { - expect( - LnFees().verifyMaxFee({ - maxFeeAmount: calc.add(validUsdMaxFeeInBtcToVerify, ONE_SAT), - btcPaymentAmount: ONE_SAT, - usdPaymentAmount: ONE_CENT, - priceRatio, - senderWalletCurrency: WalletCurrency.Usd, - isFromNoAmountInvoice: true, - }), - ).toBeInstanceOf(MaxFeeTooLargeForRoutelessPaymentError) - }) - }) - } - - describe("feeFromRawRoute", () => { - it("returns the feeFromRawRoute", () => { - const rawRoute = { total_mtokens: "21000000", safe_fee: 210 } as RawRoute - expect(LnFees().feeFromRawRoute(rawRoute)).toEqual({ - amount: 210n, - currency: WalletCurrency.Btc, - }) - }) - }) -}) diff --git a/test/galoy/unit/domain/payments/price-ratio.spec.ts b/test/galoy/unit/domain/payments/price-ratio.spec.ts deleted file mode 100644 index b54ebb2d6..000000000 --- a/test/galoy/unit/domain/payments/price-ratio.spec.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { WalletCurrency } from "@domain/shared" -import { - DisplayPriceRatio, - InvalidZeroAmountPriceRatioInputError, - PriceRatio, - toDisplayPriceRatio, - toWalletPriceRatio, - WalletPriceRatio, -} from "@domain/payments" -import { UsdDisplayCurrency } from "@domain/fiat" - -describe("PriceRatio", () => { - const otherQuoteAmount = 100n - const btcQuoteAmount = { - amount: 1000n, - currency: WalletCurrency.Btc, - } - - it("converts usd", () => { - const convertAmount = 40n - - const priceRatio = PriceRatio({ - other: otherQuoteAmount, - walletAmount: btcQuoteAmount, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(convertAmount)).toEqual({ - amount: 400n, - currency: WalletCurrency.Btc, - }) - }) - - it("converts btc", () => { - const convertAmount = { - amount: 40n, - currency: WalletCurrency.Btc, - } - - const priceRatio = PriceRatio({ - other: otherQuoteAmount, - walletAmount: btcQuoteAmount, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromWallet(convertAmount)).toEqual(4n) - }) - - describe("rounds amounts", () => { - describe("rounds amounts normally", () => { - describe("converts from usd", () => { - const btcPivot = { amount: 100_000_000n, currency: WalletCurrency.Btc } - const quotient = 25n - - it("correctly rounds down when unrounded value is just below 0.5 less than target quotient", () => { - const priceRatio = PriceRatio({ - other: 4_100_000n, - walletAmount: btcPivot, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(1n)).toEqual({ - amount: quotient - 1n, - currency: WalletCurrency.Btc, - }) - }) - - it("correctly rounds up when unrounded value is just above 0.5 more than target quotient", () => { - const priceRatio = PriceRatio({ - other: 3_900_000n, - walletAmount: btcPivot, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(1n)).toEqual({ - amount: quotient + 1n, - currency: WalletCurrency.Btc, - }) - }) - - it("correctly rounds down when unrounded value is just above target quotient", () => { - const priceRatio = PriceRatio({ - other: 3_999_900n, - walletAmount: btcPivot, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(1n)).toEqual({ - amount: quotient, - currency: WalletCurrency.Btc, - }) - }) - - it("correctly rounds up when unrounded value is just below target quotient", () => { - const priceRatio = PriceRatio({ - other: 4_000_100n, - walletAmount: btcPivot, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(1n)).toEqual({ - amount: quotient, - currency: WalletCurrency.Btc, - }) - }) - }) - describe("converts from btc", () => { - const product = 2n - const priceRatio = PriceRatio({ - other: 5_000_000n, - walletAmount: { amount: 100_000_000n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - it("correctly rounds down when unrounded value is just below 0.5 less than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWallet({ - amount: 29n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product - 1n) - }) - - it("correctly rounds up when unrounded value is just above 0.5 more than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWallet({ - amount: 51n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product + 1n) - }) - - it("correctly rounds down when unrounded value is just above target product", () => { - const usdPaymentAmount = priceRatio.convertFromWallet({ - amount: 41n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - - it("correctly rounds up when unrounded value is just below target product", () => { - const usdPaymentAmount = priceRatio.convertFromWallet({ - amount: 39n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - }) - }) - - describe("rounds amounts to floor", () => { - describe("converts from usd", () => { - //TODO: implement - }) - describe("converts from btc", () => { - const product = 2n - const priceRatio = PriceRatio({ - other: 5_000_000n, - walletAmount: { amount: 100_000_000n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - it("correctly rounds down when unrounded value is just below 0.5 less than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToFloor({ - amount: 29n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product - 1n) - }) - - it("correctly rounds down when unrounded value is just above 0.5 more than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToFloor({ - amount: 51n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - - it("correctly rounds down when unrounded value is just above target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToFloor({ - amount: 41n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - - it("correctly rounds down when unrounded value is just below target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToFloor({ - amount: 39n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product - 1n) - }) - }) - }) - - describe("rounds amounts to ceil", () => { - describe("converts from usd", () => { - //TODO: implement - }) - describe("converts from btc", () => { - const product = 2n - const priceRatio = PriceRatio({ - other: 5_000_000n, - walletAmount: { amount: 100_000_000n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - it("correctly rounds up when unrounded value is just below 0.5 less than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToCeil({ - amount: 29n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - - it("correctly rounds up when unrounded value is just above 0.5 more than target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToCeil({ - amount: 51n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product + 1n) - }) - - it("correctly rounds up when unrounded value is just above target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToCeil({ - amount: 41n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product + 1n) - }) - - it("correctly rounds up when unrounded value is just below target product", () => { - const usdPaymentAmount = priceRatio.convertFromWalletToCeil({ - amount: 39n, - currency: WalletCurrency.Btc, - }) - expect(usdPaymentAmount).toEqual(product) - }) - }) - }) - }) - - it("does not return zero usd amount for small ratios", () => { - const convertAmount = { - amount: 40n, - currency: WalletCurrency.Btc, - } - - const priceRatio = PriceRatio({ - other: 1n, - walletAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromWallet(convertAmount)).toEqual(1n) - }) - - it("does not return zero btc amount for small ratios", () => { - const convertAmount = 40n - - const priceRatio = PriceRatio({ - other: 1000n, - walletAmount: { amount: 1n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(convertAmount)).toEqual({ - amount: 1n, - currency: WalletCurrency.Btc, - }) - }) - - it("returns zero usd amount for zero btc conversion input", () => { - const convertAmount = { - amount: 0n, - currency: WalletCurrency.Btc, - } - - const priceRatio = PriceRatio({ - other: 1000n, - walletAmount: { amount: 1n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromWallet(convertAmount)).toEqual(0n) - }) - - it("returns zero btc amount for zero usd conversion input", () => { - const convertAmount = 0n - - const priceRatio = PriceRatio({ - other: 1000n, - walletAmount: { amount: 1n, currency: WalletCurrency.Btc }, - }) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.convertFromOther(convertAmount)).toEqual({ - amount: 0n, - currency: WalletCurrency.Btc, - }) - }) - - it("returns error for zero amount btc", () => { - const priceRatio = PriceRatio({ - other: 1n, - walletAmount: { - amount: 0n, - currency: WalletCurrency.Btc, - }, - }) - expect(priceRatio).toBeInstanceOf(InvalidZeroAmountPriceRatioInputError) - }) - - it("returns error for zero amount usd", () => { - const priceRatio = PriceRatio({ - other: 0n, - walletAmount: { - amount: 1n, - currency: WalletCurrency.Btc, - }, - }) - expect(priceRatio).toBeInstanceOf(InvalidZeroAmountPriceRatioInputError) - }) -}) - -describe("WalletPriceRatio", () => { - const usdQuoteAmount = { - amount: 100n, - currency: WalletCurrency.Usd, - } - const btcQuoteAmount = { - amount: 1000n, - currency: WalletCurrency.Btc, - } - const walletPriceRatio = WalletPriceRatio({ usd: usdQuoteAmount, btc: btcQuoteAmount }) - if (walletPriceRatio instanceof Error) throw walletPriceRatio - - it("convertFromUsd", () => { - const result = walletPriceRatio.convertFromUsd({ - amount: 40n, - currency: WalletCurrency.Usd, - }) - - expect(result).toStrictEqual({ - amount: 400n, - currency: WalletCurrency.Btc, - }) - }) - - it("convertFromBtc", () => { - const result = walletPriceRatio.convertFromBtc({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amount: 40n, - currency: WalletCurrency.Usd, - }) - }) - - it("convertFromBtcToFloor", () => { - const result = walletPriceRatio.convertFromBtcToFloor({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amount: 40n, - currency: WalletCurrency.Usd, - }) - }) - - it("convertFromBtcToCeil", () => { - const result = walletPriceRatio.convertFromBtcToCeil({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amount: 41n, - currency: WalletCurrency.Usd, - }) - }) - - it("usdPerSat", () => expect(walletPriceRatio.usdPerSat()).toEqual(0.1)) -}) - -describe("DisplayPriceRatio", () => { - const displayQuoteAmount = { - amountInMinor: 100n, - currency: UsdDisplayCurrency, - displayInMajor: "1.00" as DisplayCurrencyMajorAmount, - } - const btcQuoteAmount = { - amount: 1000n, - currency: WalletCurrency.Btc, - } - const displayPriceRatio = DisplayPriceRatio({ - displayAmount: displayQuoteAmount, - walletAmount: btcQuoteAmount, - }) - if (displayPriceRatio instanceof Error) throw displayPriceRatio - - it("convertFromDisplayMinorUnit", () => { - const result = displayPriceRatio.convertFromDisplayMinorUnit({ - amountInMinor: 40n, - currency: UsdDisplayCurrency, - displayInMajor: "0.40" as DisplayCurrencyMajorAmount, - }) - - expect(result).toStrictEqual({ - amount: 400n, - currency: WalletCurrency.Btc, - }) - }) - - it("convertFromWallet", () => { - const result = displayPriceRatio.convertFromWallet({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amountInMinor: 40n, - currency: UsdDisplayCurrency, - displayInMajor: "0.40", - }) - }) - - it("convertFromWalletToFloor", () => { - const result = displayPriceRatio.convertFromWalletToFloor({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amountInMinor: 40n, - currency: UsdDisplayCurrency, - displayInMajor: "0.40", - }) - }) - - it("convertFromWalletToCeil", () => { - const result = displayPriceRatio.convertFromWalletToCeil({ - amount: 401n, - currency: WalletCurrency.Btc, - }) - - expect(result).toStrictEqual({ - amountInMinor: 41n, - currency: UsdDisplayCurrency, - displayInMajor: "0.41", - }) - }) - - it("displayMinorUnitPerWalletUnit", () => - expect(displayPriceRatio.displayMinorUnitPerWalletUnit()).toEqual(0.1)) -}) - -describe("to WalletPriceRatio from float ratio", () => { - it("converts a float ratio to WalletPriceRatio object", async () => { - const ratio = 0.0005 - - const priceRatio = toWalletPriceRatio(ratio) - expect(priceRatio).not.toBeInstanceOf(Error) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.usdPerSat()).toEqual(ratio) - }) -}) - -describe("to DisplayPriceRatio from float ratio", () => { - it("converts a float ratio to DisplayPriceRatio object", async () => { - const ratio = 0.0005 - - const priceRatio = toDisplayPriceRatio({ - ratio, - displayCurrency: UsdDisplayCurrency, - }) - expect(priceRatio).not.toBeInstanceOf(Error) - if (priceRatio instanceof Error) throw priceRatio - - expect(priceRatio.displayMinorUnitPerWalletUnit()).toEqual(ratio) - }) -}) diff --git a/test/galoy/unit/domain/routes/key-factory.spec.ts b/test/galoy/unit/domain/routes/key-factory.spec.ts deleted file mode 100644 index 01214dc6a..000000000 --- a/test/galoy/unit/domain/routes/key-factory.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { toMilliSatsFromNumber } from "@domain/bitcoin" -import { toCents } from "@domain/fiat" -import { CachedRouteLookupKeyFactory } from "@domain/routes/key-factory" - -describe("cached route key generator", () => { - it("generated a valid key for msats", () => { - const paymentHash = "paymentHash" as PaymentHash - const milliSats = toMilliSatsFromNumber(100_000) - const key = CachedRouteLookupKeyFactory().createFromMilliSats({ - paymentHash, - milliSats, - }) - - const expected = '{"id":"paymentHash","mtokens":"100000"}' - expect(key).toEqual(expected) - }) - it("generated a valid key for cents", () => { - const paymentHash = "paymentHash" as PaymentHash - const cents = toCents(1_000) - const key = CachedRouteLookupKeyFactory().createFromCents({ - paymentHash, - cents, - }) - - const expected = '{"id":"paymentHash","cents":"1000"}' - expect(key).toEqual(expected) - }) -}) diff --git a/test/galoy/unit/domain/shared/amount-calculator.spec.ts b/test/galoy/unit/domain/shared/amount-calculator.spec.ts deleted file mode 100644 index ce1978f5c..000000000 --- a/test/galoy/unit/domain/shared/amount-calculator.spec.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { AmountCalculator, WalletCurrency } from "@domain/shared" - -describe("AmountCalculator", () => { - const calc = AmountCalculator() - - describe("adds", () => { - const currency = WalletCurrency.Btc - const a = { amount: 200n, currency } - const b = { amount: 100n, currency } - - it("adds two payment amounts", () => { - const res = calc.add(a, b) - expect(res).toStrictEqual({ amount: 300n, currency }) - }) - - // TODO: implement - it.skip("fails with two payment amounts of mismatched currencies", () => { - const res1 = calc.add(a, { amount: 200n, currency: WalletCurrency.Usd }) - expect(res1).toBeInstanceOf(Error) - - const res2 = calc.add({ amount: 100n, currency: WalletCurrency.Usd }, b) - expect(res2).toBeInstanceOf(Error) - }) - }) - - describe("subs", () => { - const currency = WalletCurrency.Btc - const a = { amount: 200n, currency } - const b = { amount: 100n, currency } - - it("sub two payment amounts", () => { - const res = calc.sub(a, b) - expect(res).toStrictEqual({ amount: 100n, currency }) - }) - - // TODO: implement - it.skip("fails with two payment amounts of mismatched currencies", () => { - const res1 = calc.sub(a, { amount: 200n, currency: WalletCurrency.Usd }) - expect(res1).toBeInstanceOf(Error) - - const res2 = calc.sub({ amount: 100n, currency: WalletCurrency.Usd }, b) - expect(res2).toBeInstanceOf(Error) - }) - - // TODO: implement - it.skip("fails with if 1st value is smaller than 2nd one", () => { - const res = calc.sub(b, a) - expect(res).toBeInstanceOf(Error) - }) - }) - - describe("divides", () => { - const currency = WalletCurrency.Btc - - const pivot = 200n - const divisor = 100n - const divisorBound = 50n - const quotient = 2n - - const describeCases = [ - { - type: "normal", - div: calc.divRound, - up: "up", - down: "down", - }, - { - type: "floor", - div: calc.divFloor, - up: "down", - down: "down", - }, - { - type: "ceil", - div: calc.divCeil, - up: "up", - down: "up", - }, - ] - const itCases = ({ up, down }) => [ - { - title: `no rounding if remainder is 0`, - amount: pivot, - result: { - normal: quotient, - floor: quotient, - ceil: quotient, - }, - }, - { - title: `rounds ${down} if remainder is just above pivot`, - amount: pivot + 1n, - result: { - normal: quotient, - floor: quotient, - ceil: quotient + 1n, - }, - }, - { - title: `rounds ${up} if remainder is just below pivot`, - amount: pivot - 1n, - result: { - normal: quotient, - floor: quotient - 1n, - ceil: quotient, - }, - }, - { - title: `rounds ${down} if remainder is just below half of the divisor`, - amount: pivot + (divisorBound - 1n), - result: { - normal: quotient, - floor: quotient, - ceil: quotient + 1n, - }, - }, - { - title: `rounds ${up} if remainder is just above half of the divisor`, - amount: pivot + (divisorBound + 1n), - result: { - normal: quotient + 1n, - floor: quotient, - ceil: quotient + 1n, - }, - }, - { - title: `rounds ${down} if remainder is half of the divisor`, - amount: pivot + divisorBound, - result: { - normal: quotient, - floor: quotient, - ceil: quotient + 1n, - }, - }, - ] - - for (const describeGroup of describeCases) { - describe(`by rounding to ${describeGroup.type}`, () => { - const { up, down } = describeGroup - for (const testCase of itCases({ up, down })) { - const { title, amount } = testCase - const expectedResult = testCase.result[describeGroup.type] - - it(`${title}`, () => { - const result = describeGroup.div({ amount, currency }, divisor) - expect(result.amount).toEqual(expectedResult) - expect(result.currency).toBe(currency) - }) - } - }) - } - }) - - describe("applies basis point fraction to an amount", () => { - const basisPoints = 50n // 0.5% or 0.005 - - it("correctly applies basis points to an amount", () => { - const amount = { amount: 20_000n, currency: WalletCurrency.Btc } - const expectedAmount = 100n // 0.5% of 20_000 - const percentageOfAmount = calc.mulBasisPoints(amount, basisPoints) - expect(percentageOfAmount.amount).toEqual(expectedAmount) - }) - - it("correctly applies basis points with rounding down from x.50", () => { - const amount = { amount: 17_500n, currency: WalletCurrency.Btc } - const expectedAmount = 87n // 0.5% of 17_500, rounded down from 87.5 - const percentageOfAmount = calc.mulBasisPoints(amount, basisPoints) - expect(percentageOfAmount.amount).toEqual(expectedAmount) - }) - - it("correctly applies basis points with rounding up from x.5x", () => { - const amount = { amount: 17_501n, currency: WalletCurrency.Btc } - const expectedAmount = 88n // 0.5% of 17_501, rounded up from 87.505 - const percentageOfAmount = calc.mulBasisPoints(amount, basisPoints) - expect(percentageOfAmount.amount).toEqual(expectedAmount) - }) - - it("correctly applies basis points to a small amount down to 0n", () => { - const amount = { amount: 100n, currency: WalletCurrency.Btc } - const expectedAmount = 0n // 0.5% of 100, rounded down from 0.5 - const percentageOfAmount = calc.mulBasisPoints(amount, basisPoints) - expect(percentageOfAmount.amount).toEqual(expectedAmount) - }) - - it("correctly applies basis points to a small amount up to 1n", () => { - const amount = { amount: 101n, currency: WalletCurrency.Btc } - const expectedAmount = 1n // 0.5% of 101, rounded up from 0.505 - const percentageOfAmount = calc.mulBasisPoints(amount, basisPoints) - expect(percentageOfAmount.amount).toEqual(expectedAmount) - }) - }) - - describe("min", () => { - it("returns the min from 2 payment amounts", () => { - const minimum = calc.min( - { amount: 10n, currency: WalletCurrency.Btc }, - { amount: 3n, currency: WalletCurrency.Btc }, - ) - expect(minimum).toStrictEqual({ amount: 3n, currency: WalletCurrency.Btc }) - }) - - it("returns the min from multiple payment amounts", () => { - const paymentAmounts = [ - { amount: 10n, currency: WalletCurrency.Btc }, - { amount: 3n, currency: WalletCurrency.Btc }, - { amount: 7n, currency: WalletCurrency.Btc }, - ] - const minimum = calc.min(...paymentAmounts) - expect(minimum).toStrictEqual({ amount: 3n, currency: WalletCurrency.Btc }) - }) - - it("returns the min from 2 payment amounts of the same value", () => { - const minimum = calc.min( - { amount: 3n, currency: WalletCurrency.Btc }, - { amount: 3n, currency: WalletCurrency.Btc }, - ) - expect(minimum).toStrictEqual({ amount: 3n, currency: WalletCurrency.Btc }) - }) - }) - - describe("max", () => { - it("returns the max from 2 payment amounts", () => { - const maximum = calc.max( - { amount: 10n, currency: WalletCurrency.Btc }, - { amount: 3n, currency: WalletCurrency.Btc }, - ) - expect(maximum).toStrictEqual({ amount: 10n, currency: WalletCurrency.Btc }) - }) - - it("returns the max from multiple payment amounts", () => { - const paymentAmounts = [ - { amount: 10n, currency: WalletCurrency.Btc }, - { amount: 3n, currency: WalletCurrency.Btc }, - { amount: 7n, currency: WalletCurrency.Btc }, - ] - const maximum = calc.max(...paymentAmounts) - expect(maximum).toStrictEqual({ amount: 10n, currency: WalletCurrency.Btc }) - }) - - it("returns the max from 2 payment amounts of the same value", () => { - const maximum = calc.max( - { amount: 10n, currency: WalletCurrency.Btc }, - { amount: 10n, currency: WalletCurrency.Btc }, - ) - expect(maximum).toStrictEqual({ amount: 10n, currency: WalletCurrency.Btc }) - }) - }) -}) diff --git a/test/galoy/unit/domain/swap/swap-out-checker.spec.ts b/test/galoy/unit/domain/swap/swap-out-checker.spec.ts deleted file mode 100644 index 9b3fd1bf9..000000000 --- a/test/galoy/unit/domain/swap/swap-out-checker.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { WalletCurrency, ZERO_SATS } from "@domain/shared" -import { SwapOutChecker } from "@domain/swap" - -describe("SwapOutChecker", () => { - it("returns the amount that should be swapped", () => { - const checker = SwapOutChecker({ - loopOutWhenHotWalletLessThanConfig: { - amount: BigInt(50000), - currency: WalletCurrency.Btc, - }, - swapOutAmount: { - amount: BigInt(250000), - currency: WalletCurrency.Btc, - }, - }) - expect( - checker.getSwapOutAmount({ - currentOnChainHotWalletBalance: { - amount: BigInt(40000), - currency: WalletCurrency.Btc, - }, - currentOutboundLiquidityBalance: { - amount: BigInt(300000), - currency: WalletCurrency.Btc, - }, - }), - ).toEqual({ amount: BigInt(250000), currency: WalletCurrency.Btc }) - }) - - it("returns 0 amount when we don't need a swap out", () => { - const checker = SwapOutChecker({ - loopOutWhenHotWalletLessThanConfig: { - amount: BigInt(50000), - currency: WalletCurrency.Btc, - }, - swapOutAmount: { - amount: BigInt(250000), - currency: WalletCurrency.Btc, - }, - }) - expect( - checker.getSwapOutAmount({ - currentOnChainHotWalletBalance: { - amount: BigInt(100000), - currency: WalletCurrency.Btc, - }, - currentOutboundLiquidityBalance: { - amount: BigInt(300000), - currency: WalletCurrency.Btc, - }, - }), - ).toEqual(ZERO_SATS) - }) - - it("returns 0 amount when we don't have enough outbound liquidity to perform a swap out", () => { - const checker = SwapOutChecker({ - loopOutWhenHotWalletLessThanConfig: { - amount: BigInt(50000), - currency: WalletCurrency.Btc, - }, - swapOutAmount: { - amount: BigInt(250000), - currency: WalletCurrency.Btc, - }, - }) - expect( - checker.getSwapOutAmount({ - currentOnChainHotWalletBalance: { - amount: BigInt(10000), - currency: WalletCurrency.Btc, - }, - currentOutboundLiquidityBalance: { - amount: BigInt(500), - currency: WalletCurrency.Btc, - }, - }), - ).toEqual(ZERO_SATS) - }) -}) diff --git a/test/galoy/unit/domain/users-ips/ip-metadata-authorizer.spec.ts b/test/galoy/unit/domain/users-ips/ip-metadata-authorizer.spec.ts deleted file mode 100644 index 01f330099..000000000 --- a/test/galoy/unit/domain/users-ips/ip-metadata-authorizer.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { - UnauthorizedIPMetadataASNError, - MissingIPMetadataError, - UnauthorizedIPMetadataCountryError, - UnauthorizedIPMetadataProxyError, -} from "@domain/errors" -import { IPMetadataAuthorizer } from "@domain/accounts-ips/ip-metadata-authorizer" - -const defaultConfig = { - denyCountries: [], - allowCountries: [], - denyASNs: [], - allowASNs: [], - checkProxy: true, -} - -const defaultIpInfo: IPType = { - provider: "ISP", - country: "United States", - isoCode: "US", - region: "Florida", - city: "Miami", - asn: "AS60068", - proxy: false, -} - -describe("IPMetadataAuthorizer", () => { - describe("validate", () => { - it("returns true for empty config", () => { - const authorizer = IPMetadataAuthorizer(defaultConfig).authorize(defaultIpInfo) - expect(authorizer).toBe(true) - }) - - it("returns true for an allowed country", () => { - const config = { ...defaultConfig, allowCountries: ["US"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBe(true) - }) - - it("returns true for a country not defined in deny-list", () => { - const config = { ...defaultConfig, denyCountries: ["AF"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBe(true) - }) - - it("returns true for an allowed asn", () => { - const config = { ...defaultConfig, allowASNs: ["AS60068"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBe(true) - }) - - it("returns true for an ASN not defined in deny-list", () => { - const config = { ...defaultConfig, denyASNs: ["AS60067"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBe(true) - }) - - it("returns true for proxy/vpn if check proxy is disabled", () => { - const ipInfo = { ...defaultIpInfo, proxy: true } - const config = { ...defaultConfig, checkProxy: false } - const authorizer = IPMetadataAuthorizer(config).authorize(ipInfo) - expect(authorizer).toBe(true) - }) - - it("returns error for a country not defined in allow-list", () => { - const config = { ...defaultConfig, allowCountries: ["US"] } - const ipInfo = { ...defaultIpInfo, isoCode: "AF" } - const authorizer = IPMetadataAuthorizer(config).authorize(ipInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataCountryError) - }) - - it("returns error for an ASN not defined in allow-list", () => { - const config = { ...defaultConfig, allowASNs: ["AS60068"] } - const ipInfo = { ...defaultIpInfo, asn: "AS60067" } - const authorizer = IPMetadataAuthorizer(config).authorize(ipInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataASNError) - }) - - it("returns error for proxy/vpn", () => { - const ipInfo = { ...defaultIpInfo, proxy: true } - const authorizer = IPMetadataAuthorizer(defaultConfig).authorize(ipInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataProxyError) - }) - - it("returns error for empty isoCode", () => { - const ipInfo = { ...defaultIpInfo, isoCode: undefined } - let authorizer = IPMetadataAuthorizer(defaultConfig).authorize(ipInfo) - expect(authorizer).toBeInstanceOf(MissingIPMetadataError) - - const ipInfo1 = { ...defaultIpInfo, isoCode: "" } - authorizer = IPMetadataAuthorizer(defaultConfig).authorize(ipInfo1) - expect(authorizer).toBeInstanceOf(MissingIPMetadataError) - }) - - it("returns error for empty asn", () => { - const ipInfo = { ...defaultIpInfo, asn: undefined } - let authorizer = IPMetadataAuthorizer(defaultConfig).authorize(ipInfo) - expect(authorizer).toBeInstanceOf(MissingIPMetadataError) - - const ipInfo1 = { ...defaultIpInfo, asn: "" } - authorizer = IPMetadataAuthorizer(defaultConfig).authorize(ipInfo1) - expect(authorizer).toBeInstanceOf(MissingIPMetadataError) - }) - - it("returns error for a denied country", () => { - const config = { ...defaultConfig, denyCountries: ["US"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataCountryError) - }) - - it("returns error for a denied asn", () => { - const config = { ...defaultConfig, denyASNs: ["AS60068"] } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataASNError) - }) - - it("returns error for a denied/allowed country", () => { - const config = { - ...defaultConfig, - denyCountries: ["US"], - allowCountries: ["US"], - } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataCountryError) - }) - - it("returns error for a denied/allowed asn", () => { - const config = { - ...defaultConfig, - denyASNs: ["AS60068"], - allowASNs: ["AS60068"], - } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataASNError) - }) - - it("returns error for a denied asn and allowed country", () => { - const config = { - ...defaultConfig, - denyASNs: ["AS60068"], - allowCountries: ["US"], - } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataASNError) - }) - - it("returns error for a denied country and allowed asn", () => { - const config = { - ...defaultConfig, - allowASNs: ["AS60068"], - denyCountries: ["US"], - } - const authorizer = IPMetadataAuthorizer(config).authorize(defaultIpInfo) - expect(authorizer).toBeInstanceOf(UnauthorizedIPMetadataCountryError) - }) - }) -}) diff --git a/test/galoy/unit/domain/users-ips/parse-ips.spec.ts b/test/galoy/unit/domain/users-ips/parse-ips.spec.ts deleted file mode 100644 index e6e61360c..000000000 --- a/test/galoy/unit/domain/users-ips/parse-ips.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { parseIps } from "@domain/accounts-ips" - -describe("parseIps", () => { - it("returns undefined if ip is empty", () => { - let ip = parseIps(undefined) - expect(ip).toBe(undefined) - - ip = parseIps("") - expect(ip).toBe(undefined) - - ip = parseIps([]) - expect(ip).toBe(undefined) - - ip = parseIps([""]) - expect(ip).toBe(undefined) - - ip = parseIps(", 192.168.0.2") - expect(ip).toBe(undefined) - }) - - it("returns first ip from array", () => { - const ip = parseIps(["192.168.0.1", "192.168.0.2"]) - expect(ip).toBe("192.168.0.1") - }) - - it("returns first ip from string array", () => { - let ip = parseIps("192.168.0.1, 192.168.0.2") - expect(ip).toBe("192.168.0.1") - - ip = parseIps("192.168.0.1 , 192.168.0.2") - expect(ip).toBe("192.168.0.1") - - ip = parseIps(" 192.168.0.2, 192.168.0.3") - expect(ip).toBe("192.168.0.2") - }) -}) diff --git a/test/galoy/unit/domain/users/checked-to-accountlevel.spec.ts b/test/galoy/unit/domain/users/checked-to-accountlevel.spec.ts deleted file mode 100644 index 4b66c7a16..000000000 --- a/test/galoy/unit/domain/users/checked-to-accountlevel.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { checkedToAccountLevel } from "@domain/accounts" -import { InvalidAccountLevelError } from "@domain/errors" - -describe("account-level-check", () => { - it("Passes with valid account levels", () => { - expect(checkedToAccountLevel(1)).toEqual(1) - expect(checkedToAccountLevel(2)).toEqual(2) - }) - - it("Fails with invalid account level", () => { - expect(checkedToAccountLevel(3)).toBeInstanceOf(InvalidAccountLevelError) - }) -}) diff --git a/test/galoy/unit/domain/users/checked-to-language.spec.ts b/test/galoy/unit/domain/users/checked-to-language.spec.ts deleted file mode 100644 index bf68a7fcd..000000000 --- a/test/galoy/unit/domain/users/checked-to-language.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { InvalidLanguageError } from "@domain/errors" -import { checkedToLanguage } from "@domain/users" - -describe("language-check", () => { - it("Passes with valid languages", () => { - expect(checkedToLanguage("")).toEqual("") - expect(checkedToLanguage("en")).toEqual("en") - expect(checkedToLanguage("es")).toEqual("es") - }) - - it("Fails with invalid languages", () => { - expect(checkedToLanguage("Klingon")).toBeInstanceOf(InvalidLanguageError) - }) -}) diff --git a/test/galoy/unit/domain/users/checked-to-phonenumber.spec.ts b/test/galoy/unit/domain/users/checked-to-phonenumber.spec.ts deleted file mode 100644 index 2ba90184b..000000000 --- a/test/galoy/unit/domain/users/checked-to-phonenumber.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { checkedToPhoneNumber } from "@domain/users" - -describe("phonenumber-check", () => { - it("Fail just prefix", () => { - const phone = checkedToPhoneNumber("+1") - expect(phone).toBeInstanceOf(Error) - }) - - it("Fail bitcoin address", () => { - const phone = checkedToPhoneNumber("1LKvxGL8ejTsgBjRVUNCGi7adiwaVnM9cn") - expect(phone).toBeInstanceOf(Error) - }) - - it("Fail incompleted phone number", () => { - const phone = checkedToPhoneNumber("+1650") - expect(phone).toBeInstanceOf(Error) - }) - - it("Fail with non digits", () => { - const phone = checkedToPhoneNumber("+1650555abcd") - expect(phone).toBeInstanceOf(Error) - }) - - it("a + is needed", () => { - const phone = checkedToPhoneNumber("6505554321") - expect(phone).toBeInstanceOf(Error) - }) - - it("Success on good phone number", () => { - const phone = checkedToPhoneNumber("+16505554321") - expect(phone).toEqual("+16505554321") - }) -}) diff --git a/test/galoy/unit/domain/users/checked-to-username.spec.ts b/test/galoy/unit/domain/users/checked-to-username.spec.ts deleted file mode 100644 index 910b8ab29..000000000 --- a/test/galoy/unit/domain/users/checked-to-username.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { checkedToUsername } from "@domain/accounts" - -describe("username-check", () => { - it("Passes alphanumeric username", () => { - const username = checkedToUsername("alice_12") - expect(username).toEqual("alice_12") - }) - - it("Fails legacy address", () => { - const username = checkedToUsername("1LKvxGL8ejTsgBjRVUNCGi7adiwaVnM9cn") - expect(username).toBeInstanceOf(Error) - }) - - it("Fails wrapped segwit address", () => { - const username = checkedToUsername("32ksNi7zSt3t2aesvoEWhGMUEwCFg9UCCG") - expect(username).toBeInstanceOf(Error) - }) - - it("Fails segwit address", () => { - const username = checkedToUsername("bc1qpl8ehyzu44yhwu92w892uxwxdfp9dhu3d0zj2g") - expect(username).toBeInstanceOf(Error) - }) - - it("Fails lightning payment request", () => { - const username = checkedToUsername( - "lnbc1500n1ps36h3rpp5qtgvy47pu6n3t2ggf47vahl3kdjql0d68egtaschvd34atvu5eysdpa2fjkzep6ypyx7aeqw3hjqct4w3hk6ct5d93kzmrv0ys82uryv96x2greda6hycqzpgxqr23ssp5makumjtdy54vz80ayytgld7420uuw5m6pdtq5x2n38gg7z5gd9ms9qyyssq64faagqrfa6qp45jsx8enwgs62fquqfejxk0gmtuf67z7v7d9364c5tw679cd635nmllfwzur348whvrgnf94sx6w40n6ttwa4x8grcqa0ms0d", - ) - expect(username).toBeInstanceOf(Error) - }) - - it("Fails non-underscore special characters", () => { - const username = checkedToUsername("alice-12") - expect(username).toBeInstanceOf(Error) - }) -}) diff --git a/test/galoy/unit/domain/users/phone-metadata-authorizer.spec.ts b/test/galoy/unit/domain/users/phone-metadata-authorizer.spec.ts deleted file mode 100644 index 6a70d6b03..000000000 --- a/test/galoy/unit/domain/users/phone-metadata-authorizer.spec.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { getRewardsConfig, yamlConfig } from "@config" -import { - PhoneCountryNotAllowedError, - PhoneCarrierTypeNotAllowedError, - ExpectedPhoneMetadataMissingError, -} from "@domain/users/errors" -import { PhoneMetadataAuthorizer } from "@domain/users" - -beforeEach(async () => { - yamlConfig.rewards = { - denyPhoneCountries: ["in"], - allowPhoneCountries: ["sv", "US"], - denyIPCountries: [], - allowIPCountries: [], - denyASNs: [], - allowASNs: [], - enableIpProxyCheck: true, - } -}) - -const getPhoneMetadataRewardsSettings = () => - getRewardsConfig().phoneMetadataValidationSettings - -describe("PhoneMetadataAuthorizer - validate", () => { - it("returns true for empty config", () => { - const config = { - denyCountries: [], - allowCountries: [], - } - const authorizersSV = PhoneMetadataAuthorizer(config).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "IN", - }) - expect(authorizersSV).toBe(true) - }) - - it("returns true for a valid country", () => { - const authorizersSV = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "SV", - }) - expect(authorizersSV).toBe(true) - - const authorizersSV1 = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "sv", - }) - expect(authorizersSV1).toBe(true) - - const validatorUS = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "US", - }) - expect(validatorUS).toBe(true) - - const config = { - denyCountries: ["AF"], - allowCountries: [], - } - const validatorIN = PhoneMetadataAuthorizer(config).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "IN", - }) - expect(validatorIN).toBe(true) - }) - - it("returns error for invalid country", () => { - const validator = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "CO", - }) - expect(validator).toBeInstanceOf(PhoneCountryNotAllowedError) - - const config = { - denyCountries: ["AF"], - allowCountries: [], - } - const validatorAF = PhoneMetadataAuthorizer(config).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "AF", - }) - expect(validatorAF).toBeInstanceOf(PhoneCountryNotAllowedError) - }) - - it("returns error for allowed and denied country", () => { - const config = { - denyCountries: ["in"], - allowCountries: ["in"], - } - const validator = PhoneMetadataAuthorizer(config).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "mobile", - }, - countryCode: "IN", - }) - - // denyPhoneCountries config should have priority - expect(validator).toBeInstanceOf(PhoneCountryNotAllowedError) - }) - - it("returns error with undefined metadata", () => { - const validator = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize(undefined) - expect(validator).toBeInstanceOf(ExpectedPhoneMetadataMissingError) - }) - - it("returns error with voip type", () => { - const validator = PhoneMetadataAuthorizer( - getPhoneMetadataRewardsSettings(), - ).authorize({ - carrier: { - error_code: "", - mobile_country_code: "", - mobile_network_code: "", - name: "", - type: "voip", - }, - countryCode: "US", - }) - expect(validator).toBeInstanceOf(PhoneCarrierTypeNotAllowedError) - }) -}) diff --git a/test/galoy/unit/domain/users/phone-metadata-validator.spec.ts b/test/galoy/unit/domain/users/phone-metadata-validator.spec.ts deleted file mode 100644 index 94dff498e..000000000 --- a/test/galoy/unit/domain/users/phone-metadata-validator.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - InvalidCarrierForPhoneMetadataError, - InvalidCarrierTypeForPhoneMetadataError, - InvalidCountryCodeForPhoneMetadataError, -} from "@domain/users/errors" -import { PhoneMetadataValidator } from "@domain/users" - -describe("PhoneMetadataValidator", () => { - it("returns valid PhoneMetadata object", async () => { - const validRawPhoneMetadata = { carrier: { type: "mobile" }, countryCode: "US" } - - const phoneMetadata = PhoneMetadataValidator().validate(validRawPhoneMetadata) - expect(phoneMetadata).not.toBeInstanceOf(Error) - }) - - it("returns error for invalid carrier object", async () => { - const invalidRawPhoneMetadata = { carrier: "mobile", countryCode: "US" } - - const phoneMetadata = PhoneMetadataValidator().validate(invalidRawPhoneMetadata) - expect(phoneMetadata).toBeInstanceOf(InvalidCarrierForPhoneMetadataError) - }) - - it("returns error for invalid carrier type", async () => { - const invalidRawPhoneMetadata = { carrier: { type: "invalid" }, countryCode: "US" } - - const phoneMetadata = PhoneMetadataValidator().validate(invalidRawPhoneMetadata) - expect(phoneMetadata).toBeInstanceOf(InvalidCarrierTypeForPhoneMetadataError) - }) - - it("returns error for invalid country code type", async () => { - // Country code as object - { - const invalidRawPhoneMetadata = { - carrier: { type: "mobile" }, - countryCode: { name: "" }, - } - const phoneMetadata = PhoneMetadataValidator().validate(invalidRawPhoneMetadata) - expect(phoneMetadata).toBeInstanceOf(InvalidCountryCodeForPhoneMetadataError) - } - - // Missing country code - { - const invalidRawPhoneMetadata = { - carrier: { type: "mobile" }, - } - const phoneMetadata = PhoneMetadataValidator().validate(invalidRawPhoneMetadata) - expect(phoneMetadata).toBeInstanceOf(InvalidCountryCodeForPhoneMetadataError) - } - }) -}) diff --git a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts b/test/galoy/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts deleted file mode 100644 index 2782de167..000000000 --- a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { sha256 } from "@domain/bitcoin/lightning" -import { checkedToMinutes } from "@domain/primitives" -import { BtcPaymentAmount, WalletCurrency } from "@domain/shared" -import { WalletInvoiceBuilder } from "@domain/wallet-invoices/wallet-invoice-builder" - -describe("WalletInvoiceBuilder", () => { - const recipientBtcWallet = { - id: "recipientWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "recipientAccountId" as AccountId, - username: "Username" as Username, - } - const recipientUsdWallet = { - id: "recipientWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "recipientAccountId" as AccountId, - username: "Username" as Username, - } - const uncheckedAmount = 100 - const dealerPriceRatio = 2n - const btcFromUsd = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: amount.amount / dealerPriceRatio, - currency: WalletCurrency.Btc, - }) - } - - const registerInvoice = async (args: NewRegisterInvoiceArgs) => { - const amount = toSats(args.btcPaymentAmount.amount) - - const lnInvoice = { - destination: "pubkey" as Pubkey, - paymentHash: args.paymentHash, - paymentRequest: "paymentRequest" as EncodedPaymentRequest, - milliSatsAmount: (amount * 1000) as MilliSatoshis, - description: args.description, - cltvDelta: null, - amount, - paymentAmount: args.btcPaymentAmount, - routeHints: [[]], - paymentSecret: null, - features: [], - expiresAt: args.expiresAt, - isExpired: false, - } - - const invoice: RegisteredInvoice = { - invoice: lnInvoice, - pubkey: "pubkey" as Pubkey, - descriptionHash: args.descriptionHash, - } - - return invoice - } - - const WIB = WalletInvoiceBuilder({ - dealerBtcFromUsd: btcFromUsd, - lnRegisterInvoice: registerInvoice, - }) - - const checkSecretAndHash = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - const { secret } = walletInvoice - const hashFromSecret = sha256(Buffer.from(secret, "hex")) - expect(hashFromSecret).toEqual(walletInvoice.paymentHash) - expect(walletInvoice.paymentHash).toEqual(lnInvoice.paymentHash) - expect(lnInvoice).not.toHaveProperty("secret") - } - - const testDescription = "testdescription" - const WIBWithDescription = WIB.withDescription({ - description: testDescription, - }) - const checkDescription = ({ lnInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice.description).toEqual(testDescription) - } - - const expirationInMinutes = checkedToMinutes(3) - if (expirationInMinutes instanceof Error) throw expirationInMinutes - const testExpiration = new Date("2000-01-01T00:03:00.000Z") - const checkExpiration = ({ lnInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice.expiresAt).toEqual(testExpiration) - } - - beforeAll(() => { - jest.useFakeTimers() - jest.setSystemTime(new Date("2000-01-01T00:00:00.000Z")) - }) - - afterAll(() => { - jest.useRealTimers() - }) - - describe("generated for self", () => { - const WIBWithCreator = WIBWithDescription.generatedForSelf() - const checkCreator = ({ walletInvoice }: LnAndWalletInvoice) => { - expect(walletInvoice.selfGenerated).toEqual(true) - } - - describe("with btc recipient wallet", () => { - const WIBWithRecipient = WIBWithCreator.withRecipientWallet(recipientBtcWallet) - const checkRecipientWallet = ({ walletInvoice }: LnAndWalletInvoice) => { - expect(walletInvoice.recipientWalletDescriptor).toEqual(recipientBtcWallet) - } - - describe("with amount", () => { - it("registers and persists invoice with no conversion", async () => { - const WIBWithAmount = - await WIBWithRecipient.withExpiration(expirationInMinutes).withAmount( - uncheckedAmount, - ) - - if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( - expect.objectContaining({ - amount: uncheckedAmount as Satoshis, - paymentAmount: BtcPaymentAmount(BigInt(uncheckedAmount)), - milliSatsAmount: (1000 * uncheckedAmount) as MilliSatoshis, - }), - ) - expect(walletInvoice).toEqual( - expect.objectContaining({ - usdAmount: undefined, - paid: false, - }), - ) - } - - const invoices = await WIBWithAmount.registerInvoice() - if (invoices instanceof Error) throw invoices - - checkSecretAndHash(invoices) - checkAmount(invoices) - checkDescription(invoices) - checkCreator(invoices) - checkRecipientWallet(invoices) - checkExpiration(invoices) - }) - }) - - describe("with no amount", () => { - it("registers and persists invoice", async () => { - const WIBWithAmount = - await WIBWithRecipient.withExpiration(expirationInMinutes).withoutAmount() - - if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( - expect.objectContaining({ - amount: 0 as Satoshis, - paymentAmount: BtcPaymentAmount(BigInt(0)), - milliSatsAmount: 0 as MilliSatoshis, - }), - ) - expect(walletInvoice).toEqual( - expect.objectContaining({ - usdAmount: undefined, - paid: false, - }), - ) - } - const invoices = await WIBWithAmount.registerInvoice() - - if (invoices instanceof Error) throw invoices - - checkSecretAndHash(invoices) - checkAmount(invoices) - checkDescription(invoices) - checkCreator(invoices) - checkRecipientWallet(invoices) - checkExpiration(invoices) - }) - }) - }) - - describe("with usd recipient wallet", () => { - const WIBWithRecipient = WIBWithCreator.withRecipientWallet(recipientUsdWallet) - const checkRecipientWallet = ({ walletInvoice }: LnAndWalletInvoice) => { - expect(walletInvoice.recipientWalletDescriptor).toEqual(recipientUsdWallet) - } - - describe("with amount", () => { - it("registers and persists invoice with conversion", async () => { - const WIBWithAmount = - await WIBWithRecipient.withExpiration(expirationInMinutes).withAmount( - uncheckedAmount, - ) - - if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - const convertedAmount = BigInt(uncheckedAmount) / dealerPriceRatio - expect(lnInvoice).toEqual( - expect.objectContaining({ - amount: Number(convertedAmount) as Satoshis, - paymentAmount: BtcPaymentAmount(convertedAmount), - milliSatsAmount: (1000 * Number(convertedAmount)) as MilliSatoshis, - }), - ) - expect(walletInvoice).toEqual( - expect.objectContaining({ - usdAmount: { - currency: WalletCurrency.Usd, - amount: BigInt(uncheckedAmount), - }, - paid: false, - }), - ) - } - const invoices = await WIBWithAmount.registerInvoice() - - if (invoices instanceof Error) throw invoices - - checkSecretAndHash(invoices) - checkAmount(invoices) - checkDescription(invoices) - checkCreator(invoices) - checkRecipientWallet(invoices) - checkExpiration(invoices) - }) - }) - - describe("with no amount", () => { - it("registers and persists invoice", async () => { - const WIBWithAmount = - await WIBWithRecipient.withExpiration(expirationInMinutes).withoutAmount() - - if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( - expect.objectContaining({ - amount: 0 as Satoshis, - paymentAmount: BtcPaymentAmount(BigInt(0)), - milliSatsAmount: 0 as MilliSatoshis, - }), - ) - expect(walletInvoice).toEqual( - expect.objectContaining({ - usdAmount: undefined, - paid: false, - }), - ) - } - - const invoices = await WIBWithAmount.registerInvoice() - - if (invoices instanceof Error) throw invoices - - checkSecretAndHash(invoices) - checkAmount(invoices) - checkDescription(invoices) - checkCreator(invoices) - checkRecipientWallet(invoices) - checkExpiration(invoices) - }) - }) - }) - }) -}) diff --git a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts b/test/galoy/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts deleted file mode 100644 index 6cbcfc8cd..000000000 --- a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - BtcPaymentAmount, - UsdPaymentAmount, - WalletCurrency, - ZERO_CENTS, - ZERO_SATS, -} from "@domain/shared" - -import { WalletInvoiceReceiver } from "@domain/wallet-invoices/wallet-invoice-receiver" - -describe("WalletInvoiceReceiver", () => { - const midPriceRatio = 2n - const usdFromBtcMidPrice = async (amount: BtcPaymentAmount) => { - const finalAmount = amount.amount / midPriceRatio || 1n - return Promise.resolve({ - amount: finalAmount, - currency: WalletCurrency.Usd, - }) - } - - const immediatePriceRation = 3n - const usdFromBtc = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: amount.amount / immediatePriceRation, - currency: WalletCurrency.Usd, - }) - } - - const receivedBtc = BtcPaymentAmount(1200n) - - const recipientAccountId = "recipientAccountId" as AccountId - - const partialRecipientBtcWalletDescriptor = { - id: "recipientBtcWalletId" as WalletId, - currency: WalletCurrency.Btc, - } - const recipientBtcWalletDescriptor = { - ...partialRecipientBtcWalletDescriptor, - accountId: recipientAccountId, - } - - const partialRecipientUsdWalletDescriptor = { - id: "recipientUsdWalletId" as WalletId, - currency: WalletCurrency.Usd, - } - const recipientUsdWalletDescriptor = { - ...partialRecipientUsdWalletDescriptor, - accountId: recipientAccountId, - } - - describe("for btc invoice", () => { - const btcInvoice: WalletInvoice = { - paymentHash: "paymentHash" as PaymentHash, - secret: "secret" as SecretPreImage, - selfGenerated: false, - pubkey: "pubkey" as Pubkey, - usdAmount: undefined, - paid: false, - recipientWalletDescriptor: partialRecipientBtcWalletDescriptor, - createdAt: new Date(), - } - - it("returns correct amounts", async () => { - const walletInvoiceAmounts = await WalletInvoiceReceiver({ - walletInvoice: btcInvoice, - receivedBtc, - recipientAccountId, - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - }).withConversion({ - mid: { usdFromBtc: usdFromBtcMidPrice }, - hedgeBuyUsd: { usdFromBtc }, - }) - - if (walletInvoiceAmounts instanceof Error) throw walletInvoiceAmounts - - const usdToCreditReceiver = await usdFromBtcMidPrice(receivedBtc) - - if (usdToCreditReceiver instanceof Error) throw usdToCreditReceiver - - expect(walletInvoiceAmounts).toEqual( - expect.objectContaining({ - usdBankFee: ZERO_CENTS, - btcBankFee: ZERO_SATS, - usdToCreditReceiver, - btcToCreditReceiver: receivedBtc, - }), - ) - }) - }) - - describe("for usd invoice", () => { - describe("with cents amount", () => { - const amountUsdInvoice: WalletInvoice = { - paymentHash: "paymentHash" as PaymentHash, - secret: "secret" as SecretPreImage, - recipientWalletDescriptor: partialRecipientUsdWalletDescriptor, - selfGenerated: false, - pubkey: "pubkey" as Pubkey, - usdAmount: UsdPaymentAmount(BigInt(100)), - paid: false, - createdAt: new Date(), - } - - it("returns correct amounts", async () => { - const walletInvoiceAmounts = await WalletInvoiceReceiver({ - walletInvoice: amountUsdInvoice, - receivedBtc, - recipientAccountId, - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - }).withConversion({ - mid: { usdFromBtc: usdFromBtcMidPrice }, - hedgeBuyUsd: { usdFromBtc }, - }) - - if (walletInvoiceAmounts instanceof Error) throw walletInvoiceAmounts - - expect(walletInvoiceAmounts).toEqual( - expect.objectContaining({ - usdBankFee: ZERO_CENTS, - btcBankFee: ZERO_SATS, - usdToCreditReceiver: amountUsdInvoice.usdAmount, - btcToCreditReceiver: receivedBtc, - }), - ) - }) - }) - describe("with no amount", () => { - const noAmountUsdInvoice: WalletInvoice = { - paymentHash: "paymentHash" as PaymentHash, - secret: "secret" as SecretPreImage, - recipientWalletDescriptor: partialRecipientUsdWalletDescriptor, - selfGenerated: false, - pubkey: "pubkey" as Pubkey, - paid: false, - createdAt: new Date(), - } - - it("returns correct amounts", async () => { - const walletInvoiceAmounts = await WalletInvoiceReceiver({ - walletInvoice: noAmountUsdInvoice, - receivedBtc, - recipientAccountId, - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - }).withConversion({ - mid: { usdFromBtc: usdFromBtcMidPrice }, - hedgeBuyUsd: { usdFromBtc }, - }) - - if (walletInvoiceAmounts instanceof Error) throw walletInvoiceAmounts - - const usdToCreditReceiver = await usdFromBtc(receivedBtc) - if (usdToCreditReceiver instanceof Error) throw usdToCreditReceiver - - expect(walletInvoiceAmounts).toEqual( - expect.objectContaining({ - usdBankFee: ZERO_CENTS, - btcBankFee: ZERO_SATS, - usdToCreditReceiver, - btcToCreditReceiver: receivedBtc, - }), - ) - }) - - it("credits amount less than 1 cent amount to recipient btc wallet", async () => { - const walletInvoiceAmounts = await WalletInvoiceReceiver({ - walletInvoice: noAmountUsdInvoice, - receivedBtc: BtcPaymentAmount(1n), - recipientAccountId, - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - }).withConversion({ - mid: { usdFromBtc: usdFromBtcMidPrice }, - hedgeBuyUsd: { usdFromBtc }, - }) - if (walletInvoiceAmounts instanceof Error) throw walletInvoiceAmounts - - const { recipientWalletDescriptor } = walletInvoiceAmounts - expect(recipientWalletDescriptor).toStrictEqual(recipientBtcWalletDescriptor) - }) - }) - }) -}) diff --git a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-validator.spec.ts b/test/galoy/unit/domain/wallet-invoices/wallet-invoice-validator.spec.ts deleted file mode 100644 index f70b964d8..000000000 --- a/test/galoy/unit/domain/wallet-invoices/wallet-invoice-validator.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { AlreadyPaidError, SelfPaymentError } from "@domain/errors" -import { WalletInvoiceValidator } from "@domain/wallet-invoices" -import { WalletCurrency } from "@domain/shared" - -describe("WalletInvoiceValidator", () => { - const walletInvoice: WalletInvoice = { - paymentHash: "paymentHash" as PaymentHash, - secret: "secret" as SecretPreImage, - recipientWalletDescriptor: { - id: "toWalletId" as WalletId, - currency: WalletCurrency.Btc, - }, - selfGenerated: false, - pubkey: "pubkey" as Pubkey, - usdAmount: { - currency: WalletCurrency.Usd, - amount: BigInt(10), - }, - paid: false, - createdAt: new Date(), - } - const fromWalletId = "fromWalletId" as WalletId - - it("passes for unpaid invoice and valid recipient wallet", () => { - walletInvoice.paid = false - const walletInvoiceValidator = WalletInvoiceValidator(walletInvoice) - - const validatorResult = walletInvoiceValidator.validateToSend(fromWalletId) - expect(validatorResult).not.toBeInstanceOf(Error) - }) - - it("fails for self recipient wallet", () => { - walletInvoice.paid = false - const walletInvoiceValidator = WalletInvoiceValidator(walletInvoice) - - const validatorResult = walletInvoiceValidator.validateToSend( - walletInvoice.recipientWalletDescriptor.id, - ) - expect(validatorResult).toBeInstanceOf(SelfPaymentError) - }) - - it("fails for non-pending invoice and valid recipient wallet", () => { - walletInvoice.paid = true - const walletInvoiceValidator = WalletInvoiceValidator(walletInvoice) - - const validatorResult = walletInvoiceValidator.validateToSend(fromWalletId) - expect(validatorResult).toBeInstanceOf(AlreadyPaidError) - }) -}) diff --git a/test/galoy/unit/domain/wallet-on-chain-addresses/wallet-address-receiver.spec.ts b/test/galoy/unit/domain/wallet-on-chain-addresses/wallet-address-receiver.spec.ts deleted file mode 100644 index 7f4f9673d..000000000 --- a/test/galoy/unit/domain/wallet-on-chain-addresses/wallet-address-receiver.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { AmountLessThanFeeError } from "@domain/errors" -import { WalletPriceRatio } from "@domain/payments" -import { AmountCalculator, BtcPaymentAmount, WalletCurrency } from "@domain/shared" - -import { WalletAddressReceiver } from "@domain/wallet-on-chain/wallet-address-receiver" - -const calc = AmountCalculator() - -describe("WalletAddressReceiver", () => { - const midPriceRatio = 2n - const usdFromBtcMidPrice = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: amount.amount * midPriceRatio, - currency: WalletCurrency.Usd, - }) - } - - const immediatePriceRatio = 3n - const usdFromBtc = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: amount.amount * immediatePriceRatio, - currency: WalletCurrency.Usd, - }) - } - - const receivedBtc = BtcPaymentAmount(1200n) - const satsFee = BtcPaymentAmount(200n) - const recipientBtcWallet = { - id: "recipientWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "recipientAccountId" as AccountId, - } - const recipientUsdWallet = { - id: "recipientWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "recipientAccountId" as AccountId, - } - - describe("for btc wallet", () => { - const btcWalletAddress: WalletAddress<"BTC"> = { - address: "btcAddress" as OnChainAddress, - recipientWalletDescriptor: recipientBtcWallet, - } - - it("fails when receiveBtc is less than fee", async () => { - const walletAddressAmounts = await WalletAddressReceiver({ - walletAddress: btcWalletAddress, - receivedBtc, - satsFee: BtcPaymentAmount(receivedBtc.amount + 1n), - usdFromBtcMidPrice, - usdFromBtc, - }) - - expect(walletAddressAmounts).toBeInstanceOf(AmountLessThanFeeError) - }) - - it("returns correct amounts", async () => { - const walletAddressAmounts = await WalletAddressReceiver({ - walletAddress: btcWalletAddress, - receivedBtc, - satsFee, - usdFromBtcMidPrice, - usdFromBtc, - }) - - if (walletAddressAmounts instanceof Error) throw walletAddressAmounts - - const receivedUsd = await usdFromBtcMidPrice(receivedBtc) - if (receivedUsd instanceof Error) throw receivedUsd - - const priceRatio = WalletPriceRatio({ usd: receivedUsd, btc: receivedBtc }) - if (priceRatio instanceof Error) throw priceRatio - const centsFee = priceRatio.convertFromBtcToCeil(satsFee) - - expect(walletAddressAmounts).toEqual( - expect.objectContaining({ - usdBankFee: centsFee, - btcBankFee: satsFee, - usdToCreditReceiver: calc.sub(receivedUsd, centsFee), - btcToCreditReceiver: calc.sub(receivedBtc, satsFee), - }), - ) - }) - }) - - describe("for usd wallet", () => { - const usdWalletAddress: WalletAddress<"USD"> = { - address: "usdAddress" as OnChainAddress, - recipientWalletDescriptor: recipientUsdWallet, - } - - it("fails when receiveBtc is less than fee", async () => { - const walletAddressAmounts = await WalletAddressReceiver({ - walletAddress: usdWalletAddress, - receivedBtc, - satsFee: BtcPaymentAmount(receivedBtc.amount + 1n), - usdFromBtcMidPrice, - usdFromBtc, - }) - - expect(walletAddressAmounts).toBeInstanceOf(AmountLessThanFeeError) - }) - - it("returns correct amounts", async () => { - const walletAddressAmounts = await WalletAddressReceiver({ - walletAddress: usdWalletAddress, - receivedBtc, - satsFee, - usdFromBtcMidPrice, - usdFromBtc, - }) - - if (walletAddressAmounts instanceof Error) throw walletAddressAmounts - - const receivedUsd = await usdFromBtc(receivedBtc) - if (receivedUsd instanceof Error) throw receivedUsd - - const priceRatio = WalletPriceRatio({ usd: receivedUsd, btc: receivedBtc }) - if (priceRatio instanceof Error) throw priceRatio - const centsFee = priceRatio.convertFromBtcToCeil(satsFee) - - expect(walletAddressAmounts).toEqual( - expect.objectContaining({ - usdBankFee: centsFee, - btcBankFee: satsFee, - usdToCreditReceiver: calc.sub(receivedUsd, centsFee), - btcToCreditReceiver: calc.sub(receivedBtc, satsFee), - }), - ) - }) - }) -}) diff --git a/test/galoy/unit/domain/wallets/deposit-fee-calculator.spec.ts b/test/galoy/unit/domain/wallets/deposit-fee-calculator.spec.ts deleted file mode 100644 index 1e11ee37f..000000000 --- a/test/galoy/unit/domain/wallets/deposit-fee-calculator.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { DepositFeeCalculator } from "@domain/wallets" -import { WalletCurrency, ZERO_SATS } from "@domain/shared" - -describe("DepositFeeCalculator", () => { - const calculator = DepositFeeCalculator() - - const onChainFeeConfig = { - ratio: 30n as DepositFeeRatioAsBasisPoints, - minBankFee: { - amount: 3000n, - currency: WalletCurrency.Btc, - }, - minBankFeeThreshold: { - amount: 1_000_000n, - currency: WalletCurrency.Btc, - }, - } - - describe("onChainDepositFee", () => { - it("applies a depositFeeRatio", () => { - const amount = { - amount: 10_000_000n, - currency: WalletCurrency.Btc, - } - const config = { ...onChainFeeConfig, ratio: 200n as DepositFeeRatioAsBasisPoints } - const fee = calculator.onChainDepositFee({ ...config, amount }) - expect(fee).toEqual({ amount: 200000n, currency: WalletCurrency.Btc }) - }) - - it("applies a depositFeeRatio of 0", () => { - let amount = { - amount: 1_000_001n, - currency: WalletCurrency.Btc, - } - const config = { ...onChainFeeConfig, ratio: 0n as DepositFeeRatioAsBasisPoints } - let fee = calculator.onChainDepositFee({ ...config, amount }) - expect(fee).toEqual(ZERO_SATS) - - amount = { - amount: 999_999n, - currency: WalletCurrency.Btc, - } - fee = calculator.onChainDepositFee({ ...config, amount }) - expect(fee).toEqual({ amount: 3000n, currency: WalletCurrency.Btc }) - }) - - it("applies a min fee", () => { - const amount = { - amount: 9999n, - currency: WalletCurrency.Btc, - } - const config = { - ...onChainFeeConfig, - minBankFeeThreshold: { - amount: 10_000n, - currency: WalletCurrency.Btc, - }, - } - const fee = calculator.onChainDepositFee({ ...config, amount }) - expect(fee).toEqual({ amount: 3000n, currency: WalletCurrency.Btc }) - }) - }) - - describe("lnDepositFee", () => { - it("is free", () => { - const fee = calculator.lnDepositFee() - expect(fee).toEqual(ZERO_SATS) - }) - }) -}) diff --git a/test/galoy/unit/domain/wallets/payment-input-validator.spec.ts b/test/galoy/unit/domain/wallets/payment-input-validator.spec.ts deleted file mode 100644 index 522043df4..000000000 --- a/test/galoy/unit/domain/wallets/payment-input-validator.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -import crypto from "crypto" - -import { UsdDisplayCurrency } from "@domain/fiat" -import { AccountLevel, AccountStatus } from "@domain/accounts" -import { InvalidAccountStatusError, SelfPaymentError } from "@domain/errors" -import { PaymentInputValidator, WalletType } from "@domain/wallets" -import { WalletCurrency, InvalidBtcPaymentAmountError } from "@domain/shared" - -describe("PaymentInputValidator", () => { - const dummyAccount: Account = { - id: crypto.randomUUID() as AccountId, - uuid: crypto.randomUUID() as AccountUuid, - createdAt: new Date(), - username: "username" as Username, - defaultWalletId: "senderWalletId" as WalletId, - withdrawFee: 0 as Satoshis, - level: AccountLevel.One, - status: AccountStatus.Active, - statusHistory: [{ status: AccountStatus.Active }], - title: "" as BusinessMapTitle, - coordinates: { - latitude: 0, - longitude: 0, - }, - notificationSettings: { - push: { - enabled: true, - disabledCategories: [], - }, - }, - contactEnabled: true, - contacts: [], - isEditor: false, - quizQuestions: [], - quiz: [], - kratosUserId: "kratosUserId" as UserId, - displayCurrency: UsdDisplayCurrency, - } - - const dummySenderWallet: Wallet = { - id: crypto.randomUUID() as WalletId, - accountId: dummyAccount.id, - type: WalletType.Checking, - currency: WalletCurrency.Btc, - onChainAddressIdentifiers: [], - onChainAddresses: () => [], - } - - const dummyRecipientWallet: Wallet = { - id: crypto.randomUUID() as WalletId, - accountId: crypto.randomUUID() as AccountId, - type: WalletType.Checking, - currency: WalletCurrency.Btc, - onChainAddressIdentifiers: [], - onChainAddresses: () => [], - } - - const wallets: { [key: WalletId]: Wallet } = {} - wallets[dummySenderWallet.id] = dummySenderWallet - wallets[dummyRecipientWallet.id] = dummyRecipientWallet - - const getWalletFn: PaymentInputValidatorConfig = (walletId: WalletId) => { - return Promise.resolve(wallets[walletId]) - } - - it("returns the correct types when everything is valid", async () => { - const validator: PaymentInputValidator = PaymentInputValidator(getWalletFn) - const result = await validator.validatePaymentInput({ - amount: 2, - amountCurrency: WalletCurrency.Btc, - senderWalletId: dummySenderWallet.id, - senderAccount: dummyAccount, - recipientWalletId: dummyRecipientWallet.id, - }) - if (result instanceof Error) throw result - - const { amount, senderWallet, recipientWallet } = result - expect(amount).toStrictEqual({ amount: 2n, currency: WalletCurrency.Btc }) - expect(senderWallet).toEqual(expect.objectContaining(dummySenderWallet)) - expect(recipientWallet).toEqual(expect.objectContaining(dummyRecipientWallet)) - }) - - it("Fails on invalid amount", async () => { - const validator: PaymentInputValidator = PaymentInputValidator(getWalletFn) - const result = await validator.validatePaymentInput({ - amount: -1, - amountCurrency: WalletCurrency.Btc, - senderWalletId: dummySenderWallet.id, - senderAccount: dummyAccount, - recipientWalletId: dummyRecipientWallet.id, - }) - expect(result).toBeInstanceOf(InvalidBtcPaymentAmountError) - }) - - it("Fails when sender === recipient", async () => { - const validator: PaymentInputValidator = PaymentInputValidator(getWalletFn) - const result = await validator.validatePaymentInput({ - amount: 2, - amountCurrency: WalletCurrency.Btc, - senderWalletId: dummySenderWallet.id, - senderAccount: dummyAccount, - recipientWalletId: dummySenderWallet.id, - }) - expect(result).toBeInstanceOf(SelfPaymentError) - }) - - it("Fails if the account is not active", async () => { - const validator: PaymentInputValidator = PaymentInputValidator(getWalletFn) - const result = await validator.validatePaymentInput({ - amount: 2, - amountCurrency: WalletCurrency.Btc, - senderWalletId: dummySenderWallet.id, - senderAccount: { - ...dummyAccount, - status: AccountStatus.Locked, - }, - recipientWalletId: dummyRecipientWallet.id, - }) - expect(result).toBeInstanceOf(InvalidAccountStatusError) - }) -}) diff --git a/test/galoy/unit/domain/wallets/settlement-amounts.spec.ts b/test/galoy/unit/domain/wallets/settlement-amounts.spec.ts deleted file mode 100644 index 5e34a90b0..000000000 --- a/test/galoy/unit/domain/wallets/settlement-amounts.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { UsdDisplayCurrency, displayAmountFromNumber, toCents } from "@domain/fiat" -import { WalletCurrency } from "@domain/shared" -import { SettlementAmounts } from "@domain/wallets/settlement-amounts" - -describe("SettlementAmounts", () => { - const txnCommon = { - id: "id" as LedgerTransactionId, - journalId: "journalId" as LedgerJournalId, - walletId: "walletId" as WalletId, - type: "type" as LedgerTransactionType, - timestamp: new Date(), - pendingConfirmation: false, - feeKnownInAdvance: true, - - fee: 0, - feeUsd: 0, - usd: 0, - } - - const satsAmount = toSats(10_000) - const satsFee = toSats(100) - const centsAmount = toCents(200) - const centsFee = toCents(2) - const displayAmount = 1400 as DisplayCurrencyBaseAmount - const displayFee = 14 as DisplayCurrencyBaseAmount - const displayCurrency = "TTD" as DisplayCurrency - - const txnsAmounts = { - satsAmount, - satsFee, - centsAmount, - centsFee, - displayAmount, - displayFee, - displayCurrency, - } - - const expectedDisplayFeeObj = displayAmountFromNumber({ - amount: displayFee, - currency: displayCurrency, - }) - if (expectedDisplayFeeObj instanceof Error) throw expectedDisplayFeeObj - const expectedDisplayFee = expectedDisplayFeeObj.displayInMajor - - const debitCreditScenarios = { - "amount": { - sats: satsAmount, - cents: centsAmount, - display: displayAmount, - }, - "fee": { - sats: satsFee, - cents: centsFee, - display: displayFee, - }, - "amount + fee": { - sats: satsAmount + satsFee, - cents: centsAmount + centsFee, - display: displayAmount + displayFee, - }, - "amount - fee": { - sats: satsAmount - satsFee, - cents: centsAmount - centsFee, - display: displayAmount - displayFee, - }, - } - - describe("Check all possible BTC entries", () => { - const txnForBtcWallet = { - ...txnCommon, - ...txnsAmounts, - currency: WalletCurrency.Btc, - } - - describe("debit", () => { - for (const testCase of Object.keys(debitCreditScenarios)) { - it(`${testCase}`, () => { - const testValues = debitCreditScenarios[testCase] - const debit = testValues.sats - - const expectedSettlementAmountForDebit = -debit - const expectedDisplayAmount = -testValues.display - - const txnDebit: LedgerTransaction<"BTC"> = { - ...txnForBtcWallet, - credit: toSats(0), - debit: toSats(debit), - } - const settlementAmounts = SettlementAmounts().fromTxn(txnDebit) - const { settlementAmount, settlementDisplayAmount, settlementDisplayFee } = - settlementAmounts - - const expectedDisplayAmountForDebitObj = displayAmountFromNumber({ - amount: expectedDisplayAmount, - currency: txnDebit.displayCurrency || UsdDisplayCurrency, - }) - if (expectedDisplayAmountForDebitObj instanceof Error) { - throw expectedDisplayAmountForDebitObj - } - const expectedDisplayAmountForDebit = - expectedDisplayAmountForDebitObj.displayInMajor - - expect(settlementAmount).toEqual(expectedSettlementAmountForDebit) - expect(settlementDisplayAmount).toEqual(expectedDisplayAmountForDebit) - expect(settlementDisplayFee).toEqual(expectedDisplayFee) - }) - } - }) - - describe("credit", () => { - for (const testCase of Object.keys(debitCreditScenarios)) { - it(`${testCase}`, () => { - const testValues = debitCreditScenarios[testCase] - const credit = testValues.sats - - const expectedSettlementAmountForCredit = credit - const expectedDisplayAmount = testValues.display - - const txnCredit: LedgerTransaction<"BTC"> = { - ...txnForBtcWallet, - credit: toSats(credit), - debit: toSats(0), - } - const settlementAmounts = SettlementAmounts().fromTxn(txnCredit) - const { settlementAmount, settlementDisplayAmount, settlementDisplayFee } = - settlementAmounts - - const expectedDisplayAmountForCreditObj = displayAmountFromNumber({ - amount: expectedDisplayAmount, - currency: txnCredit.displayCurrency || UsdDisplayCurrency, - }) - if (expectedDisplayAmountForCreditObj instanceof Error) { - throw expectedDisplayAmountForCreditObj - } - const expectedDisplayAmountForCredit = - expectedDisplayAmountForCreditObj.displayInMajor - - expect(settlementAmount).toEqual(expectedSettlementAmountForCredit) - expect(settlementDisplayAmount).toEqual(expectedDisplayAmountForCredit) - expect(settlementDisplayFee).toEqual(expectedDisplayFee) - }) - } - }) - }) - - describe("Check all possible USD entries", () => { - const txnForBtcWallet = { - ...txnCommon, - ...txnsAmounts, - currency: WalletCurrency.Usd, - } - - describe("debit", () => { - for (const testCase of Object.keys(debitCreditScenarios)) { - it(`${testCase}`, () => { - const testValues = debitCreditScenarios[testCase] - const debit = testValues.cents - - const expectedSettlementAmountForDebit = -debit - const expectedDisplayAmount = -testValues.display - - const txnDebit: LedgerTransaction<"USD"> = { - ...txnForBtcWallet, - credit: toCents(0), - debit: toCents(debit), - } - const settlementAmounts = SettlementAmounts().fromTxn(txnDebit) - const { settlementAmount, settlementDisplayAmount, settlementDisplayFee } = - settlementAmounts - - const expectedDisplayAmountForDebitObj = displayAmountFromNumber({ - amount: expectedDisplayAmount, - currency: txnDebit.displayCurrency || UsdDisplayCurrency, - }) - if (expectedDisplayAmountForDebitObj instanceof Error) { - throw expectedDisplayAmountForDebitObj - } - const expectedDisplayAmountForDebit = - expectedDisplayAmountForDebitObj.displayInMajor - - expect(settlementAmount).toEqual(expectedSettlementAmountForDebit) - expect(settlementDisplayAmount).toEqual(expectedDisplayAmountForDebit) - expect(settlementDisplayFee).toEqual(expectedDisplayFee) - }) - } - }) - - describe("credit", () => { - for (const testCase of Object.keys(debitCreditScenarios)) { - it(`${testCase}`, () => { - const testValues = debitCreditScenarios[testCase] - const credit = testValues.cents - - const expectedSettlementAmountForCredit = credit - const expectedDisplayAmount = testValues.display - - const txnCredit: LedgerTransaction<"USD"> = { - ...txnForBtcWallet, - credit: toCents(credit), - debit: toCents(0), - } - const settlementAmounts = SettlementAmounts().fromTxn(txnCredit) - const { settlementAmount, settlementDisplayAmount, settlementDisplayFee } = - settlementAmounts - - const expectedDisplayAmountForCreditObj = displayAmountFromNumber({ - amount: expectedDisplayAmount, - currency: txnCredit.displayCurrency || UsdDisplayCurrency, - }) - if (expectedDisplayAmountForCreditObj instanceof Error) { - throw expectedDisplayAmountForCreditObj - } - const expectedDisplayAmountForCredit = - expectedDisplayAmountForCreditObj.displayInMajor - - expect(settlementAmount).toEqual(expectedSettlementAmountForCredit) - expect(settlementDisplayAmount).toEqual(expectedDisplayAmountForCredit) - expect(settlementDisplayFee).toEqual(expectedDisplayFee) - }) - } - }) - }) -}) diff --git a/test/galoy/unit/domain/wallets/tx-history.spec.ts b/test/galoy/unit/domain/wallets/tx-history.spec.ts deleted file mode 100644 index c58a18130..000000000 --- a/test/galoy/unit/domain/wallets/tx-history.spec.ts +++ /dev/null @@ -1,470 +0,0 @@ -import crypto from "crypto" - -import { LedgerTransactionType } from "@domain/ledger" -import { SettlementMethod, PaymentInitiationMethod, TxStatus } from "@domain/wallets" -import { - displayCurrencyPerBaseUnitFromAmounts, - translateMemo, - WalletTransactionHistory, -} from "@domain/wallets/tx-history" -import { toSats } from "@domain/bitcoin" -import { - memoSharingConfig, - MEMO_SHARING_CENTS_THRESHOLD, - MEMO_SHARING_SATS_THRESHOLD, -} from "@config" -import { WalletCurrency } from "@domain/shared" -import { UsdDisplayCurrency, priceAmountFromNumber, toCents } from "@domain/fiat" - -describe("translates ledger txs to wallet txs", () => { - const timestamp = new Date() - - const satsAmount = toSats(100_000) - const satsFee = toSats(2) - const centsAmount = toCents(2_000) - const centsFee = toCents(10) - const displayFee = 10 as DisplayCurrencyBaseAmount - - const baseLedgerTransaction = { - id: "id" as LedgerTransactionId, - satsFee, - centsFee, - displayFee, - displayCurrency: UsdDisplayCurrency, - pendingConfirmation: false, - journalId: "journalId" as LedgerJournalId, - timestamp, - feeKnownInAdvance: false, - } - - const baseWalletTransaction = { - id: "id" as LedgerTransactionId, - status: TxStatus.Success, - createdAt: timestamp, - } - - const ledgerTxnsInputs = ({ - walletId, - settlementAmount, - satsAmount, - centsAmount, - currency, - }: { - walletId: WalletId - settlementAmount: Satoshis | UsdCents - satsAmount: Satoshis - centsAmount: UsdCents - currency: WalletCurrency - }): LedgerTransaction[] => { - const currencyBaseLedgerTxns = { - ...baseLedgerTransaction, - walletId, - satsAmount, - centsAmount, - displayAmount: centsAmount as unknown as DisplayCurrencyBaseAmount, - currency, - - fee: undefined, - feeUsd: undefined, - usd: undefined, - } - - return [ - { - ...currencyBaseLedgerTxns, - type: LedgerTransactionType.Invoice, - - debit: toSats(0), - credit: settlementAmount, - - paymentHash: "paymentHash" as PaymentHash, - pubkey: "pubkey" as Pubkey, - memoFromPayer: "SomeMemo", - }, - { - ...currencyBaseLedgerTxns, - recipientWalletId: "walletIdRecipient" as WalletId, - type: LedgerTransactionType.IntraLedger, - - debit: toSats(0), - credit: settlementAmount, - - paymentHash: "paymentHash" as PaymentHash, - pubkey: "pubkey" as Pubkey, - username: "username" as Username, - }, - { - ...currencyBaseLedgerTxns, - recipientWalletId: "walletIdRecipient" as WalletId, - type: LedgerTransactionType.OnchainIntraLedger, - - debit: toSats(0), - credit: settlementAmount, - - address: "address" as OnChainAddress, - txHash: "txHash" as OnChainTxHash, - }, - { - ...currencyBaseLedgerTxns, - type: LedgerTransactionType.OnchainReceipt, - - debit: toSats(0), - credit: settlementAmount, - - address: "address" as OnChainAddress, - txHash: "txHash" as OnChainTxHash, - }, - ] - } - - const expectedWalletTxns = ({ - walletId, - settlementAmount, - centsAmount, - currency, - }: { - walletId: WalletId - settlementAmount: Satoshis | UsdCents - centsAmount: UsdCents - currency: WalletCurrency - }): WalletTransaction[] => { - const displayCurrency = UsdDisplayCurrency - - const settlementFee = currency === WalletCurrency.Btc ? satsFee : centsFee - const settlementDisplayPrice = displayCurrencyPerBaseUnitFromAmounts({ - displayAmount: centsAmount, - displayCurrency, - walletAmount: settlementAmount, - walletCurrency: currency, - }) - - if (currency === WalletCurrency.Usd) { - expect(settlementDisplayPrice).toEqual( - priceAmountFromNumber({ - priceOfOneSatInMinorUnit: 1, - displayCurrency, - walletCurrency: currency, - }), - ) - } - - const currencyBaseWalletTxns = { - ...baseWalletTransaction, - walletId, - settlementCurrency: currency, - - settlementAmount, - settlementFee, - settlementDisplayAmount: (centsAmount / 100).toFixed(2), - settlementDisplayFee: (centsFee / 100).toFixed(2), - settlementDisplayPrice, - } - - return [ - { - ...currencyBaseWalletTxns, - initiationVia: { - type: PaymentInitiationMethod.Lightning, - paymentHash: "paymentHash" as PaymentHash, - pubkey: "pubkey" as Pubkey, - }, - settlementVia: { - type: SettlementMethod.Lightning, - revealedPreImage: undefined, - }, - memo: "SomeMemo", - }, - - { - ...currencyBaseWalletTxns, - initiationVia: { - type: PaymentInitiationMethod.Lightning, - paymentHash: "paymentHash" as PaymentHash, - pubkey: "pubkey" as Pubkey, - }, - settlementVia: { - type: SettlementMethod.IntraLedger, - counterPartyWalletId: "walletIdRecipient" as WalletId, - counterPartyUsername: "username" as Username, - }, - memo: null, - }, - { - ...currencyBaseWalletTxns, - initiationVia: { - type: PaymentInitiationMethod.OnChain, - address: "address" as OnChainAddress, - }, - settlementVia: { - type: SettlementMethod.IntraLedger, - counterPartyWalletId: "walletIdRecipient" as WalletId, - counterPartyUsername: null, - }, - memo: null, - }, - { - ...currencyBaseWalletTxns, - initiationVia: { - type: PaymentInitiationMethod.OnChain, - address: "address" as OnChainAddress, - }, - settlementVia: { - type: SettlementMethod.OnChain, - transactionHash: "txHash" as OnChainTxHash, - }, - memo: null, - }, - ] - } - - describe("WalletTransactionHistory.fromLedger", () => { - it("translates btc ledger txs", () => { - const currency = WalletCurrency.Btc - const settlementAmount = satsAmount - - const txnsArgs = { - walletId: crypto.randomUUID() as WalletId, - settlementAmount, - satsAmount, - centsAmount, - centsFee, - displayAmount: centsAmount, - displayFee: centsFee, - currency, - } - - const ledgerTransactions = ledgerTxnsInputs(txnsArgs) - const result = WalletTransactionHistory.fromLedger({ - ledgerTransactions, - nonEndUserWalletIds: [], - memoSharingConfig, - }) - - const expected = expectedWalletTxns(txnsArgs) - expect(result.transactions).toEqual(expected) - }) - - it("translates usd ledger txs", () => { - const currency = WalletCurrency.Usd - const settlementAmount = centsAmount - - const txnsArgs = { - walletId: crypto.randomUUID() as WalletId, - settlementAmount, - satsAmount, - centsAmount, - centsFee, - displayAmount: centsAmount, - displayFee: centsFee, - currency, - } - - const ledgerTransactions = ledgerTxnsInputs(txnsArgs) - const result = WalletTransactionHistory.fromLedger({ - ledgerTransactions, - nonEndUserWalletIds: [], - memoSharingConfig, - }) - - const expected = expectedWalletTxns(txnsArgs) - expect(result.transactions).toEqual(expected) - }) - - it("handles missing satsAmount-related properties", () => { - const currency = WalletCurrency.Btc - const settlementAmount = satsAmount - - const txnsArgs = { - walletId: crypto.randomUUID() as WalletId, - settlementAmount, - satsAmount, - centsAmount, - centsFee, - displayAmount: centsAmount, - displayFee: centsFee, - currency, - } - - const ledgerTransactions = ledgerTxnsInputs(txnsArgs) - - // Remove satsAmount-related properties - const ledgerTransactionsModified = ledgerTransactions.map((tx) => { - const { - satsAmount, - satsFee, - centsAmount, - centsFee, - displayAmount, - displayFee, - displayCurrency, - ...rest - } = tx - - const removed = [ - satsAmount, - satsFee, - centsAmount, - centsFee, - displayAmount, - displayFee, - displayCurrency, - ] - removed // dummy call to satisfy type-checker - - return rest - }) - - const result = WalletTransactionHistory.fromLedger({ - ledgerTransactions: ledgerTransactionsModified, - nonEndUserWalletIds: [], - memoSharingConfig, - }) - - const expected = expectedWalletTxns(txnsArgs) - - // Modify satsAmount-related-dependent properties - const expectedTransactionsModified = expected.map((tx) => { - const { - settlementFee, - settlementDisplayAmount, - settlementDisplayFee, - settlementDisplayPrice, - ...rest - } = tx - - const removed = [ - settlementFee, - settlementDisplayAmount, - settlementDisplayFee, - settlementDisplayPrice, - ] - removed // dummy call to satisfy type-checker - - return { - settlementFee: 0, - settlementDisplayAmount: "0.00", - settlementDisplayFee: "0.00", - settlementDisplayPrice: priceAmountFromNumber({ - priceOfOneSatInMinorUnit: 0, - displayCurrency: UsdDisplayCurrency, - walletCurrency: tx.settlementCurrency, - }), - ...rest, - } - }) - - expect(result.transactions).toEqual(expectedTransactionsModified) - }) - }) -}) - -describe("translateDescription", () => { - const journalIdMemoArgs = { - walletId: "" as WalletId, - journalId: "" as LedgerJournalId, - nonEndUserWalletIds: ["dealerBtcWalletId" as WalletId], - } - - it("return journalId for dealer wallet id", () => { - const journalId = "journal-01" as LedgerJournalId - - const result = translateMemo({ - memoFromPayer: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, - walletId: journalIdMemoArgs.nonEndUserWalletIds[0], - journalId, - nonEndUserWalletIds: journalIdMemoArgs.nonEndUserWalletIds, - memoSharingConfig, - }) - expect(result).toEqual(`JournalId:${journalId}`) - }) - - it("returns the memoFromPayer for BTC wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) - - it("returns memo if there is no memoFromPayer for BTC wallet", () => { - const result = translateMemo({ - lnMemo: "some memo", - credit: MEMO_SHARING_SATS_THRESHOLD, - currency: WalletCurrency.Btc, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) - - it("returns null under spam thresh for BTC wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: 1 as Satoshis, - currency: WalletCurrency.Btc, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toBeNull() - }) - - it("returns memo for debit under spam threshold for BTC wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: 0 as Satoshis, - currency: WalletCurrency.Btc, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) - - it("returns the memoFromPayer for USD wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: MEMO_SHARING_CENTS_THRESHOLD, - currency: WalletCurrency.Usd, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) - - it("returns memo if there is no memoFromPayer for USD wallet", () => { - const result = translateMemo({ - lnMemo: "some memo", - credit: MEMO_SHARING_CENTS_THRESHOLD, - currency: WalletCurrency.Usd, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) - - it("returns null under spam thresh for USD wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: 1 as UsdCents, - currency: WalletCurrency.Usd, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toBeNull() - }) - - it("returns memo for debit under spam threshold for USD wallet", () => { - const result = translateMemo({ - memoFromPayer: "some memo", - credit: 0 as UsdCents, - currency: WalletCurrency.Usd, - memoSharingConfig, - ...journalIdMemoArgs, - }) - expect(result).toEqual("some memo") - }) -}) diff --git a/test/galoy/unit/domain/wallets/withdrawal-fee-calculator.spec.ts b/test/galoy/unit/domain/wallets/withdrawal-fee-calculator.spec.ts deleted file mode 100644 index b1c353227..000000000 --- a/test/galoy/unit/domain/wallets/withdrawal-fee-calculator.spec.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { OnChainFees } from "@domain/wallets" -import { AmountCalculator, WalletCurrency, ZERO_SATS } from "@domain/shared" - -const thresholdImbalance = { - amount: BigInt(1_000_000), - currency: WalletCurrency.Btc, -} - -const calc = AmountCalculator() - -describe("OnChainFees", () => { - describe("onChainWithdrawalFlatFee", () => { - const feeRatioAsBasisPoints = 0n - const calculator = OnChainFees({ - thresholdImbalance, - feeRatioAsBasisPoints, - }) - - it("returns the sum of onchain fee and wallet fee", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(0), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(100_000_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - minerFee, - amount, - minBankFee, - imbalance, - }) - expect(fee.totalFee).toEqual({ amount: 9200n, currency: "BTC" }) - }) - }) - describe("onChainWithdrawalProportionalOnImbalanceFee", () => { - const feeRatioAsBasisPoints = 50n - const calculator = OnChainFees({ - thresholdImbalance, - feeRatioAsBasisPoints, - }) - - it("returns flat fee for no tx", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = ZERO_SATS - const amount = { amount: BigInt(1_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(minBankFee) - }) - it("returns flat fee for loop in imbalance and small amount", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(-2_000_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(1_000_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(minBankFee) - }) - it("returns flat fee for loop out imbalance below threshold, low amount", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(500_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(250_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(minBankFee) - }) - it("returns proportional fee for loop in imbalance and large amount", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(-2_000_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(100_000_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - - const expectedAmount = calc.mulBasisPoints( - calc.sub(calc.add(amount, imbalance), thresholdImbalance), - feeRatioAsBasisPoints, - ) - expect(fee.bankFee).toEqual(expectedAmount) - }) - it("returns proportional fee for small loop out imbalance and large amount", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(500_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(10_000_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual( - calc.mulBasisPoints( - { amount: 9_500_000n, currency: WalletCurrency.Btc }, - feeRatioAsBasisPoints, - ), - ) - }) - it("returns flat fee for loop out imbalance below threshold", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(500_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(250_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(minBankFee) - }) - - it("returns proportional fee for loop out imbalance above threshold, amount < imbalance", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(2_000_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(500_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(calc.mulBasisPoints(amount, feeRatioAsBasisPoints)) - }) - it("returns proportional fee for loop out imbalance above threshold, amount > imbalance", async () => { - const minerFee = { amount: BigInt(7200), currency: WalletCurrency.Btc } - const minBankFee = { amount: BigInt(2000), currency: WalletCurrency.Btc } - const imbalance = { amount: BigInt(2_000_000), currency: WalletCurrency.Btc } - const amount = { amount: BigInt(10_000_000), currency: WalletCurrency.Btc } - const fee = calculator.withdrawalFee({ - amount, - minerFee, - minBankFee, - imbalance, - }) - expect(fee.bankFee).toEqual(calc.mulBasisPoints(amount, feeRatioAsBasisPoints)) - }) - }) - describe("onChainIntraLedgerFee", () => { - const feeRatioAsBasisPoints = 50n - const calculator = OnChainFees({ - thresholdImbalance, - feeRatioAsBasisPoints, - }) - - it("always returns zero", async () => { - const fee = calculator.intraLedgerFees() - expect(fee.btc.amount).toEqual(0n) - expect(fee.usd.amount).toEqual(0n) - }) - }) -}) diff --git a/test/galoy/unit/graphql/index.spec.ts b/test/galoy/unit/graphql/index.spec.ts deleted file mode 100644 index 245c61921..000000000 --- a/test/galoy/unit/graphql/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ExchangeCurrencyUnit, WalletCurrency } from "@domain/shared" -import { normalizePaymentAmount } from "@graphql/shared/root/mutation" - -describe("normalizePaymentAmount", () => { - it("returns the correct type", () => { - const btcPaymentAmount = { - amount: 10n, - currency: WalletCurrency.Btc, - } - expect(normalizePaymentAmount(btcPaymentAmount)).toStrictEqual({ - amount: 10, - currencyUnit: ExchangeCurrencyUnit.Btc, - }) - - const usdPaymentAmount = { - amount: 20n, - currency: WalletCurrency.Usd, - } - expect(normalizePaymentAmount(usdPaymentAmount)).toStrictEqual({ - amount: 20, - currencyUnit: ExchangeCurrencyUnit.Usd, - }) - }) -}) diff --git a/test/galoy/unit/jest.config.js b/test/galoy/unit/jest.config.js deleted file mode 100644 index 6846ecf87..000000000 --- a/test/galoy/unit/jest.config.js +++ /dev/null @@ -1,27 +0,0 @@ -// const swcConfig = require("../../swc-config.json") - -// module.exports = { -// moduleFileExtensions: ["js", "json", "ts", "cjs", "mjs"], -// rootDir: "../../", -// roots: ["/test/unit", "/src"], -// transform: { -// "^.+\\.(t|j)sx?$": ["@swc/jest", swcConfig], -// }, -// testRegex: ".*\\.spec\\.ts$", -// testEnvironment: "node", -// moduleNameMapper: { -// "^@config$": ["src/config/index"], -// "^@app$": ["src/app/index"], -// "^@utils$": ["src/utils/index"], - -// "^@core/(.*)$": ["src/core/$1"], -// "^@app/(.*)$": ["src/app/$1"], -// "^@domain/(.*)$": ["src/domain/$1"], -// "^@services/(.*)$": ["src/services/$1"], -// "^@servers/(.*)$": ["src/servers/$1"], -// "^@graphql/(.*)$": ["src/graphql/$1"], -// "^test/(.*)$": ["test/$1"], -// }, -// // Flash fork: skip galoy tests -// testPathIgnorePatterns: ["/test/galoy/unit/"], -// } diff --git a/test/galoy/unit/ledger/ledger.spec.ts b/test/galoy/unit/ledger/ledger.spec.ts deleted file mode 100644 index fab7b52bd..000000000 --- a/test/galoy/unit/ledger/ledger.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import crypto from "crypto" - -import { liabilitiesMainAccount, toLiabilitiesWalletId, toWalletId } from "@domain/ledger" - -describe("ledger.ts", () => { - describe("resolveWalletId", () => { - const walletId = crypto.randomUUID() as WalletId - it("returns account id from string path", () => { - expect(toWalletId(toLiabilitiesWalletId(walletId))).toEqual(walletId) - }) - - it("returns null if invalid path", () => { - const lowerCasePrefix = liabilitiesMainAccount.toLowerCase() - expect(toWalletId("" as LiabilitiesWalletId)).toBe(undefined) - expect(toWalletId("test" as LiabilitiesWalletId)).toBe(undefined) - expect(toWalletId("test:id" as LiabilitiesWalletId)).toBe(undefined) - expect(toWalletId(`${liabilitiesMainAccount}:` as LiabilitiesWalletId)).toBe( - undefined, - ) - expect(toWalletId(`${lowerCasePrefix}:` as LiabilitiesWalletId)).toBe(undefined) - }) - }) -}) diff --git a/test/galoy/unit/payments/onchain-payment-flow-builder.spec.ts b/test/galoy/unit/payments/onchain-payment-flow-builder.spec.ts deleted file mode 100644 index 23c921cfe..000000000 --- a/test/galoy/unit/payments/onchain-payment-flow-builder.spec.ts +++ /dev/null @@ -1,1265 +0,0 @@ -import { getFeesConfig, getOnChainWalletConfig } from "@config" - -import { SettlementMethod, PaymentInitiationMethod, OnChainFees } from "@domain/wallets" -import { LessThanDustThresholdError, SelfPaymentError } from "@domain/errors" -import { - InvalidOnChainPaymentFlowBuilderStateError, - SubOneCentSatAmountForUsdSelfSendError, - WalletPriceRatio, -} from "@domain/payments" -import { - ONE_CENT, - paymentAmountFromNumber, - ValidationError, - WalletCurrency, -} from "@domain/shared" -import { OnChainPaymentFlowBuilder } from "@domain/payments/onchain-payment-flow-builder" -import { toSats } from "@domain/bitcoin" -import { ImbalanceCalculator } from "@domain/ledger/imbalance-calculator" - -const feeConfig = getFeesConfig() -const { dustThreshold } = getOnChainWalletConfig() - -describe("OnChainPaymentFlowBuilder", () => { - const address = "address" as OnChainAddress - const uncheckedAmount = 10000 - const dustAmount = dustThreshold - 1 - - const senderBtcWalletDescriptor = { - id: "senderBtcWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "senderAccountId" as AccountId, - } - - const senderUsdWalletDescriptor = { - id: "senderUsdWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "senderAccountId" as AccountId, - } - - const senderAsRecipientCommonArgs = { - userId: "senderUserId" as UserId, - recipientWalletDescriptorsForAccount: [ - senderBtcWalletDescriptor, - senderUsdWalletDescriptor, - ], - } - const senderBtcAsRecipientArgs = { - ...senderAsRecipientCommonArgs, - recipientWalletDescriptor: senderBtcWalletDescriptor, - } - - const senderUsdAsRecipientArgs = { - ...senderAsRecipientCommonArgs, - recipientWalletDescriptor: senderUsdWalletDescriptor, - } - - const senderAccount = { withdrawFee: toSats(100) } as Account - - const recipientBtcWalletDescriptor = { - id: "recipientBtcWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "recipientAccountId" as AccountId, - } - const recipientUsdWalletDescriptor = { - id: "recipientUsdWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "recipientAccountId" as AccountId, - } - - const recipientCommonArgs = { - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - username: "Username" as Username, - userId: "recipientUserId" as UserId, - } - - const recipientBtcArgs = { - ...recipientCommonArgs, - recipientWalletDescriptor: recipientBtcWalletDescriptor, - } - - const recipientUsdArgs = { - ...recipientCommonArgs, - recipientWalletDescriptor: recipientUsdWalletDescriptor, - } - - // 0.02 ratio (0.02 cents/sat, or $20,000 USD/BTC) - const midPriceRatio = 0.02 - const immediateSpread = 0.001 // 0.10 % - // const futureSpread = 0.0012 // 0.12% - - const centsFromSats = ({ sats, spread, round }): bigint => - BigInt(round(sats * midPriceRatio * spread)) - const satsFromCents = ({ cents, spread, round }): bigint => - BigInt(round((cents / midPriceRatio) * spread)) - - const usdFromBtcMid = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSats({ - sats: Number(amount.amount), - spread: 1, - round: Math.round, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdMid = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1, - round: Math.round, - }), - currency: WalletCurrency.Btc, - }) - } - const mid = { usdFromBtc: usdFromBtcMid, btcFromUsd: btcFromUsdMid } - - const usdFromBtcBuy = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSats({ - sats: Number(amount.amount), - spread: 1 - immediateSpread, - round: Math.floor, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdBuy = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1 + immediateSpread, - round: Math.ceil, - }), - currency: WalletCurrency.Btc, - }) - } - const hedgeBuyUsd = { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - } - - const usdFromBtcSell = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSats({ - sats: Number(amount.amount), - spread: 1 + immediateSpread, - round: Math.ceil, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdSell = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1 - immediateSpread, - round: Math.floor, - }), - currency: WalletCurrency.Btc, - }) - } - const hedgeSellUsd = { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - } - - const withConversionArgs = { - mid, - hedgeBuyUsd, - hedgeSellUsd, - } - - const volumeLightningFn = async () => - Promise.resolve({ - outgoingBaseAmount: toSats(1000), - incomingBaseAmount: toSats(1000), - }) - - const volumeOnChainFn = async () => - Promise.resolve({ - outgoingBaseAmount: toSats(1000), - incomingBaseAmount: toSats(1000), - }) - - describe("onchain initiated", () => { - const onChainBuilder = OnChainPaymentFlowBuilder({ - volumeLightningFn, - volumeOnChainFn, - isExternalAddress: async (state) => - /* eslint @typescript-eslint/ban-ts-comment: "off" */ - // @ts-ignore-next-line error - Promise.resolve(!state.recipientWalletId), - sendAll: false, - dustThreshold, - }) - - describe("with address", () => { - const withAddressBuilder = onChainBuilder.withAddress(address) - const checkAddress = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - address, - }), - ) - } - - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAddressBuilder.withSenderWalletAndAccount({ - wallet: senderBtcWalletDescriptor, - account: senderAccount, - }) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - senderWithdrawFee: senderAccount.withdrawFee, - }), - ) - } - - const thresholdImbalanceAmount = paymentAmountFromNumber({ - amount: feeConfig.withdrawThreshold, - currency: WalletCurrency.Btc, - }) - if (thresholdImbalanceAmount instanceof Error) throw thresholdImbalanceAmount - - const onChainFees = OnChainFees({ - feeRatioAsBasisPoints: feeConfig.withdrawRatioAsBasisPoints, - thresholdImbalance: thresholdImbalanceAmount, - }) - - const amountCurrency = WalletCurrency.Btc - describe("onchain settled", () => { - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.OnChain, - paymentInitiationMethod: PaymentInitiationMethod.OnChain, - }), - ) - } - describe("without recipient wallet", () => { - const convertForBtcWalletToBtcAddress = mid - - describe("with amount", () => { - const withAmountBuilder = withBtcWalletBuilder - .withoutRecipientWallet() - .withAmount({ amount: BigInt(uncheckedAmount), currency: amountCurrency }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(uncheckedAmount), - }), - ) - } - - it("correctly applies miner, bank and imbalance fees", async () => { - const minerFee = { amount: 300n, currency: WalletCurrency.Btc } - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(uncheckedAmount), - currency: WalletCurrency.Btc, - } - - const usdPaymentAmount = - await convertForBtcWalletToBtcAddress.usdFromBtc(btcPaymentAmount) - - const sendAmount = paymentAmountFromNumber({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - if (sendAmount instanceof Error) throw sendAmount - - const minBankFee = paymentAmountFromNumber({ - amount: senderAccount.withdrawFee, - currency: WalletCurrency.Btc, - }) - if (minBankFee instanceof Error) throw minBankFee - - const imbalanceCalculator = ImbalanceCalculator({ - method: feeConfig.withdrawMethod, - volumeLightningFn, - volumeOnChainFn, - sinceDaysAgo: feeConfig.withdrawDaysLookback, - }) - const imbalance = await imbalanceCalculator.getSwapOutImbalanceAmount( - senderBtcWalletDescriptor, - ) - if (imbalance instanceof Error) throw imbalance - - const withdrawFees = onChainFees.withdrawalFee({ - minerFee, - amount: sendAmount, - minBankFee, - imbalance, - }) - if (withdrawFees instanceof Error) throw withdrawFees - - const walletPriceRatio = WalletPriceRatio({ - usd: usdPaymentAmount, - btc: btcPaymentAmount, - }) - if (walletPriceRatio instanceof Error) throw walletPriceRatio - - const btcProtocolAndBankFee = withdrawFees.totalFee - const usdProtocolAndBankFee = - await walletPriceRatio.convertFromBtcToCeil(btcProtocolAndBankFee) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee, - usdProtocolAndBankFee, - }), - ) - }) - }) - - describe("with dust amount", () => { - it("correctly returns dust error", async () => { - const minerFee = { amount: 300n, currency: WalletCurrency.Btc } - - const paymentLowest = await withBtcWalletBuilder - .withoutRecipientWallet() - .withAmount({ amount: BigInt(51), currency: amountCurrency }) // Close to 1 cent - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentLowest).toBeInstanceOf(LessThanDustThresholdError) - - const paymentBelow = await withBtcWalletBuilder - .withoutRecipientWallet() - .withAmount({ amount: BigInt(dustAmount), currency: amountCurrency }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentBelow).toBeInstanceOf(LessThanDustThresholdError) - - const paymentAbove = await withBtcWalletBuilder - .withoutRecipientWallet() - .withAmount({ - amount: BigInt(dustAmount + 1), - currency: amountCurrency, - }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentAbove).not.toBeInstanceOf(Error) - }) - }) - }) - }) - - describe("intraledger settled", () => { - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.IntraLedger, - paymentInitiationMethod: PaymentInitiationMethod.OnChain, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - } - - describe("with btc recipient wallet", () => { - const convertForBtcWalletToBtcWallet = mid - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - - const withBtcRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - - it("correctly applies no fees, with normal amount", async () => { - const amount = uncheckedAmount - const withAmountBuilder = withBtcRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToBtcWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with dust amount", async () => { - const amount = dustAmount - const withAmountBuilder = withBtcRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToBtcWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with min amount", async () => { - const amount = 51 // Close to 1 cent - const withAmountBuilder = withBtcRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToBtcWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - }) - - describe("with usd recipient wallet", () => { - const convertForBtcWalletToUsdWallet = hedgeBuyUsd - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - const withUsdRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithUsdRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithSelfUsdRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(senderUsdAsRecipientArgs) - - it("correctly applies no fees, with normal amount", async () => { - const amount = uncheckedAmount - const withAmountBuilder = withUsdRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToUsdWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with dust amount", async () => { - const amount = dustAmount - const withAmountBuilder = withUsdRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToUsdWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with min amount", async () => { - const amount = 51 // Close to 1 cent - const withAmountBuilder = withUsdRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = { - amount: BigInt(amount), - currency: WalletCurrency.Btc, - } - const usdPaymentAmount = - await convertForBtcWalletToUsdWallet.usdFromBtc(btcPaymentAmount) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("credits amount less than 1 cent amount to recipient btc wallet", async () => { - const amount = 1 - const paymentFlow = await lessThan1CentWithUsdRecipientBuilder - .withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - .withConversion(withConversionArgs) - .withoutMinerFee() - if (paymentFlow instanceof Error) throw paymentFlow - - const { walletDescriptor: recipientWalletDescriptor } = - paymentFlow.recipientDetails() - expect(recipientWalletDescriptor).toStrictEqual( - recipientBtcWalletDescriptor, - ) - }) - - it("fails to send less than 1 cent to self", async () => { - const amount = 1 - const paymentFlow = await lessThan1CentWithSelfUsdRecipientBuilder - .withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - .withConversion(withConversionArgs) - .withoutMinerFee() - expect(paymentFlow).toBeInstanceOf(SubOneCentSatAmountForUsdSelfSendError) - }) - }) - }) - }) - - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAddressBuilder.withSenderWalletAndAccount({ - wallet: senderUsdWalletDescriptor, - account: senderAccount, - }) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - senderWithdrawFee: senderAccount.withdrawFee, - }), - ) - } - - const thresholdImbalanceAmount = paymentAmountFromNumber({ - amount: feeConfig.withdrawThreshold, - currency: WalletCurrency.Btc, - }) - if (thresholdImbalanceAmount instanceof Error) throw thresholdImbalanceAmount - - const onChainFees = OnChainFees({ - feeRatioAsBasisPoints: feeConfig.withdrawRatioAsBasisPoints, - thresholdImbalance: thresholdImbalanceAmount, - }) - - const amountCurrencyCases = [ - { amountCurrency: WalletCurrency.Usd, uncheckedAmount: 10_000 }, - { amountCurrency: WalletCurrency.Btc, uncheckedAmount: 499_500 }, - ] - for (const { amountCurrency, uncheckedAmount } of amountCurrencyCases) { - describe(`${amountCurrency.toLowerCase()} send amount`, () => { - describe("onchain settled", () => { - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.OnChain, - paymentInitiationMethod: PaymentInitiationMethod.OnChain, - }), - ) - } - - describe("without recipient wallet", () => { - const convertForUsdWalletToBtcAddress = hedgeSellUsd - const convertReverseForUsdWalletToBtcAddress = mid // an approximation to reverse functions - - describe("with amount", () => { - const withAmountBuilder = withUsdWalletBuilder - .withoutRecipientWallet() - .withAmount({ - amount: BigInt(uncheckedAmount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(uncheckedAmount), - }), - ) - } - - it("correctly applies miner, bank and imbalance fees", async () => { - const minerFee = { amount: 300n, currency: WalletCurrency.Btc } - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - if (payment instanceof Error) throw payment - - const sendAmount = paymentAmountFromNumber({ - amount: uncheckedAmount, - currency: amountCurrency, - }) - if (sendAmount instanceof Error) throw sendAmount - - const btcPaymentAmount = - amountCurrency === WalletCurrency.Btc - ? (sendAmount as BtcPaymentAmount) - : await convertForUsdWalletToBtcAddress.btcFromUsd( - sendAmount as UsdPaymentAmount, - ) - - const usdPaymentAmount = - amountCurrency === WalletCurrency.Usd - ? (sendAmount as UsdPaymentAmount) - : await convertForUsdWalletToBtcAddress.usdFromBtc( - sendAmount as BtcPaymentAmount, - ) - - const minBankFee = paymentAmountFromNumber({ - amount: senderAccount.withdrawFee, - currency: WalletCurrency.Btc, - }) - if (minBankFee instanceof Error) throw minBankFee - - const imbalanceCalculator = ImbalanceCalculator({ - method: feeConfig.withdrawMethod, - volumeLightningFn, - volumeOnChainFn, - sinceDaysAgo: feeConfig.withdrawDaysLookback, - }) - const imbalanceForWallet = - await imbalanceCalculator.getSwapOutImbalanceAmount( - senderUsdWalletDescriptor, - ) - if (imbalanceForWallet instanceof Error) throw imbalanceForWallet - - const imbalance = await convertForUsdWalletToBtcAddress.btcFromUsd({ - amount: imbalanceForWallet.amount, - currency: WalletCurrency.Usd, - }) - - const withdrawalFees = onChainFees.withdrawalFee({ - minerFee, - amount: btcPaymentAmount, - minBankFee, - imbalance, - }) - if (withdrawalFees instanceof Error) throw withdrawalFees - const btcProtocolAndBankFee = withdrawalFees.totalFee - - const priceRatio = WalletPriceRatio({ - usd: usdPaymentAmount, - btc: btcPaymentAmount, - }) - if (priceRatio instanceof Error) throw priceRatio - const usdProtocolAndBankFee = - priceRatio.convertFromBtcToCeil(btcProtocolAndBankFee) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee, - usdProtocolAndBankFee, - }), - ) - }) - }) - - describe("with dust amount", () => { - it("correctly returns dust error", async () => { - const dustBtcAmount = { - amount: BigInt(dustAmount), - currency: WalletCurrency.Btc, - } - const dustUsdAmount = - await convertReverseForUsdWalletToBtcAddress.usdFromBtc( - dustBtcAmount, - ) - - expect(dustUsdAmount.amount).toBeGreaterThan(1n) - const minerFee = { amount: 300n, currency: WalletCurrency.Btc } - - const paymentLowest = await withUsdWalletBuilder - .withoutRecipientWallet() - .withAmount({ amount: BigInt(1), currency: amountCurrency }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentLowest).toBeInstanceOf(LessThanDustThresholdError) - - const paymentBelow = await withUsdWalletBuilder - .withoutRecipientWallet() - .withAmount({ - amount: BigInt(dustUsdAmount.amount), - currency: amountCurrency, - }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentBelow).toBeInstanceOf(LessThanDustThresholdError) - - const dustSendAmount = - amountCurrency === WalletCurrency.Btc - ? dustBtcAmount - : dustUsdAmount - const paymentAbove = await withUsdWalletBuilder - .withoutRecipientWallet() - .withAmount({ - amount: BigInt(dustSendAmount.amount + 1n), - currency: amountCurrency, - }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - expect(paymentAbove).not.toBeInstanceOf(Error) - }) - }) - }) - }) - - describe("intraledger settled", () => { - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.IntraLedger, - paymentInitiationMethod: PaymentInitiationMethod.OnChain, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - } - - describe("with btc recipient wallet", () => { - const convertForUsdWalletToBtcWallet = hedgeSellUsd - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - - const withBtcRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientBtcArgs) - - it("correctly applies no fees, with normal amount", async () => { - const amount = uncheckedAmount - const withAmountBuilder = withBtcRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const sendAmount = paymentAmountFromNumber({ - amount, - currency: amountCurrency, - }) - if (sendAmount instanceof Error) throw sendAmount - - const btcPaymentAmount = - amountCurrency === WalletCurrency.Btc - ? (sendAmount as BtcPaymentAmount) - : await convertForUsdWalletToBtcWallet.btcFromUsd( - sendAmount as UsdPaymentAmount, - ) - - const usdPaymentAmount = - amountCurrency === WalletCurrency.Usd - ? (sendAmount as UsdPaymentAmount) - : await convertForUsdWalletToBtcWallet.usdFromBtc( - sendAmount as BtcPaymentAmount, - ) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with dust amount", async () => { - const amount = 1 - const withAmountBuilder = withBtcRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const sendAmount = paymentAmountFromNumber({ - amount, - currency: amountCurrency, - }) - if (sendAmount instanceof Error) throw sendAmount - - const btcPaymentAmount = - amountCurrency === WalletCurrency.Btc - ? (sendAmount as BtcPaymentAmount) - : await convertForUsdWalletToBtcWallet.btcFromUsd( - sendAmount as UsdPaymentAmount, - ) - - const usdPaymentAmount = - amountCurrency === WalletCurrency.Usd - ? (sendAmount as UsdPaymentAmount) - : await convertForUsdWalletToBtcWallet.usdFromBtc( - sendAmount as BtcPaymentAmount, - ) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - }) - - describe("with usd recipient wallet", () => { - const convertForUsdWalletToUsdWallet = mid - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - const withUsdRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientUsdArgs) - - it("correctly applies no fees, with normal amount", async () => { - const amount = uncheckedAmount - const withAmountBuilder = withUsdRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const sendAmount = paymentAmountFromNumber({ - amount, - currency: amountCurrency, - }) - if (sendAmount instanceof Error) throw sendAmount - - const btcPaymentAmount = - amountCurrency === WalletCurrency.Btc - ? (sendAmount as BtcPaymentAmount) - : await convertForUsdWalletToUsdWallet.btcFromUsd( - sendAmount as UsdPaymentAmount, - ) - - const usdPaymentAmount = - amountCurrency === WalletCurrency.Usd - ? (sendAmount as UsdPaymentAmount) - : sendAmount.amount === 1n - ? ONE_CENT - : await convertForUsdWalletToUsdWallet.usdFromBtc( - sendAmount as BtcPaymentAmount, - ) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - - it("correctly applies no fees, with dust amount", async () => { - const amount = 1 - const withAmountBuilder = withUsdRecipientBuilder.withAmount({ - amount: BigInt(amount), - currency: amountCurrency, - }) - const checkInputAmount = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: BigInt(amount), - }), - ) - } - - const payment = await withAmountBuilder - .withConversion(withConversionArgs) - .withoutMinerFee() - if (payment instanceof Error) throw payment - - const sendAmount = paymentAmountFromNumber({ - amount, - currency: amountCurrency, - }) - if (sendAmount instanceof Error) throw sendAmount - - const btcPaymentAmount = - amountCurrency === WalletCurrency.Btc - ? (sendAmount as BtcPaymentAmount) - : await convertForUsdWalletToUsdWallet.btcFromUsd( - sendAmount as UsdPaymentAmount, - ) - - const usdPaymentAmount = - amountCurrency === WalletCurrency.Usd - ? (sendAmount as UsdPaymentAmount) - : sendAmount.amount === 1n - ? ONE_CENT - : await convertForUsdWalletToUsdWallet.usdFromBtc( - sendAmount as BtcPaymentAmount, - ) - - checkAddress(payment) - checkSettlementMethod(payment) - checkInputAmount(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - usdPaymentAmount, - btcProtocolAndBankFee: onChainFees.intraLedgerFees().btc, - usdProtocolAndBankFee: onChainFees.intraLedgerFees().usd, - }), - ) - }) - }) - }) - }) - } - }) - }) - }) - - describe("error states", () => { - const amountCurrency = WalletCurrency.Btc - - describe("zero-value uncheckedAmount", () => { - it("returns a ValidationError", async () => { - const isIntraLedger = false - const minerFee = { amount: 300n, currency: WalletCurrency.Btc } - - const payment = await OnChainPaymentFlowBuilder({ - volumeLightningFn, - volumeOnChainFn, - isExternalAddress: async () => Promise.resolve(!isIntraLedger), - sendAll: false, - dustThreshold, - }) - .withAddress("address" as OnChainAddress) - .withSenderWalletAndAccount({ - wallet: senderBtcWalletDescriptor, - account: senderAccount, - }) - .withoutRecipientWallet() - .withAmount({ amount: BigInt(0), currency: amountCurrency }) - .withConversion(withConversionArgs) - .withMinerFee(minerFee) - - expect(payment).toBeInstanceOf(ValidationError) - }) - }) - - describe("no recipient wallet despite IntraLedger", () => { - it("returns InvalidLightningPaymentFlowBuilderStateError", async () => { - const payment = await OnChainPaymentFlowBuilder({ - volumeLightningFn, - volumeOnChainFn, - isExternalAddress: async () => Promise.resolve(false), - sendAll: false, - dustThreshold, - }) - .withAddress("address" as OnChainAddress) - .withSenderWalletAndAccount({ - wallet: senderBtcWalletDescriptor, - account: senderAccount, - }) - .withoutRecipientWallet() - .withAmount({ amount: BigInt(uncheckedAmount), currency: amountCurrency }) - .withConversion(withConversionArgs) - .withoutMinerFee() - - expect(payment).toBeInstanceOf(InvalidOnChainPaymentFlowBuilderStateError) - }) - }) - - describe("sender and recipient are identical", () => { - it("returns ImpossibleLightningPaymentFlowBuilderStateError", async () => { - const payment = await OnChainPaymentFlowBuilder({ - volumeLightningFn, - volumeOnChainFn, - isExternalAddress: async () => Promise.resolve(false), - sendAll: false, - dustThreshold, - }) - .withAddress("address" as OnChainAddress) - .withSenderWalletAndAccount({ - wallet: senderBtcWalletDescriptor, - account: senderAccount, - }) - .withRecipientWallet(senderBtcAsRecipientArgs) - .withAmount({ amount: BigInt(uncheckedAmount), currency: amountCurrency }) - .withConversion(withConversionArgs) - .withoutMinerFee() - - expect(payment).toBeInstanceOf(SelfPaymentError) - }) - }) - - describe("btcProposedAmount below dust from withConversion builder", () => { - it("returns LessThanDustThresholdError", async () => { - const builder = await OnChainPaymentFlowBuilder({ - volumeLightningFn, - volumeOnChainFn, - isExternalAddress: async () => Promise.resolve(true), - sendAll: false, - dustThreshold, - }) - .withAddress("address" as OnChainAddress) - .withSenderWalletAndAccount({ - wallet: senderBtcWalletDescriptor, - account: senderAccount, - }) - .withoutRecipientWallet() - .withAmount({ amount: BigInt(dustAmount), currency: amountCurrency }) - .withConversion(withConversionArgs) - - const proposedBtcAmount = await builder.btcProposedAmount() - expect(proposedBtcAmount).toBeInstanceOf(LessThanDustThresholdError) - - const proposedAmounts = await builder.proposedAmounts() - expect(proposedAmounts).toBeInstanceOf(LessThanDustThresholdError) - }) - }) - }) -}) diff --git a/test/galoy/unit/payments/payment-flow-builder.spec.ts b/test/galoy/unit/payments/payment-flow-builder.spec.ts deleted file mode 100644 index c45611038..000000000 --- a/test/galoy/unit/payments/payment-flow-builder.spec.ts +++ /dev/null @@ -1,1634 +0,0 @@ -/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "checkSettlementMethod", "checkInvoice", "checkSenderWallet", "checkRecipientWallet"] }] */ -import { SettlementMethod, PaymentInitiationMethod } from "@domain/wallets" -import { decodeInvoice } from "@domain/bitcoin/lightning" -import { SelfPaymentError } from "@domain/errors" -import { - LightningPaymentFlowBuilder, - LnFees, - InvalidLightningPaymentFlowBuilderStateError, - WalletPriceRatio, - SubOneCentSatAmountForUsdSelfSendError, -} from "@domain/payments" -import { ONE_CENT, ValidationError, WalletCurrency } from "@domain/shared" - -const skippedPubkey = - "038f8f113c580048d847d6949371726653e02b928196bad310e3eda39ff61723f6" as Pubkey -const skippedChanId = "1x0x0" as ChanId -const skipProbe = { pubkey: [skippedPubkey], chanId: [skippedChanId] } - -describe("LightningPaymentFlowBuilder", () => { - const paymentRequestWithAmount = - "lnbc210u1p32zq9xpp5dpzhj6e7y6d4ggs6awh7m4eupuemas0gq06pqjgy9tq35740jlfsdqqcqzpgxqyz5vqsp58t3zalj5sc563g0xpcgx9lfkeqrx7m7xw53v2txc2pr60jcwn0vq9qyyssqkatadajwt0n285teummg4urul9t3shddnf05cfxzsfykvscxm4zqz37j87sahvz3kul0lzgz2svltdm933yr96du84zpyn8rx6fst4sp43jh32" as EncodedPaymentRequest - const invoiceWithAmount = decodeInvoice(paymentRequestWithAmount) as LnInvoice - - const skippedPubkeyPaymentRequestWithAmount = - "lnbc10u1p3w0mf7pp5v9xg3eksnsyrsa3vk5uv00rvye4wf9n0744xgtx0kcrafeanvx7sdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhddrwgrqy63w5eyqqqqryqqqqthqqpyrzjqw8c7yfutqqy3kz8662fxutjvef7q2ujsxtt45csu0k688lkzu3lddrwgrqy63w5eyqqqqryqqqqthqqpysp53n0sc9hvqgdkrv4ppwrm2pa0gcysa8r2swjkrkjnxkcyrsjmxu4s9qypqsq5zvh7glzpas4l9ptxkdhgefyffkn8humq6amkrhrh2gq02gv8emxrynkwke3uwgf4cfevek89g4020lgldxgusmse79h4caqg30qq2cqmyrc7d" as EncodedPaymentRequest - const skippedPubkeyInvoiceWithAmount = decodeInvoice( - skippedPubkeyPaymentRequestWithAmount, - ) as LnInvoice - - const skippedChanIdPaymentRequestWithAmount = - "lnbc1m1pjz2963pp5eeed387k90rxz9ggkarh3qzf42tw5epay0v3adv79aldgjf2a0nqdqqcqzpgxqrrssrzjqvgptfurj3528snx6e3dtwepafxw5fpzdymw9pj20jj09sunnqmwqqqqqyqqqqqqqqqqqqlgqqqqqqgqjqnp4qdruvn0zq9wqqhtryvch753zm6hqq4kyt48dsstkemjjc3njvggnqsp5s4pla42w34ekurw8ywfwjpwcakz5h3ynn8hx5znfckda8udmn5sq9qyyssq4sll8vh2n6kds0ht7l942jqa33nrrrhd9fhfdrdfec6mwtms05ppdrnztn2zg87cm4q7lye39f0gmt9tpjwy26hafrkqza4esjmctuqpxchx3a" as EncodedPaymentRequest - const skippedChanIdInvoiceWithAmount = decodeInvoice( - skippedChanIdPaymentRequestWithAmount, - ) as LnInvoice - - const paymentRequestWithNoAmount = - "lnbc1p3zn402pp54skf32qeal5jnfm73u5e3d9h5448l4yutszy0kr9l56vdsy8jefsdqqcqzpuxqyz5vqsp5c6z7a4lrey4ejvhx5q4l83jm9fhy34dsqgxnceem4dgz6fmh456s9qyyssqkxkg6ke6nt39dusdhpansu8j0r5f7gadwcampnw2g8ap0fccteer7hzjc8tgat9m5wxd98nxjxhwx0ha6g95v9edmgd30f0m8kujslgpxtzt6w" as EncodedPaymentRequest - const invoiceWithNoAmount = decodeInvoice(paymentRequestWithNoAmount) as LnInvoice - - const senderBtcWalletDescriptor = { - id: "senderBtcWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "senderAccountId" as AccountId, - } - - const senderUsdWalletDescriptor = { - id: "senderUsdWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "senderAccountId" as AccountId, - } - - const senderAsRecipientCommonArgs = { - userId: "senderUserId" as UserId, - recipientWalletDescriptorsForAccount: [ - senderBtcWalletDescriptor, - senderUsdWalletDescriptor, - ], - } - const senderBtcAsRecipientArgs = { - ...senderAsRecipientCommonArgs, - recipientWalletDescriptor: senderBtcWalletDescriptor, - } - - const senderUsdAsRecipientArgs = { - ...senderAsRecipientCommonArgs, - recipientWalletDescriptor: senderUsdWalletDescriptor, - } - - const recipientBtcWalletDescriptor = { - id: "recipientBtcWalletId" as WalletId, - currency: WalletCurrency.Btc, - accountId: "recipientAccountId" as AccountId, - } - const recipientUsdWalletDescriptor = { - id: "recipientUsdWalletId" as WalletId, - currency: WalletCurrency.Usd, - accountId: "recipientAccountId" as AccountId, - } - - const recipientCommonArgs = { - recipientWalletDescriptorsForAccount: [ - recipientBtcWalletDescriptor, - recipientUsdWalletDescriptor, - ], - username: "Username" as Username, - userId: "recipientUserId" as UserId, - } - - const recipientBtcArgs = { - ...recipientCommonArgs, - recipientWalletDescriptor: recipientBtcWalletDescriptor, - } - - const recipientUsdArgs = { - ...recipientCommonArgs, - recipientWalletDescriptor: recipientUsdWalletDescriptor, - } - - const pubkey = "pubkey" as Pubkey - const rawRoute = { total_mtokens: "21000000", safe_fee: 210 } as RawRoute - - // 0.02 ratio (0.02 cents/sat, or $20,000 USD/BTC) - const midPriceRatio = 0.02 - const immediateSpread = 0.001 // 0.10 % - // const futureSpread = 0.0012 // 0.12% - - const centsFromSatsForMid = ({ sats, spread, round }): bigint => { - if (Number(sats) === 0) return 0n - - const result = BigInt(round(sats * midPriceRatio * spread)) - return result || 1n - } - - const centsFromSats = ({ sats, spread, round }): bigint => - BigInt(round(sats * midPriceRatio * spread)) - const satsFromCents = ({ cents, spread, round }): bigint => - BigInt(round((cents / midPriceRatio) * spread)) - - const usdFromBtcMid = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSatsForMid({ - sats: Number(amount.amount), - spread: 1, - round: Math.round, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdMid = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1, - round: Math.round, - }), - currency: WalletCurrency.Btc, - }) - } - const mid = { usdFromBtc: usdFromBtcMid, btcFromUsd: btcFromUsdMid } - - const usdFromBtcBuy = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSats({ - sats: Number(amount.amount), - spread: 1 - immediateSpread, - round: Math.floor, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdBuy = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1 + immediateSpread, - round: Math.ceil, - }), - currency: WalletCurrency.Btc, - }) - } - const hedgeBuyUsd = { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - } - - const usdFromBtcSell = async (amount: BtcPaymentAmount) => { - return Promise.resolve({ - amount: centsFromSats({ - sats: Number(amount.amount), - spread: 1 + immediateSpread, - round: Math.ceil, - }), - currency: WalletCurrency.Usd, - }) - } - const btcFromUsdSell = async (amount: UsdPaymentAmount) => { - return Promise.resolve({ - amount: satsFromCents({ - cents: Number(amount.amount), - spread: 1 - immediateSpread, - round: Math.floor, - }), - currency: WalletCurrency.Btc, - }) - } - const hedgeSellUsd = { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - } - - describe("ln initiated, ln settled", () => { - const lightningBuilder = LightningPaymentFlowBuilder({ - localNodeIds: [], - skipProbe, - }) - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.Lightning, - paymentInitiationMethod: PaymentInitiationMethod.Lightning, - }), - ) - } - - describe("invoice with amount", () => { - const withAmountBuilder = lightningBuilder.withInvoice(invoiceWithAmount) - const withSkippedPubkeyAmountBuilder = lightningBuilder.withInvoice( - skippedPubkeyInvoiceWithAmount, - ) - const withSkippedChanIdAmountBuilder = lightningBuilder.withInvoice( - skippedChanIdInvoiceWithAmount, - ) - const checkInvoice = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount: invoiceWithAmount.paymentAmount, - inputAmount: invoiceWithAmount.paymentAmount?.amount, - }), - ) - } - - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAmountBuilder - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - - const withSkippedPubkeyBtcWalletBuilder = withSkippedPubkeyAmountBuilder - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - - const withSkippedChanIdBtcWalletBuilder = withSkippedChanIdAmountBuilder - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - }), - ) - } - - it("sets 'skipProbe' property to true for skipProbe destination invoice", async () => { - const skippedPubkeyBuilder = - await withSkippedPubkeyBtcWalletBuilder.withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - expect(skippedPubkeyBuilder.skipProbeForDestination()).toBeTruthy() - - const skippedChanIdBuilder = - await withSkippedChanIdBtcWalletBuilder.withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - expect(skippedChanIdBuilder.skipProbeForDestination()).toBeTruthy() - }) - - it("uses mid price and max btc fees", async () => { - const payment = await withBtcWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - if (invoiceWithAmount.paymentAmount === null) - throw new Error("paymentAmount should not be null") - - const usdPaymentAmount = await usdFromBtcMid(invoiceWithAmount.paymentAmount) - - const btcProtocolAndBankFee = LnFees().maxProtocolAndBankFee( - invoiceWithAmount.paymentAmount, - ) - if (btcProtocolAndBankFee instanceof Error) return btcProtocolAndBankFee - expect(btcProtocolAndBankFee).not.toBeInstanceOf(Error) - - const priceRatio = WalletPriceRatio(payment.paymentAmounts()) - if (priceRatio instanceof Error) throw priceRatio - const usdProtocolAndBankFee = - priceRatio.convertFromBtcToCeil(btcProtocolAndBankFee) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - btcProtocolAndBankFee, - usdProtocolAndBankFee, - skipProbeForDestination: false, - }), - ) - }) - - it("can take fees from a route", async () => { - const payment = await withBtcWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withRoute({ - pubkey, - rawRoute, - }) - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - - const btcProtocolAndBankFee = LnFees().feeFromRawRoute(rawRoute) - if (btcProtocolAndBankFee instanceof Error) return btcProtocolAndBankFee - expect(btcProtocolAndBankFee).not.toBeInstanceOf(Error) - - const priceRatio = WalletPriceRatio(payment.paymentAmounts()) - if (priceRatio instanceof Error) throw priceRatio - - expect(payment).toEqual( - expect.objectContaining({ - btcProtocolAndBankFee, - usdProtocolAndBankFee: - priceRatio.convertFromBtcToCeil(btcProtocolAndBankFee), - outgoingNodePubkey: pubkey, - cachedRoute: rawRoute, - skipProbeForDestination: false, - }), - ) - - const { rawRoute: returnedRawRoute, outgoingNodePubkey } = - payment.routeDetails() - expect(returnedRawRoute).toStrictEqual(rawRoute) - expect(outgoingNodePubkey).toBe(pubkey) - }) - }) - - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAmountBuilder - .withSenderWallet(senderUsdWalletDescriptor) - .withoutRecipientWallet() - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - skipProbeForDestination: false, - }), - ) - } - - it("uses dealer price and max btc fees", async () => { - const payment = await withUsdWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - if (invoiceWithAmount.paymentAmount === null) - throw new Error("paymentAmount should not be null") - - const usdPaymentAmount = await usdFromBtcSell(invoiceWithAmount.paymentAmount) - - const btcProtocolAndBankFee = LnFees().maxProtocolAndBankFee( - invoiceWithAmount.paymentAmount, - ) - if (btcProtocolAndBankFee instanceof Error) return btcProtocolAndBankFee - expect(btcProtocolAndBankFee).not.toBeInstanceOf(Error) - - const usdProtocolAndBankFee = await usdFromBtcSell(btcProtocolAndBankFee) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - btcProtocolAndBankFee, - usdProtocolAndBankFee, - skipProbeForDestination: false, - }), - ) - }) - - it("can take fees from a route", async () => { - const payment = await withUsdWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withRoute({ - pubkey, - rawRoute, - }) - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - - const btcProtocolAndBankFee = LnFees().feeFromRawRoute(rawRoute) - if (btcProtocolAndBankFee instanceof Error) return btcProtocolAndBankFee - expect(btcProtocolAndBankFee).not.toBeInstanceOf(Error) - - expect(payment).toEqual( - expect.objectContaining({ - btcProtocolAndBankFee, - usdProtocolAndBankFee: await usdFromBtcSell(btcProtocolAndBankFee), - outgoingNodePubkey: pubkey, - cachedRoute: rawRoute, - skipProbeForDestination: false, - }), - ) - - const { rawRoute: returnedRawRoute, outgoingNodePubkey } = - payment.routeDetails() - expect(returnedRawRoute).toStrictEqual(rawRoute) - expect(outgoingNodePubkey).toBe(pubkey) - }) - }) - }) - - describe("invoice with no amount", () => { - const uncheckedAmount = 10000n - const withAmountBuilder = lightningBuilder.withNoAmountInvoice({ - invoice: invoiceWithNoAmount, - uncheckedAmount: Number(uncheckedAmount), - }) - const checkInvoice = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: uncheckedAmount, - skipProbeForDestination: false, - }), - ) - } - - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAmountBuilder - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - btcPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }, - skipProbeForDestination: false, - }), - ) - } - - it("uses mid price and max btc fees", async () => { - const payment = await withBtcWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - if (invoiceWithAmount.paymentAmount === null) - throw new Error("paymentAmount should not be null") - - const usdPaymentAmount = await usdFromBtcMid({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - - const btcProtocolAndBankFee = LnFees().maxProtocolAndBankFee({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - if (btcProtocolAndBankFee instanceof Error) return btcProtocolAndBankFee - expect(btcProtocolAndBankFee).not.toBeInstanceOf(Error) - - const priceRatio = WalletPriceRatio(payment.paymentAmounts()) - if (priceRatio instanceof Error) throw priceRatio - const usdProtocolAndBankFee = - priceRatio.convertFromBtcToCeil(btcProtocolAndBankFee) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - btcProtocolAndBankFee, - usdProtocolAndBankFee, - skipProbeForDestination: false, - }), - ) - }) - }) - - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAmountBuilder - .withSenderWallet(senderUsdWalletDescriptor) - .withoutRecipientWallet() - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - usdPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }, - skipProbeForDestination: false, - }), - ) - } - - it("uses dealer price and max usd fees", async () => { - const payment = await withUsdWalletBuilder - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = await btcFromUsdSell({ - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - btcProtocolAndBankFee: LnFees().maxProtocolAndBankFee(btcPaymentAmount), - skipProbeForDestination: false, - }), - ) - }) - }) - }) - }) - - describe("ln initiated, intraledger settled", () => { - const intraledgerBuilder = LightningPaymentFlowBuilder({ - localNodeIds: [invoiceWithAmount.destination, invoiceWithNoAmount.destination], - skipProbe, - }) - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.IntraLedger, - paymentInitiationMethod: PaymentInitiationMethod.Lightning, - btcProtocolAndBankFee: LnFees().intraLedgerFees().btc, - usdProtocolAndBankFee: LnFees().intraLedgerFees().usd, - }), - ) - } - describe("invoice with amount", () => { - const withAmountBuilder = intraledgerBuilder.withInvoice(invoiceWithAmount) - const checkInvoice = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount: invoiceWithAmount.paymentAmount, - inputAmount: invoiceWithAmount.paymentAmount?.amount, - }), - ) - } - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAmountBuilder.withSenderWallet( - senderBtcWalletDescriptor, - ) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - it("uses mid price and intraledger fees", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcMid( - invoiceWithAmount.paymentAmount as BtcPaymentAmount, - ) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - }) - describe("with usd recipient", () => { - const usdPaymentAmount = { - amount: 111111n, - currency: WalletCurrency.Usd, - } - const withUsdRecipientBuilder = withBtcWalletBuilder.withRecipientWallet({ - ...recipientUsdArgs, - usdPaymentAmount, - }) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - usdPaymentAmount, - }), - ) - } - - it("uses amount specified by recipient invoice", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - }) - }) - }) - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAmountBuilder.withSenderWallet( - senderUsdWalletDescriptor, - ) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientBtcArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - it("uses dealer price", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcSell( - invoiceWithAmount.paymentAmount as BtcPaymentAmount, - ) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - }) - describe("with usd recipient", () => { - const usdPaymentAmount = { - amount: 111111n, - currency: WalletCurrency.Usd, - } - const withUsdRecipientBuilder = withUsdWalletBuilder.withRecipientWallet({ - ...recipientUsdArgs, - usdPaymentAmount, - }) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - usdPaymentAmount, - }), - ) - } - - it("uses amount specified by recipient invoice", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - }) - }) - }) - }) - - describe("invoice with no amount", () => { - const uncheckedAmount = 10000n - const withAmountBuilder = intraledgerBuilder.withNoAmountInvoice({ - invoice: invoiceWithNoAmount, - uncheckedAmount: Number(uncheckedAmount), - }) - - const lessThan1CentAmount = 1n - const lessThan1CentWithAmountBuilder = intraledgerBuilder.withNoAmountInvoice({ - invoice: invoiceWithNoAmount, - uncheckedAmount: Number(lessThan1CentAmount), - }) - - const checkInvoice = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: uncheckedAmount, - }), - ) - } - - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAmountBuilder.withSenderWallet( - senderBtcWalletDescriptor, - ) - const lessThan1CentWithBtcWalletBuilder = - lessThan1CentWithAmountBuilder.withSenderWallet(senderBtcWalletDescriptor) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - btcPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - const lessThan1CentWithBtcRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - - it("uses mid price", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcMid({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - - it("sends amount less than 1 cent", async () => { - const paymentBefore = lessThan1CentWithBtcRecipientBuilder.withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - const payment = await paymentBefore.withoutRoute() - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount: ONE_CENT, - }), - ) - }) - }) - - describe("with usd recipient", () => { - const withUsdRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithUsdRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithSelfUsdRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet( - senderUsdAsRecipientArgs, - ) - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - it("uses dealer price", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcBuy({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - - it("credits amount less than 1 cent amount to recipient btc wallet", async () => { - const paymentFlow = await lessThan1CentWithUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (paymentFlow instanceof Error) throw paymentFlow - - const { walletDescriptor: recipientWalletDescriptor } = - paymentFlow.recipientDetails() - expect(recipientWalletDescriptor).toStrictEqual(recipientBtcWalletDescriptor) - }) - - it("fails to send less than 1 cent to self", async () => { - const paymentFlow = await lessThan1CentWithSelfUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - expect(paymentFlow).toBeInstanceOf(SubOneCentSatAmountForUsdSelfSendError) - }) - }) - }) - - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAmountBuilder.withSenderWallet( - senderUsdWalletDescriptor, - ) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - usdPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientBtcArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - it("uses dealer price", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = await btcFromUsdSell({ - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - }), - ) - }) - }) - describe("with usd recipient", () => { - const withUsdRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientUsdArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - it("uses mid price", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = await btcFromUsdMid({ - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - }), - ) - }) - }) - }) - }) - }) - - describe("intraledger initiated, intraledger settled", () => { - const intraledgerBuilder = LightningPaymentFlowBuilder({ - localNodeIds: [], - skipProbe, - }) - const checkSettlementMethod = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - settlementMethod: SettlementMethod.IntraLedger, - paymentInitiationMethod: PaymentInitiationMethod.IntraLedger, - btcProtocolAndBankFee: LnFees().intraLedgerFees().btc, - usdProtocolAndBankFee: LnFees().intraLedgerFees().usd, - }), - ) - } - describe("no invoice", () => { - const uncheckedAmount = 10000n - const withAmountBuilder = intraledgerBuilder.withoutInvoice({ - uncheckedAmount: Number(uncheckedAmount), - description: "", - }) - - const lessThan1CentAmount = 1n - const lessThan1CentWithAmountBuilder = intraledgerBuilder.withoutInvoice({ - uncheckedAmount: Number(lessThan1CentAmount), - description: "", - }) - - const checkInvoice = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - inputAmount: uncheckedAmount, - }), - ) - } - - describe("with btc wallet", () => { - const withBtcWalletBuilder = withAmountBuilder.withSenderWallet( - senderBtcWalletDescriptor, - ) - const lessThan1CentWithBtcWalletBuilder = - lessThan1CentWithAmountBuilder.withSenderWallet(senderBtcWalletDescriptor) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderBtcWalletDescriptor.id, - senderWalletCurrency: senderBtcWalletDescriptor.currency, - btcPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - - const lessThan1CentWithBtcRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet(recipientBtcArgs) - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - - it("uses mid price", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcMid({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - - it("sends amount less than 1 cent", async () => { - const paymentBefore = lessThan1CentWithBtcRecipientBuilder.withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - const payment = await paymentBefore.withoutRoute() - if (payment instanceof Error) throw payment - - checkSettlementMethod(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount: ONE_CENT, - }), - ) - }) - }) - - describe("with usd recipient", () => { - const withUsdRecipientBuilder = - withBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithUsdRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet(recipientUsdArgs) - - const lessThan1CentWithSelfUsdRecipientBuilder = - lessThan1CentWithBtcWalletBuilder.withRecipientWallet( - senderUsdAsRecipientArgs, - ) - - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - it("uses dealer price", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const usdPaymentAmount = await usdFromBtcBuy({ - amount: uncheckedAmount, - currency: WalletCurrency.Btc, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - usdPaymentAmount, - }), - ) - }) - - it("credits amount less than 1 cent amount to recipient btc wallet", async () => { - const paymentFlow = await lessThan1CentWithUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (paymentFlow instanceof Error) throw paymentFlow - - const { walletDescriptor: recipientWalletDescriptor } = - paymentFlow.recipientDetails() - expect(recipientWalletDescriptor).toStrictEqual(recipientBtcWalletDescriptor) - }) - - it("fails to send amount less than 1 cent to self", async () => { - const payment = await lessThan1CentWithSelfUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - expect(payment).toBeInstanceOf(SubOneCentSatAmountForUsdSelfSendError) - }) - }) - }) - - describe("with usd wallet", () => { - const withUsdWalletBuilder = withAmountBuilder.withSenderWallet( - senderUsdWalletDescriptor, - ) - - const checkSenderWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - senderWalletId: senderUsdWalletDescriptor.id, - senderWalletCurrency: senderUsdWalletDescriptor.currency, - usdPaymentAmount: { - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }, - }), - ) - } - - describe("with btc recipient", () => { - const withBtcRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientBtcArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientBtcWalletDescriptor.id, - recipientWalletCurrency: recipientBtcWalletDescriptor.currency, - recipientUsername: recipientBtcArgs.username, - }), - ) - } - it("uses dealer price", async () => { - const payment = await withBtcRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = await btcFromUsdSell({ - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - }), - ) - }) - }) - describe("with usd recipient", () => { - const withUsdRecipientBuilder = - withUsdWalletBuilder.withRecipientWallet(recipientUsdArgs) - const checkRecipientWallet = (payment) => { - expect(payment).toEqual( - expect.objectContaining({ - recipientWalletId: recipientUsdWalletDescriptor.id, - recipientWalletCurrency: recipientUsdWalletDescriptor.currency, - recipientUsername: recipientUsdArgs.username, - }), - ) - } - - it("uses mid price", async () => { - const payment = await withUsdRecipientBuilder - .withConversion({ - mid: { - usdFromBtc: usdFromBtcMid, - btcFromUsd: btcFromUsdMid, - }, - hedgeBuyUsd: { - usdFromBtc: usdFromBtcBuy, - btcFromUsd: btcFromUsdBuy, - }, - hedgeSellUsd: { - usdFromBtc: usdFromBtcSell, - btcFromUsd: btcFromUsdSell, - }, - }) - .withoutRoute() - if (payment instanceof Error) throw payment - - const btcPaymentAmount = await btcFromUsdMid({ - amount: uncheckedAmount, - currency: WalletCurrency.Usd, - }) - - checkSettlementMethod(payment) - checkInvoice(payment) - checkSenderWallet(payment) - checkRecipientWallet(payment) - expect(payment).toEqual( - expect.objectContaining({ - btcPaymentAmount, - }), - ) - }) - }) - }) - }) - }) - - describe("error states", () => { - describe("pass a NoAmount invoice to withInvoice", () => { - it("returns a ValidationError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [], - skipProbe, - }) - .withInvoice(invoiceWithNoAmount) - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(InvalidLightningPaymentFlowBuilderStateError) - }) - }) - describe("non-integer uncheckedAmount", () => { - it("returns a ValidationError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [], - skipProbe, - }) - .withNoAmountInvoice({ invoice: invoiceWithNoAmount, uncheckedAmount: 0.4 }) - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(ValidationError) - }) - }) - describe("zero-value uncheckedAmount", () => { - it("returns a ValidationError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [], - skipProbe, - }) - .withNoAmountInvoice({ invoice: invoiceWithNoAmount, uncheckedAmount: 0 }) - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(ValidationError) - }) - }) - describe("no recipient wallet despite IntraLedger", () => { - it("returns InvalidLightningPaymentFlowBuilderStateError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [invoiceWithAmount.destination], - skipProbe, - }) - .withInvoice(invoiceWithAmount) - .withSenderWallet(senderBtcWalletDescriptor) - .withoutRecipientWallet() - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(InvalidLightningPaymentFlowBuilderStateError) - }) - }) - - describe("recipient is usd wallet but no usd amount specified", () => { - it("returns ImpossibleLightningPaymentFlowBuilderStateError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [invoiceWithAmount.destination], - skipProbe, - }) - .withInvoice(invoiceWithAmount) - .withSenderWallet(senderBtcWalletDescriptor) - .withRecipientWallet(senderUsdAsRecipientArgs) - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(InvalidLightningPaymentFlowBuilderStateError) - }) - }) - - describe("recipient is usd wallet and amount is specifies (for no amount invoice)", () => { - it("returns ImpossibleLightningPaymentFlowBuilderStateError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [invoiceWithNoAmount.destination], - skipProbe, - }) - .withNoAmountInvoice({ invoice: invoiceWithNoAmount, uncheckedAmount: 1000 }) - .withSenderWallet(senderBtcWalletDescriptor) - .withRecipientWallet({ - ...senderUsdAsRecipientArgs, - usdPaymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - }) - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(InvalidLightningPaymentFlowBuilderStateError) - }) - }) - - describe("sender and recipient are identical", () => { - it("returns ImpossibleLightningPaymentFlowBuilderStateError", async () => { - const payment = await LightningPaymentFlowBuilder({ - localNodeIds: [invoiceWithAmount.destination], - skipProbe, - }) - .withInvoice(invoiceWithAmount) - .withSenderWallet(senderBtcWalletDescriptor) - .withRecipientWallet(senderBtcAsRecipientArgs) - .withConversion({ - mid, - hedgeBuyUsd, - hedgeSellUsd, - }) - .withoutRoute() - - expect(payment).toBeInstanceOf(SelfPaymentError) - }) - }) - }) -}) diff --git a/test/galoy/unit/payments/payment-flow.spec.ts b/test/galoy/unit/payments/payment-flow.spec.ts deleted file mode 100644 index 7ad3694c3..000000000 --- a/test/galoy/unit/payments/payment-flow.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { InsufficientBalanceError, InvalidCurrencyForWalletError } from "@domain/errors" -import { toCents } from "@domain/fiat" -import { inputAmountFromLedgerTransaction } from "@domain/ledger" -import { OnChainPaymentFlow, PaymentFlow } from "@domain/payments" -import { WalletCurrency, safeBigInt, AmountCalculator } from "@domain/shared" -import { PaymentInitiationMethod, SettlementMethod } from "@domain/wallets" - -const calc = AmountCalculator() - -const btcPaymentAmount = { amount: BigInt(20_000), currency: WalletCurrency.Btc } -const usdPaymentAmount = { amount: BigInt(1_000), currency: WalletCurrency.Usd } -const btcProtocolAndBankFee = { amount: BigInt(400), currency: WalletCurrency.Btc } -const usdProtocolAndBankFee = { amount: BigInt(20), currency: WalletCurrency.Usd } -const btcBankFee = { amount: BigInt(40), currency: WalletCurrency.Btc } -const usdBankFee = { amount: BigInt(2), currency: WalletCurrency.Usd } -const timestamp = new Date() - -const walletsToTest = [ - { - name: "btc", - sendAmount: calc.add(btcPaymentAmount, btcProtocolAndBankFee), - inputAmount: btcPaymentAmount.amount, - }, - { - name: "usd", - sendAmount: calc.add(usdPaymentAmount, usdProtocolAndBankFee), - inputAmount: usdPaymentAmount.amount, - }, -] - -const runCheckBalanceTests = ({ - name, - paymentFlow, -}: { - name: string - paymentFlow: PaymentFlow | OnChainPaymentFlow -}) => { - const sendAmount = - paymentFlow.senderWalletCurrency === WalletCurrency.Btc - ? paymentFlow.totalAmountsForPayment().btc - : paymentFlow.totalAmountsForPayment().usd - - describe("checkBalanceForSend", () => { - describe(`${name} sending wallet`, () => { - it("passes for send amount under balance", () => { - const balanceForSend = { - amount: sendAmount.amount + 1n, - currency: sendAmount.currency as S, - } - const check = paymentFlow.checkBalanceForSend(balanceForSend) - expect(check).not.toBeInstanceOf(Error) - expect(check).toBe(true) - }) - - it("passes for send amount equal to balance", () => { - const balanceForSend = sendAmount as BalanceAmount - const check = paymentFlow.checkBalanceForSend(balanceForSend) - expect(check).not.toBeInstanceOf(Error) - expect(check).toBe(true) - }) - - it("fails for send amount above balance", () => { - const balanceForSend = { - amount: sendAmount.amount - 1n, - currency: sendAmount.currency as S, - } - const check = paymentFlow.checkBalanceForSend(balanceForSend) - expect(check).toBeInstanceOf(InsufficientBalanceError) - }) - - it("fails for wrong balance currency", () => { - const balanceForSend = { - amount: sendAmount.amount + 1n, - currency: - sendAmount.currency === WalletCurrency.Btc - ? (WalletCurrency.Usd as S) - : (WalletCurrency.Btc as S), - } - const check = paymentFlow.checkBalanceForSend(balanceForSend) - expect(check).toBeInstanceOf(InvalidCurrencyForWalletError) - }) - }) - }) -} - -describe("LightningPaymentFlowFromLedgerTransaction", () => { - const paymentFlowState: PaymentFlowState = { - senderWalletId: "walletId" as WalletId, - senderAccountId: "accountId" as AccountId, - settlementMethod: SettlementMethod.Lightning, - paymentInitiationMethod: PaymentInitiationMethod.Lightning, - - paymentHash: "paymentHash" as PaymentHash, - descriptionFromInvoice: "", - skipProbeForDestination: false, - createdAt: timestamp, - paymentSentAndPending: true, - - btcPaymentAmount, - usdPaymentAmount, - - inputAmount: undefined as unknown as bigint, - senderWalletCurrency: undefined as unknown as S, - - btcProtocolAndBankFee, - usdProtocolAndBankFee, - } - - for (const { name, sendAmount, inputAmount } of walletsToTest) { - const paymentFlow = PaymentFlow({ - ...paymentFlowState, - inputAmount, - senderWalletCurrency: sendAmount.currency as S, - }) - if (paymentFlow instanceof Error) throw paymentFlow - - runCheckBalanceTests({ name, paymentFlow }) - } -}) - -describe("OnChainPaymentFlowFromLedgerTransaction", () => { - const onChainPaymentFlowState: OnChainPaymentFlowState = { - senderWalletId: "walletId" as WalletId, - senderAccountId: "accountId" as AccountId, - settlementMethod: SettlementMethod.Lightning, - paymentInitiationMethod: PaymentInitiationMethod.Lightning, - - address: "OnChainAddress" as OnChainAddress, - createdAt: timestamp, - paymentSentAndPending: true, - - btcPaymentAmount, - usdPaymentAmount, - - inputAmount: undefined as unknown as bigint, - senderWalletCurrency: undefined as unknown as S, - - btcProtocolAndBankFee, - usdProtocolAndBankFee, - btcBankFee, - usdBankFee, - } - - for (const { name, sendAmount, inputAmount } of walletsToTest) { - const onChainPaymentFlow = OnChainPaymentFlow({ - ...onChainPaymentFlowState, - inputAmount, - senderWalletCurrency: sendAmount.currency, - }) - if (onChainPaymentFlow instanceof Error) throw onChainPaymentFlow - - runCheckBalanceTests({ name, paymentFlow: onChainPaymentFlow }) - } -}) - -describe("inputAmountFromLedgerTransaction", () => { - const baseLedgerTransaction = { - satsAmount: toSats(1000), - centsAmount: toCents(20), - satsFee: toSats(1), - centsFee: toCents(1), - displayAmount: 20 as DisplayCurrencyBaseAmount, - displayFee: 1 as DisplayCurrencyBaseAmount, - displayCurrency: "USD", - } - - const btcLedgerTransaction = { - debit: toSats(1001), - credit: toSats(0), - currency: "BTC", - ...baseLedgerTransaction, - } as LedgerTransaction<"BTC"> - - const usdLedgerTransaction = { - debit: toCents(21), - credit: toCents(0), - currency: "USD", - ...baseLedgerTransaction, - } as LedgerTransaction<"USD"> - - it("calculates the correct input amount given a BTC LedgerTransaction", () => { - const inputAmount = inputAmountFromLedgerTransaction(btcLedgerTransaction) - expect(inputAmount).not.toBeInstanceOf(Error) - - const satsAmount = safeBigInt(baseLedgerTransaction.satsAmount) - expect(satsAmount).not.toBeInstanceOf(Error) - - expect(inputAmount).toEqual(satsAmount) - }) - - it("calculates the correct input amount given a USD LedgerTransaction", () => { - const inputAmount = inputAmountFromLedgerTransaction(usdLedgerTransaction) - expect(inputAmount).not.toBeInstanceOf(Error) - - const centsAmount = safeBigInt(baseLedgerTransaction.centsAmount) - expect(centsAmount).not.toBeInstanceOf(Error) - - expect(inputAmount).toEqual(centsAmount) - }) -}) diff --git a/test/galoy/unit/services/ipfetcher/fetch-ip-info.spec.ts b/test/galoy/unit/services/ipfetcher/fetch-ip-info.spec.ts deleted file mode 100644 index f0dee6ede..000000000 --- a/test/galoy/unit/services/ipfetcher/fetch-ip-info.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import axios from "axios" -import MockAdapter from "axios-mock-adapter" -import { IpFetcher } from "@services/ipfetcher" - -let mock - -beforeAll(() => { - mock = new MockAdapter(axios) -}) - -afterEach(() => { - mock.reset() -}) - -describe("IpFetcher - fetchIPInfo", () => { - it("returns proxy false when proxy is no", async () => { - const ip = "152.231.190.229" as IpAddress - mock.onGet(new RegExp(`${ip}`)).reply(200, getIpInfo(ip)) - - const ipInfo = await IpFetcher().fetchIPInfo(ip) - expect(ipInfo).toEqual( - expect.objectContaining({ - proxy: false, - status: "ok", - }), - ) - }) - - it("returns proxy true when proxy is yes", async () => { - const ip = "152.231.190.229" as IpAddress - const data = getIpInfo(ip) - data[ip]["proxy"] = "yes" - - mock.onGet(new RegExp(`${ip}`)).reply(200, data) - - const ipInfo = await IpFetcher().fetchIPInfo(ip) - expect(ipInfo).toEqual( - expect.objectContaining({ - proxy: true, - status: "ok", - }), - ) - }) - - it("returns proxy false when proxy is undefined", async () => { - const ip = "152.231.190.229" as IpAddress - const data = getIpInfo(ip) - delete data[ip]["proxy"] - - mock.onGet(new RegExp(`${ip}`)).reply(200, data) - - const ipInfo = await IpFetcher().fetchIPInfo(ip) - expect(ipInfo).toEqual( - expect.objectContaining({ - proxy: false, - status: "ok", - }), - ) - }) -}) - -const getIpInfo = (ip: string) => ({ - status: "ok", - [ip]: { - asn: "AS52228", - provider: "Cable Tica", - organisation: "Cable Tica", - continent: "North America", - country: "Costa Rica", - isoCode: "CR", - region: "Provincia de San Jose", - regioncode: "SJ", - city: "Perez Zeledon", - latitude: 9.3573, - longitude: -83.6356, - proxy: "no", - type: "Residential", - }, -}) diff --git a/test/galoy/unit/services/ledger/domain/entry-builder.spec.ts b/test/galoy/unit/services/ledger/domain/entry-builder.spec.ts deleted file mode 100644 index a7e39aabb..000000000 --- a/test/galoy/unit/services/ledger/domain/entry-builder.spec.ts +++ /dev/null @@ -1,1026 +0,0 @@ -/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectEntryToEqual", "expectJournalToBeBalanced"] }] */ - -import { Entry, IJournal } from "medici" - -import { WalletCurrency, AmountCalculator, ZERO_BANK_FEE } from "@domain/shared" - -import { - lndLedgerAccountId, - EntryBuilder, - onChainLedgerAccountId, -} from "@services/ledger/domain" -import { MainBook } from "@services/ledger/books" -import { UsdDisplayCurrency } from "@domain/fiat" - -const createEntry = () => MainBook.entry("") - -type DisplayAmountsArg = { - displayAmount: DisplayCurrencyBaseAmount - displayFee: DisplayCurrencyBaseAmount - displayCurrency: DisplayCurrency -} - -describe("EntryBuilder", () => { - const findEntry = (txs: ILedgerTransaction[], account: string): ILedgerTransaction => { - const entry = txs.find((tx) => tx.accounts === account) - if (!entry) throw new Error("Invalid entry") - return entry - } - - const expectEntryToEqual = (entry: ILedgerTransaction, amount) => { - expect(entry.debit + entry.credit).toEqual(Number(amount.amount)) - expect(entry.currency).toEqual(amount.currency) - } - - const testEntryDisplayAmounts = ( - entry: Entry, - displayAmounts: { - walletAmounts: DisplayAmountsArg - senderAmounts: DisplayAmountsArg - recipientAmounts: DisplayAmountsArg - }, - ) => { - const { transactions } = entry - const internalEntries = transactions.filter(({ accounts }) => - Object.values(staticAccountIds).includes(accounts as LedgerAccountId), - ) - for (const entry of internalEntries) { - expect(entry).toEqual(expect.objectContaining(displayAmounts.walletAmounts)) - } - - const userEntries = transactions.filter( - ({ accounts }) => - !Object.values(staticAccountIds).includes(accounts as LedgerAccountId), - ) - expect(userEntries.length).toBeGreaterThanOrEqual(0) - expect(userEntries.length).toBeLessThanOrEqual(2) - - const userDebits = userEntries.filter(({ debit }) => debit > 0) - expect(userDebits.length).toBeLessThanOrEqual(1) - if (userDebits.length) { - expect(userDebits[0]).toEqual(expect.objectContaining(displayAmounts.senderAmounts)) - } - - const userCredits = userEntries.filter(({ credit }) => credit > 0) - expect(userCredits.length).toBeLessThanOrEqual(1) - if (userCredits.length) { - expect(userCredits[0]).toEqual( - expect.objectContaining(displayAmounts.recipientAmounts), - ) - } - } - - const expectJournalToBeBalanced = (journal: MediciEntry) => { - let usdCredits = 0 - let btcCredits = 0 - let usdDebits = 0 - let btcDebits = 0 - - const credits = journal.transactions.filter((t) => t.credit > 0) - const debits = journal.transactions.filter((t) => t.debit > 0) - const zeroAmounts = journal.transactions.filter( - (t) => t.debit === 0 && t.credit === 0, - ) - - // eslint-disable-next-line - Object.values(debits).forEach((entry) => - entry.currency === WalletCurrency.Usd - ? (usdDebits += entry.amount) - : (btcDebits += entry.amount), - ) - // eslint-disable-next-line - Object.values(credits).forEach((entry) => - entry.currency === WalletCurrency.Usd - ? (usdCredits += entry.amount) - : (btcCredits += entry.amount), - ) - - expect(usdCredits).toEqual(usdDebits) - expect(btcCredits).toEqual(btcDebits) - expect(zeroAmounts.length).toBe(0) - } - - const calc = AmountCalculator() - const staticAccountIds = { - bankOwnerAccountId: "bankOwnerAccountId" as LedgerAccountId, - dealerBtcAccountId: "dealerBtcAccountId" as LedgerAccountId, - dealerUsdAccountId: "dealerUsdAccountId" as LedgerAccountId, - lightningAccountId: "Assets:Reserve:Lightning" as LedgerAccountId, - onchainAccountId: "Assets:OnChain" as LedgerAccountId, - } - const debitorAccountId = "debitorAccountId" as LedgerAccountId - const btcDebitorAccountDescriptor = { - id: debitorAccountId, - currency: WalletCurrency.Btc, - } as LedgerAccountDescriptor<"BTC"> - - const usdDebitorAccountDescriptor = { - id: debitorAccountId, - currency: WalletCurrency.Usd, - } as LedgerAccountDescriptor<"USD"> - - const creditorAccountId = "creditorAccountId" as LedgerAccountId - - const btcCreditorAccountDescriptor = { - id: creditorAccountId, - currency: WalletCurrency.Btc, - } as LedgerAccountDescriptor<"BTC"> - - const usdCreditorAccountDescriptor = { - id: creditorAccountId, - currency: WalletCurrency.Usd, - } as LedgerAccountDescriptor<"USD"> - - const btcAmount = { - currency: WalletCurrency.Btc, - amount: 2000n, - } - const usdAmount = { - currency: WalletCurrency.Usd, - amount: 20n, - } - - const amount = { - btcWithFees: btcAmount, - usdWithFees: usdAmount, - } - const btcFee = { - currency: WalletCurrency.Btc, - amount: 110n, - } - const usdFee = { - currency: WalletCurrency.Usd, - amount: 1n, - } - - const bankFee = { - usdBankFee: usdFee, - btcBankFee: btcFee, - } - - const metadata = { - memo: "memo", - memoPayer: "memoPayer", - currency: "BAD CURRENCY", - some: "some", - more: "more", - } - - const additionalInternalMetadata = { - displayAmount: 20 as DisplayCurrencyBaseAmount, - displayFee: 0 as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, - } - - const additionalUserUsdMetadata = { - displayAmount: 22 as DisplayCurrencyBaseAmount, - displayFee: 0 as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, - } - - const additionalUserEurMetadata = { - displayAmount: 25 as DisplayCurrencyBaseAmount, - displayFee: 0 as DisplayCurrencyBaseAmount, - displayCurrency: "EUR" as DisplayCurrency, - } - - describe("Btc account", () => { - describe("send", () => { - describe("offchain", () => { - it("without fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOffChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, lndLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.bankOwnerAccountId), - ).toBeUndefined() - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOffChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual( - findEntry(credits, staticAccountIds.bankOwnerAccountId), - btcFee, - ) - expectEntryToEqual( - findEntry(credits, lndLedgerAccountId), - calc.sub(btcAmount, btcFee), - ) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - }) - }) - describe("onchain", () => { - it("without fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOnChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, onChainLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.bankOwnerAccountId), - ).toBeUndefined() - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOnChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual( - findEntry(credits, staticAccountIds.bankOwnerAccountId), - btcFee, - ) - expectEntryToEqual( - findEntry(credits, onChainLedgerAccountId), - calc.sub(btcAmount, btcFee), - ) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - }) - }) - }) - - describe("receive", () => { - it("without fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitLnd() - .creditAccount({ - accountDescriptor: btcCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(debits, lndLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(credits, creditorAccountId), btcAmount) - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitLnd() - .creditAccount({ - accountDescriptor: btcCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expect(findEntry(credits, staticAccountIds.bankOwnerAccountId).credit).toEqual( - Number(btcFee.amount), - ) - expectEntryToEqual(findEntry(debits, lndLedgerAccountId), btcAmount) - expectEntryToEqual( - findEntry(credits, creditorAccountId), - calc.sub(btcAmount, btcFee), - ) - }) - }) - }) - - describe("Usd account", () => { - describe("send", () => { - describe("offchain", () => { - it("without fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOffChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, lndLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOffChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual( - findEntry(credits, lndLedgerAccountId), - calc.sub(btcAmount, btcFee), - ) - expectEntryToEqual( - findEntry(credits, staticAccountIds.bankOwnerAccountId), - btcFee, - ) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - }) - describe("onchain", () => { - it("without fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOnChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, onChainLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditOnChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual( - findEntry(credits, onChainLedgerAccountId), - calc.sub(btcAmount, btcFee), - ) - expectEntryToEqual( - findEntry(credits, staticAccountIds.bankOwnerAccountId), - btcFee, - ) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - }) - }) - - describe("receive", () => { - describe("without fee", () => { - it("handles txn with btc amount & usd amount", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitLnd() - .creditAccount({ - accountDescriptor: usdCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(debits, lndLedgerAccountId), btcAmount) - expectEntryToEqual(findEntry(credits, creditorAccountId), usdAmount) - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - - // e.g. a `recordReceive' fee-reimbursement with low sats amount - it("handles txn with btc amount & zero usd amount", () => { - const btcAmount = { - currency: WalletCurrency.Btc, - amount: 18n, - } - const usdAmount = { - currency: WalletCurrency.Usd, - amount: 0n, - } - - const amount = { - btcWithFees: btcAmount, - usdWithFees: usdAmount, - } - - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitLnd() - .creditAccount({ - accountDescriptor: usdCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(debits, lndLedgerAccountId), btcAmount) - expect(credits.find((tx) => tx.accounts === creditorAccountId)).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - }) - - it("with fee", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(bankFee) - .debitLnd() - .creditAccount({ - accountDescriptor: usdCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(debits, lndLedgerAccountId), btcAmount) - expectEntryToEqual( - findEntry(credits, creditorAccountId), - calc.sub(usdAmount, usdFee), - ) - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerBtcAccountId), - calc.sub(btcAmount, btcFee), - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerUsdAccountId), - calc.sub(usdAmount, usdFee), - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - }) - }) - - describe("intra ledger", () => { - describe("from btc", () => { - it("to btc", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditAccount({ - accountDescriptor: btcCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - expectEntryToEqual(findEntry(credits, creditorAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - }) - - it("to usd", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditAccount({ - accountDescriptor: usdCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, creditorAccountId), usdAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), btcAmount) - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - }) - describe("from usd", () => { - it("to btc", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditAccount({ - accountDescriptor: btcCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, creditorAccountId), btcAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - expectEntryToEqual( - findEntry(debits, staticAccountIds.dealerBtcAccountId), - btcAmount, - ) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.dealerBtcAccountId), - ).toBeUndefined() - expectEntryToEqual( - findEntry(credits, staticAccountIds.dealerUsdAccountId), - usdAmount, - ) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.dealerUsdAccountId), - ).toBeUndefined() - }) - - it("to usd", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: usdDebitorAccountDescriptor, - additionalMetadata: additionalUserUsdMetadata, - }) - .creditAccount({ - accountDescriptor: usdCreditorAccountDescriptor, - additionalMetadata: additionalUserEurMetadata, - }) - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - expectEntryToEqual(findEntry(credits, creditorAccountId), usdAmount) - expectEntryToEqual(findEntry(debits, debitorAccountId), usdAmount) - }) - }) - }) - - describe("metadata", () => { - describe("offchain", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: { - ...additionalUserUsdMetadata, - more: "yes", - muchMore: "muchMore", - }, - }) - .creditOffChain() - - const debits = result.transactions.filter((t) => t.debit > 0) - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - - const resultEntry = findEntry(debits, debitorAccountId) - - it("debitor can take additional metadata", () => { - expect(resultEntry.currency).toEqual(WalletCurrency.Btc) - expect(resultEntry.meta).toEqual( - expect.objectContaining({ - some: "some", - more: "yes", - muchMore: "muchMore", - }), - ) - }) - - it("debitor can take memos", () => { - expect(resultEntry).toEqual( - expect.objectContaining({ - memo: "memo", - memoPayer: "memoPayer", - }), - ) - }) - }) - describe("onchain", () => { - const entry = createEntry() - const builder = EntryBuilder({ - staticAccountIds, - entry, - metadata, - additionalInternalMetadata, - }) - const result = builder - .withTotalAmount(amount) - .withBankFee(ZERO_BANK_FEE) - .debitAccount({ - accountDescriptor: btcDebitorAccountDescriptor, - additionalMetadata: { - ...additionalUserUsdMetadata, - more: "yes", - muchMore: "muchMore", - }, - }) - .creditOnChain() - - const debits = result.transactions.filter((t) => t.debit > 0) - expectJournalToBeBalanced(result) - testEntryDisplayAmounts(entry, { - walletAmounts: additionalInternalMetadata, - senderAmounts: additionalUserUsdMetadata, - recipientAmounts: additionalUserEurMetadata, - }) - - const resultEntry = findEntry(debits, debitorAccountId) - - it("debitor can take additional metadata", () => { - expect(resultEntry.currency).toEqual(WalletCurrency.Btc) - expect(resultEntry.meta).toEqual( - expect.objectContaining({ - some: "some", - more: "yes", - muchMore: "muchMore", - }), - ) - }) - - it("debitor can take memos", () => { - expect(resultEntry).toEqual( - expect.objectContaining({ - memo: "memo", - memoPayer: "memoPayer", - }), - ) - }) - }) - }) -}) diff --git a/test/galoy/unit/services/ledger/domain/fee-only-entry-builder.spec.ts b/test/galoy/unit/services/ledger/domain/fee-only-entry-builder.spec.ts deleted file mode 100644 index 5866b0d41..000000000 --- a/test/galoy/unit/services/ledger/domain/fee-only-entry-builder.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectEntryToEqual", "expectJournalToBeBalanced"] }] */ - -import { WalletCurrency } from "@domain/shared" - -import { onChainLedgerAccountId } from "@services/ledger/domain" -import { MainBook } from "@services/ledger/books" -import { FeeOnlyEntryBuilder } from "@services/ledger/domain/fee-only-entry-builder" - -const createEntry = () => MainBook.entry("") - -describe("FeeOnlyEntryBuilder", () => { - const findEntry = (txs: ILedgerTransaction[], account: string): ILedgerTransaction => { - const entry = txs.find((tx) => tx.accounts === account) - if (!entry) throw new Error("Invalid entry") - return entry - } - - const expectEntryToEqual = (entry: ILedgerTransaction, amount) => { - expect(entry.debit + entry.credit).toEqual(Number(amount.amount)) - expect(entry.currency).toEqual(amount.currency) - } - - const expectJournalToBeBalanced = (journal: MediciEntry) => { - let usdCredits = 0 - let btcCredits = 0 - let usdDebits = 0 - let btcDebits = 0 - - const credits = journal.transactions.filter((t) => t.credit > 0) - const debits = journal.transactions.filter((t) => t.debit > 0) - const zeroAmounts = journal.transactions.filter( - (t) => t.debit === 0 && t.credit === 0, - ) - - // eslint-disable-next-line - Object.values(debits).forEach((entry) => - entry.currency === WalletCurrency.Usd - ? (usdDebits += entry.amount) - : (btcDebits += entry.amount), - ) - // eslint-disable-next-line - Object.values(credits).forEach((entry) => - entry.currency === WalletCurrency.Usd - ? (usdCredits += entry.amount) - : (btcCredits += entry.amount), - ) - - expect(usdCredits).toEqual(usdDebits) - expect(btcCredits).toEqual(btcDebits) - expect(zeroAmounts.length).toBe(0) - } - - const staticAccountIds = { - bankOwnerAccountId: "bankOwnerAccountId" as LedgerAccountId, - } - - const btcFee = { amount: 1000n, currency: WalletCurrency.Btc } - const metadata = { - currency: "BAD CURRENCY", - some: "some", - more: "more", - } - - it("overestimated fee, credit back bank owner", () => { - const entry = createEntry() - const builder = FeeOnlyEntryBuilder({ - staticAccountIds, - entry, - metadata, - btcFee, - }) - const result = builder.debitBankOwner().creditOnChain() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - - expectEntryToEqual(findEntry(credits, onChainLedgerAccountId), btcFee) - expect( - credits.find((tx) => tx.accounts === staticAccountIds.bankOwnerAccountId), - ).toBeUndefined() - - expectEntryToEqual(findEntry(debits, staticAccountIds.bankOwnerAccountId), btcFee) - expect(debits.find((tx) => tx.accounts === onChainLedgerAccountId)).toBeUndefined() - }) - - it("underestimated fee, debit from bank owner", () => { - const entry = createEntry() - const builder = FeeOnlyEntryBuilder({ - staticAccountIds, - entry, - metadata, - btcFee, - }) - const result = builder.debitOnChain().creditBankOwner() - - const credits = result.transactions.filter((t) => t.credit > 0) - const debits = result.transactions.filter((t) => t.debit > 0) - - expectJournalToBeBalanced(result) - - expectEntryToEqual(findEntry(credits, staticAccountIds.bankOwnerAccountId), btcFee) - expect(credits.find((tx) => tx.accounts === onChainLedgerAccountId)).toBeUndefined() - - expectEntryToEqual(findEntry(debits, onChainLedgerAccountId), btcFee) - expect( - debits.find((tx) => tx.accounts === staticAccountIds.bankOwnerAccountId), - ).toBeUndefined() - }) -}) diff --git a/test/galoy/unit/services/ledger/domain/tx-metadata.spec.ts b/test/galoy/unit/services/ledger/domain/tx-metadata.spec.ts deleted file mode 100644 index e4409e191..000000000 --- a/test/galoy/unit/services/ledger/domain/tx-metadata.spec.ts +++ /dev/null @@ -1,660 +0,0 @@ -import { toSats } from "@domain/bitcoin" -import { UsdDisplayCurrency, toCents } from "@domain/fiat" -import { LedgerTransactionType } from "@domain/ledger" -import { WalletCurrency, ZERO_CENTS, ZERO_SATS } from "@domain/shared" -import { - LnIntraledgerLedgerMetadata, - LnTradeIntraAccountLedgerMetadata, - OnChainIntraledgerLedgerMetadata, - OnChainTradeIntraAccountLedgerMetadata, - WalletIdIntraledgerLedgerMetadata, - WalletIdTradeIntraAccountLedgerMetadata, - LnSendLedgerMetadata, - OnChainSendLedgerMetadata, - OnChainReceiveLedgerMetadata, - LnReceiveLedgerMetadata, - LnFeeReimbursementReceiveLedgerMetadata, - LnFailedPaymentReceiveLedgerMetadata, -} from "@services/ledger/facade" - -describe("Tx metadata", () => { - const senderUsername = "sender" as Username - const recipientUsername = "receiver" as Username - const memoOfPayer = "sample payer memo" - const pubkey = "pubkey" as Pubkey - const payeeAddresses: OnChainAddress[] = ["Address" as OnChainAddress] - const newAddressRequestId: OnChainAddressRequestId = - "newAddressRequestId" as OnChainAddressRequestId - const paymentHash = "paymenthash" as PaymentHash - const onChainTxHash = "onChainTxHash" as OnChainTxHash - const onChainTxVout = 1 as OnChainTxVout - const journalId = "journalId" as LedgerJournalId - - const senderAmountDisplayCurrency = 100 as DisplayCurrencyBaseAmount - const senderFeeDisplayCurrency = 10 as DisplayCurrencyBaseAmount - const senderDisplayCurrency = "CRC" as DisplayCurrency - - const recipientAmountDisplayCurrency = 15 as DisplayCurrencyBaseAmount - const recipientFeeDisplayCurrency = 2 as DisplayCurrencyBaseAmount - const recipientDisplayCurrency = "EUR" as DisplayCurrency - - const paymentAmounts = { - btcPaymentAmount: { amount: 2000n, currency: WalletCurrency.Btc }, - usdPaymentAmount: { amount: 10n, currency: WalletCurrency.Usd }, - btcProtocolAndBankFee: ZERO_SATS, - usdProtocolAndBankFee: ZERO_CENTS, - } - - const startingMetadataArgs = { - paymentAmounts: paymentAmounts, - - senderAmountDisplayCurrency, - senderFeeDisplayCurrency, - senderDisplayCurrency, - - memoOfPayer, - } - - const expectedCommonMetadata = { - satsAmount: toSats(2000), - centsAmount: toCents(10), - - satsFee: toSats(0), - centsFee: toCents(0), - } - - const expectedAmounts = { - internal: { - displayAmount: Number( - paymentAmounts.usdPaymentAmount.amount, - ) as DisplayCurrencyBaseAmount, - displayFee: Number( - paymentAmounts.usdProtocolAndBankFee.amount, - ) as DisplayCurrencyBaseAmount, - displayCurrency: UsdDisplayCurrency, - }, - - sender: { - displayAmount: senderAmountDisplayCurrency, - displayFee: senderFeeDisplayCurrency, - displayCurrency: senderDisplayCurrency, - }, - - recipient: { - displayAmount: recipientAmountDisplayCurrency, - displayFee: recipientFeeDisplayCurrency, - displayCurrency: recipientDisplayCurrency, - }, - } - - describe("intraledger", () => { - const runTest = (testCase: { - title: string - commonMetadataArgs: - | { - payeeAddresses: OnChainAddress[] - sendAll: boolean - } - | { - paymentHash: PaymentHash - pubkey: Pubkey - } - | undefined - expectedCommonMetadata - expectedAdditionalDebitMetadata - crossAccount: { - MetadataFn - title: string - type: LedgerTransactionType - } - selfTrade: { - MetadataFn - title: string - type: LedgerTransactionType - } - }) => { - describe(`${testCase.title}`, () => { - const commonMetadataArgs = { - ...startingMetadataArgs, - ...testCase.commonMetadataArgs, - } - - describe(`cross-account (${testCase.crossAccount.title})`, () => { - const { MetadataFn, type } = testCase.crossAccount - - const metadataArgs = { - ...commonMetadataArgs, - - recipientAmountDisplayCurrency, - recipientFeeDisplayCurrency, - recipientDisplayCurrency, - - senderUsername, - recipientUsername, - } - - it("CRC sender", () => { - const { - metadata, - debitAccountAdditionalMetadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - ...expectedCommonMetadata, - ...testCase.expectedCommonMetadata, - username: senderUsername, - type, - }), - ) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining({ - username: recipientUsername, - ...testCase.expectedAdditionalDebitMetadata, - - ...expectedAmounts.sender, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.recipient), - ) - expect(creditAccountAdditionalMetadata).not.toHaveProperty([ - "username", - "memoPayer", - ]) - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty([ - "username", - "memoPayer", - ]) - }) - - it("USD sender", () => { - const { - debitAccountAdditionalMetadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn({ - ...metadataArgs, - senderDisplayCurrency: UsdDisplayCurrency, - }) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining({ - ...testCase.expectedAdditionalDebitMetadata, - ...expectedAmounts.internal, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.recipient), - ) - expect(creditAccountAdditionalMetadata).not.toHaveProperty("memoPayer") - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty("memoPayer") - }) - - it("USD recipient", () => { - const { - debitAccountAdditionalMetadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn({ - ...metadataArgs, - recipientDisplayCurrency: UsdDisplayCurrency, - }) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining({ - ...testCase.expectedAdditionalDebitMetadata, - ...expectedAmounts.sender, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(creditAccountAdditionalMetadata).not.toHaveProperty("memoPayer") - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty("memoPayer") - }) - }) - - describe(`self-trade (${testCase.selfTrade.title})`, () => { - const { MetadataFn, type } = testCase.selfTrade - - const metadataArgs = commonMetadataArgs - - it("CRC self", () => { - const { - metadata, - debitAccountAdditionalMetadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - ...expectedCommonMetadata, - ...testCase.expectedCommonMetadata, - type, - }), - ) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining({ - ...testCase.expectedAdditionalDebitMetadata, - ...expectedAmounts.sender, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.sender), - ) - expect(creditAccountAdditionalMetadata).not.toHaveProperty("memoPayer") - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty("memoPayer") - }) - - it("USD self", () => { - const { - metadata, - debitAccountAdditionalMetadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn({ - ...metadataArgs, - senderDisplayCurrency: UsdDisplayCurrency, - }) - - expect(metadata).toEqual( - expect.objectContaining({ - ...expectedCommonMetadata, - ...testCase.expectedCommonMetadata, - type, - }), - ) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining({ - ...testCase.expectedAdditionalDebitMetadata, - ...expectedAmounts.internal, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(creditAccountAdditionalMetadata).not.toHaveProperty("memoPayer") - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty("memoPayer") - }) - }) - }) - } - - // Run tests against cases - const testCases = [ - { - title: "onchain", - commonMetadataArgs: { - payeeAddresses, - sendAll: true, - }, - expectedCommonMetadata: { memoPayer: undefined }, - expectedAdditionalDebitMetadata: { memoPayer: memoOfPayer }, - crossAccount: { - title: "OnChainIntraledgerLedgerMetadata", - MetadataFn: OnChainIntraledgerLedgerMetadata, - type: LedgerTransactionType.OnchainIntraLedger, - }, - selfTrade: { - title: "OnChainTradeIntraAccountLedgerMetadata", - MetadataFn: OnChainTradeIntraAccountLedgerMetadata, - type: LedgerTransactionType.OnChainTradeIntraAccount, - }, - }, - - { - title: "ln", - commonMetadataArgs: { - paymentHash, - pubkey, - }, - expectedCommonMetadata: { memoPayer: undefined }, - expectedAdditionalDebitMetadata: { memoPayer: memoOfPayer }, - crossAccount: { - title: "LnIntraledgerLedgerMetadata", - MetadataFn: LnIntraledgerLedgerMetadata, - type: LedgerTransactionType.LnIntraLedger, - }, - selfTrade: { - title: "LnTradeIntraAccountLedgerMetadata", - MetadataFn: LnTradeIntraAccountLedgerMetadata, - type: LedgerTransactionType.LnTradeIntraAccount, - }, - }, - - { - title: "walletId", - commonMetadataArgs: undefined, - expectedCommonMetadata: { memoPayer: memoOfPayer }, - expectedAdditionalDebitMetadata: {}, - crossAccount: { - title: "WalletIdIntraledgerLedgerMetadata", - MetadataFn: WalletIdIntraledgerLedgerMetadata, - type: LedgerTransactionType.IntraLedger, - }, - selfTrade: { - title: "WalletIdTradeIntraAccountLedgerMetadata", - MetadataFn: WalletIdTradeIntraAccountLedgerMetadata, - type: LedgerTransactionType.WalletIdTradeIntraAccount, - }, - }, - ] - - testCases.forEach(runTest) - }) - - describe("external", () => { - const commonCrcMetadataArgs = { - paymentAmounts, - feeDisplayCurrency: expectedAmounts.sender.displayFee, - amountDisplayCurrency: expectedAmounts.sender.displayAmount, - displayCurrency: expectedAmounts.sender.displayCurrency, - } - - const commonUsdMetadataArgs = { - paymentAmounts, - feeDisplayCurrency: expectedAmounts.internal.displayFee, - amountDisplayCurrency: expectedAmounts.internal.displayAmount, - displayCurrency: expectedAmounts.internal.displayCurrency as DisplayCurrency, - } - - const sendMetadataArgs = { - memoOfPayer, - } - const expectedSendMetadataArgs = { - memoPayer: sendMetadataArgs.memoOfPayer, - pending: true, - } - - const receiveMetadataArgs = { onChainTxVout } - const expectedReceiveMetadataArgs = { pending: false } - - const onchainMetadataArgs = { - onChainTxHash, - payeeAddresses, - newAddressRequestId, - } - const expectedOnChainSendMetadataArgs = { - payee_addresses: onchainMetadataArgs.payeeAddresses, - } - - const expectedOnChainReceiveMetadataArgs = { - payee_addresses: onchainMetadataArgs.payeeAddresses, - hash: onChainTxHash, - } - - const lnMetadataArgs = { - paymentHash, - pubkey, - } - - const expectedLnMetadataArgs = { - hash: lnMetadataArgs.paymentHash, - } - - describe("send", () => { - const expectedMetadata = { - ...expectedCommonMetadata, - ...expectedSendMetadataArgs, - } - - const displayCurrencyCases = [ - { - commonMetadataArgs: commonUsdMetadataArgs, - expectedDebitAmounts: expectedAmounts.internal, - }, - { - commonMetadataArgs: commonCrcMetadataArgs, - expectedDebitAmounts: expectedAmounts.sender, - }, - ] - - describe("onchain", () => { - describe("OnChainSendLedgerMetadata", () => { - for (const { - commonMetadataArgs, - expectedDebitAmounts, - } of displayCurrencyCases) { - it(`${commonMetadataArgs.displayCurrency} sender`, () => { - const metadataArgs = { - ...commonMetadataArgs, - ...sendMetadataArgs, - ...onchainMetadataArgs, - sendAll: true, - } - - const { - metadata, - debitAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = OnChainSendLedgerMetadata(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - type: LedgerTransactionType.OnchainPayment, - ...expectedMetadata, - ...expectedOnChainSendMetadataArgs, - sendAll: true, - }), - ) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedDebitAmounts), - ) - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty(["memoPayer"]) - }) - } - }) - }) - - describe("ln", () => { - describe("LnSendLedgerMetadata", () => { - for (const { - commonMetadataArgs, - expectedDebitAmounts, - } of displayCurrencyCases) { - it(`${commonMetadataArgs.displayCurrency} sender`, () => { - const metadataArgs = { - ...commonMetadataArgs, - ...sendMetadataArgs, - ...lnMetadataArgs, - feeKnownInAdvance: true, - } - - const { - metadata, - debitAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = LnSendLedgerMetadata(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - type: LedgerTransactionType.Payment, - ...expectedMetadata, - ...expectedLnMetadataArgs, - feeKnownInAdvance: true, - pubkey, - }), - ) - - expect(debitAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedDebitAmounts), - ) - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty(["memoPayer"]) - }) - } - }) - }) - }) - - describe("receive", () => { - const expectedMetadata = { - ...expectedCommonMetadata, - ...expectedReceiveMetadataArgs, - } - - const displayCurrencyCases = [ - { - commonMetadataArgs: commonUsdMetadataArgs, - expectedCreditAmounts: expectedAmounts.internal, - }, - { - commonMetadataArgs: commonCrcMetadataArgs, - expectedCreditAmounts: expectedAmounts.sender, - }, - ] - - describe("onchain", () => { - describe("OnChainReceiveLedgerMetadata", () => { - for (const { - commonMetadataArgs, - expectedCreditAmounts, - } of displayCurrencyCases) { - it(`${commonMetadataArgs.displayCurrency} sender`, () => { - const metadataArgs = { - ...commonMetadataArgs, - ...receiveMetadataArgs, - ...onchainMetadataArgs, - } - - const { - metadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = OnChainReceiveLedgerMetadata(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - type: LedgerTransactionType.OnchainReceipt, - ...expectedMetadata, - ...expectedOnChainReceiveMetadataArgs, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedCreditAmounts), - ) - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty(["memoPayer"]) - }) - } - }) - }) - - describe("ln", () => { - const lnReceiveCases = [ - { - title: "LnReceiveLedgerMetadata", - MetadataFn: LnReceiveLedgerMetadata, - type: LedgerTransactionType.Invoice, - expectedAdditionalMetadata: {}, - }, - { - title: "LnFeeReimbursementReceiveLedgerMetadata", - MetadataFn: LnFeeReimbursementReceiveLedgerMetadata, - type: LedgerTransactionType.LnFeeReimbursement, - expectedAdditionalMetadata: { related_journal: journalId }, - }, - { - title: "LnFailedPaymentReceiveLedgerMetadata", - MetadataFn: LnFailedPaymentReceiveLedgerMetadata, - type: LedgerTransactionType.Payment, - expectedAdditionalMetadata: { related_journal: journalId }, - }, - ] - - for (const { - title, - MetadataFn, - type, - expectedAdditionalMetadata, - } of lnReceiveCases) { - describe(`${title}`, () => { - for (const { - commonMetadataArgs, - expectedCreditAmounts, - } of displayCurrencyCases) { - it(`${commonMetadataArgs.displayCurrency} sender`, () => { - const metadataArgs = { - ...commonMetadataArgs, - ...receiveMetadataArgs, - ...lnMetadataArgs, - journalId, - } - - const { - metadata, - creditAccountAdditionalMetadata, - internalAccountsAdditionalMetadata, - } = MetadataFn(metadataArgs) - - expect(metadata).toEqual( - expect.objectContaining({ - type, - ...expectedMetadata, - ...expectedLnMetadataArgs, - ...expectedAdditionalMetadata, - }), - ) - - expect(creditAccountAdditionalMetadata).toEqual( - expect.objectContaining(expectedCreditAmounts), - ) - - expect(internalAccountsAdditionalMetadata).toEqual( - expect.objectContaining(expectedAmounts.internal), - ) - expect(internalAccountsAdditionalMetadata).not.toHaveProperty([ - "memoPayer", - ]) - }) - } - }) - } - }) - }) - }) -}) diff --git a/test/galoy/unit/services/mongoose/utils.spec.ts b/test/galoy/unit/services/mongoose/utils.spec.ts deleted file mode 100644 index 5f5525a61..000000000 --- a/test/galoy/unit/services/mongoose/utils.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Types } from "mongoose" -import { toObjectId, fromObjectId } from "@services/mongoose/utils" - -describe("toObjectId / fromObjectId", () => { - it("presevers identity", () => { - const id = new Types.ObjectId() - const userId = fromObjectId(id) - expect(toObjectId(userId)).toEqual(id) - }) - - it("converts to string", () => { - const id = new Types.ObjectId() - const userId = fromObjectId(id) - expect(typeof userId).toBe("string") - }) -}) diff --git a/test/galoy/unit/services/notifications/create-push-notification-content.spec.ts b/test/galoy/unit/services/notifications/create-push-notification-content.spec.ts deleted file mode 100644 index af9cfc8b1..000000000 --- a/test/galoy/unit/services/notifications/create-push-notification-content.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { getLocale } from "@config" -import { createPushNotificationContent } from "@services/notifications/create-push-notification-content" - -import { - btcTransactions, - usdTransactions, - btcTransactionsWithDisplayCurrency, - usdTransactionsWithDisplayCurrency, -} from "./transactions" - -const userLanguage = getLocale() - -describe("Notifications - createPushNotificationContent", () => { - test.each(btcTransactions)( - "content is valid for BTC $type transaction", - ({ type, paymentAmount, body, title }) => { - const result = createPushNotificationContent({ - type, - amount: paymentAmount, - userLanguage, - }) - expect(result).toEqual(expect.objectContaining({ body, title })) - }, - ) - - test.each(btcTransactionsWithDisplayCurrency)( - "content is valid for BTC $type transaction with display currency", - ({ type, paymentAmount, displayPaymentAmount, body, title }) => { - const result = createPushNotificationContent({ - type, - amount: paymentAmount, - displayAmount: displayPaymentAmount, - userLanguage, - }) - expect(result).toEqual(expect.objectContaining({ body, title })) - }, - ) - - test.each(usdTransactions)( - "content is valid for USD $type transaction", - ({ type, paymentAmount, body, title }) => { - const result = createPushNotificationContent({ - type, - amount: paymentAmount, - userLanguage, - }) - expect(result).toEqual(expect.objectContaining({ body, title })) - }, - ) - - test.each(usdTransactionsWithDisplayCurrency)( - "content is valid for USD $type transaction with display currency", - ({ type, paymentAmount, displayPaymentAmount, body, title }) => { - const result = createPushNotificationContent({ - type, - amount: paymentAmount, - displayAmount: displayPaymentAmount, - userLanguage, - }) - expect(result).toEqual(expect.objectContaining({ body, title })) - }, - ) -}) diff --git a/test/galoy/unit/services/notifications/transactions.ts b/test/galoy/unit/services/notifications/transactions.ts deleted file mode 100644 index ad4442644..000000000 --- a/test/galoy/unit/services/notifications/transactions.ts +++ /dev/null @@ -1,387 +0,0 @@ -import { NotificationType } from "@domain/notifications" -import { WalletCurrency } from "@domain/shared" -import { UsdDisplayCurrency } from "@domain/fiat" - -const usdDisplayPaymentAmount = { - amountInMinor: 500n, - currency: UsdDisplayCurrency, - displayInMajor: "5.00", -} - -const crcDisplayPaymentAmount = { - amountInMinor: 350050n, - currency: "CRC" as DisplayCurrency, - displayInMajor: "3500.50", -} - -export const btcTransactions = [ - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "+1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "Sent payment of 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "+1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "pending +1,000 sats", - title: "BTC Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "Sent onchain payment of 1,000 sats confirmed", - title: "BTC Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - body: "+1,000 sats", - title: "BTC Transaction", - }, -] - -export const btcTransactionsWithDisplayCurrency = [ - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent payment of $5 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "pending +$5 | 1,000 sats", - title: "BTC Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent onchain payment of +$5 | 1,000 sats confirmed", - title: "BTC Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent payment of ₡3,500.50 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | 1,000 sats", - title: "BTC Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "pending +₡3,500.50 | 1,000 sats", - title: "BTC Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent onchain payment of +₡3,500.50 | 1,000 sats confirmed", - title: "BTC Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Btc }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | 1,000 sats", - title: "BTC Transaction", - }, -] - -export const usdTransactions = [ - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "+$10", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "Sent payment of $10", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "+$10", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "pending +$10", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "Sent onchain payment of $10 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 1000n, currency: WalletCurrency.Usd }, - body: "+$10", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "+$10.30", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "Sent payment of $10.30", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "+$10.30", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "pending +$10.30", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "Sent onchain payment of $10.30 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 1030n, currency: WalletCurrency.Usd }, - body: "+$10.30", - title: "USD Transaction", - }, -] - -export const usdTransactionsWithDisplayCurrency = [ - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent payment of $5", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "pending +$5", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent onchain payment of $5 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent payment of ₡3,500.50 | $5", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "pending +₡3,500.50 | $5", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent onchain payment of +₡3,500.50 | $5 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 500n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5.07", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent payment of $5.07", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5.07", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "pending +$5.07", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "Sent onchain payment of $5.07 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: usdDisplayPaymentAmount, - body: "+$5.07", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerReceipt, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5.07", - title: "USD Transaction", - }, - { - type: NotificationType.IntraLedgerPayment, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent payment of ₡3,500.50 | $5.07", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceipt, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5.07", - title: "USD Transaction", - }, - { - type: NotificationType.OnchainReceiptPending, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "pending +₡3,500.50 | $5.07", - title: "USD Transaction | Pending", - }, - { - type: NotificationType.OnchainPayment, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "Sent onchain payment of +₡3,500.50 | $5.07 confirmed", - title: "USD Transaction", - }, - { - type: NotificationType.LnInvoicePaid, - paymentAmount: { amount: 507n, currency: WalletCurrency.Usd }, - displayPaymentAmount: crcDisplayPaymentAmount, - body: "+₡3,500.50 | $5.07", - title: "USD Transaction", - }, -] diff --git a/test/galoy/unit/utils/grpc-stream-client/backoff.spec.ts b/test/galoy/unit/utils/grpc-stream-client/backoff.spec.ts deleted file mode 100644 index acbe9e51e..000000000 --- a/test/galoy/unit/utils/grpc-stream-client/backoff.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { GrpcStreamClient } from "@utils" - -const { FibonacciBackoff } = GrpcStreamClient - -describe("FibonacciBackoff", () => { - it("should correctly initialize FibonacciBackoff instance", () => { - const fibBackOff = new FibonacciBackoff(100, 7) - - expect(fibBackOff).toBeDefined() - }) - - it("should correctly generate Fibonacci backoff series", () => { - const fibBackOff = new FibonacciBackoff(100, 7) - const expectedSeries = [100, 200, 300, 500, 800, 1300, 2100] - - for (let i = 0; i < 7; i++) { - expect(fibBackOff.next()).toBe(expectedSeries[i]) - } - }) - - it("should return the last number after the limit is reached", () => { - const fibBackOff = new FibonacciBackoff(100, 7) - const expectedLastNumber = 2100 - - for (let i = 0; i < 10; i++) { - // Attempting to generate more numbers than the limit - fibBackOff.next() - } - - expect(fibBackOff.next()).toBe(expectedLastNumber) - }) - - it("should correctly reset the backoff series", () => { - const fibBackOff = new FibonacciBackoff(100, 7) - const expectedInitialNumber = 100 - - fibBackOff.next() - fibBackOff.next() - fibBackOff.reset() - - expect(fibBackOff.next()).toBe(expectedInitialNumber) - }) -}) diff --git a/test/galoy/unit/utils/grpc-stream-client/stream-builder.spec.ts b/test/galoy/unit/utils/grpc-stream-client/stream-builder.spec.ts deleted file mode 100644 index 544d3b984..000000000 --- a/test/galoy/unit/utils/grpc-stream-client/stream-builder.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Metadata } from "@grpc/grpc-js" -import { Empty } from "google-protobuf/google/protobuf/empty_pb" - -import { GrpcStreamClient } from "@utils" - -const { streamBuilder, Stream, StreamEvents } = GrpcStreamClient - -const methodMock = jest.fn() -const handler = jest.fn() - -describe("streamBuilder", () => { - it("should add event listener for onData", () => { - const result = streamBuilder(methodMock).onData(handler) - - expect(result.state.listeners).toHaveLength(1) - expect(result.state.listeners).toContainEqual({ - type: StreamEvents.data, - listener: handler, - listenerOptions: undefined, - }) - }) - - it("should add event listener for onEnd", () => { - const result = streamBuilder(methodMock).onEnd(handler) - - expect(result.state.listeners).toHaveLength(1) - expect(result.state.listeners).toContainEqual({ - type: StreamEvents.end, - listener: handler, - listenerOptions: undefined, - }) - }) - - it("should add event listener for onError", () => { - const result = streamBuilder(methodMock).onError(handler) - - expect(result.state.listeners).toHaveLength(1) - expect(result.state.listeners).toContainEqual({ - type: StreamEvents.error, - listener: handler, - listenerOptions: undefined, - }) - }) - - it("should add event listener for onRetry", () => { - const result = streamBuilder(methodMock).onRetry(handler) - - expect(result.state.listeners).toHaveLength(1) - expect(result.state.listeners).toContainEqual({ - type: StreamEvents.retry, - listener: handler, - listenerOptions: undefined, - }) - }) - - it("should add event listener for onMetadata", () => { - const result = streamBuilder(methodMock).onMetadata(handler) - - expect(result.state.listeners).toHaveLength(1) - expect(result.state.listeners).toContainEqual({ - type: StreamEvents.metadata, - listener: handler, - listenerOptions: undefined, - }) - }) - - it("should update method with withMethod", () => { - const newMethodMock = jest.fn() - const result = streamBuilder(methodMock).withMethod(newMethodMock) - - expect(result.state.method).toBe(newMethodMock) - }) - - it("should update request with withRequest", () => { - const newRequest = new Empty() - const result = streamBuilder(methodMock).withRequest(newRequest) - - expect(result.state.request).toBe(newRequest) - }) - - it("should update metadata with withMetadata", () => { - const metadata = new Metadata() - metadata.set("key", "value") - const result = streamBuilder(methodMock).withMetadata(metadata) - - expect(result.state.metadata).toBe(metadata) - }) - - it("should update backoff with withBackoff", () => { - const newBackoff = new GrpcStreamClient.FibonacciBackoff(30000, 7) - const result = streamBuilder(methodMock).withBackoff(newBackoff) - - expect(result.state.backoff).toBe(newBackoff) - }) - - it("should update options with withOptions", () => { - const newOptions = { retry: true } - const result = streamBuilder(methodMock).withOptions(newOptions) - - expect(result.state.options).toBe(newOptions) - }) - - it("should build a valid Stream", () => { - const mockConnect = jest.fn() - const metadata = new Metadata() - metadata.set("key", "value") - - jest.spyOn(Stream.prototype, "connect").mockImplementationOnce(mockConnect) - const result = streamBuilder(methodMock) - .withMetadata(metadata) - .onMetadata(handler) - .onData(handler) - .onError(handler) - .onEnd(handler) - .onRetry(handler) - .build() - - expect(mockConnect).toHaveBeenCalled() - expect(result).toBeInstanceOf(Stream) - expect(result.metaData).toBe(metadata) - expect(result.eventListeners.metadata).toHaveLength(1) - expect(result.eventListeners.data).toHaveLength(1) - expect(result.eventListeners.end).toHaveLength(1) - expect(result.eventListeners.error).toHaveLength(1) - expect(result.eventListeners.retry).toHaveLength(1) - }) -}) diff --git a/test/galoy/unit/utils/grpc-stream-client/stream.spec.ts b/test/galoy/unit/utils/grpc-stream-client/stream.spec.ts deleted file mode 100644 index dfaca52d3..000000000 --- a/test/galoy/unit/utils/grpc-stream-client/stream.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Metadata, ServiceError } from "@grpc/grpc-js" -import { Empty } from "google-protobuf/google/protobuf/empty_pb" - -import { GrpcStreamClient } from "@utils" - -const { Stream, StreamEvents } = GrpcStreamClient - -jest.useFakeTimers() - -afterAll(() => { - jest.useRealTimers() -}) - -describe("Stream", () => { - let mockStreamMethod - let mockGrpcData - let mockMetaData - let mockBackoff - let mockOptions - let mockStream - - beforeEach(() => { - mockStreamMethod = jest.fn() - mockGrpcData = Empty - mockMetaData = new Metadata() - mockBackoff = { next: jest.fn(), reset: jest.fn() } - mockOptions = { retry: true, retries: 3, isObject: false } - - mockStream = new Stream( - mockStreamMethod, - mockGrpcData, - mockMetaData, - mockBackoff, - mockOptions, - ) - }) - - it("should add a listener to the specified event", () => { - const mockListener = jest.fn() - mockStream.addEventListener(StreamEvents.data, mockListener) - expect(mockStream.eventListeners.data).toHaveLength(1) - }) - - it("should remove a listener from the specified event", () => { - const mockListener = jest.fn() - mockStream.addEventListener(StreamEvents.data, mockListener) - mockStream.removeEventListener(StreamEvents.data, mockListener) - expect(mockStream.eventListeners.data).toHaveLength(0) - }) - - it("should create a new stream using the provided method", () => { - mockStreamMethod.mockClear() - mockStream.connect() - expect(mockStreamMethod).toHaveBeenCalledWith(mockGrpcData, mockMetaData) - }) - - it("should dispatch the event to the listeners", () => { - const mockListener = jest.fn() - const mockEvent = {} as ServiceError - mockStream.addEventListener(StreamEvents.error, mockListener) - mockStream.handleEvent(StreamEvents.error, mockEvent) - expect(mockListener).toHaveBeenCalledWith(mockStream, mockEvent) - }) - - it("should schedule a reconnect using backoff", () => { - const backoff = 5000 - mockBackoff.next.mockReturnValue(backoff) - mockStreamMethod.mockClear() - mockStream.reconnect() - jest.advanceTimersByTime(backoff) - expect(mockStreamMethod).toHaveBeenCalledWith(mockGrpcData, mockMetaData) - }) -}) diff --git a/test/galoy/unit/utils/index.spec.ts b/test/galoy/unit/utils/index.spec.ts deleted file mode 100644 index 4f9d01660..000000000 --- a/test/galoy/unit/utils/index.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { btc2sat, sat2btc } from "@domain/bitcoin" -import { elapsedSinceTimestamp } from "@utils" - -describe("utils", () => { - describe("btc2sat", () => { - it("converts from BTC to Satoshis", () => { - expect(btc2sat(0)).toEqual(0) - expect(btc2sat(1.2)).toEqual(120000000) - expect(btc2sat(1.1235678)).toEqual(112356780) - expect(btc2sat(-1.2)).toEqual(-120000000) - expect(btc2sat(-1.1235678)).toEqual(-112356780) - }) - }) - - describe("sat2btc", () => { - it("converts from Satoshis to BTC", () => { - expect(sat2btc(0)).toEqual(0) - expect(sat2btc(120000000)).toEqual(1.2) - expect(sat2btc(112356780)).toEqual(1.1235678) - expect(sat2btc(-120000000)).toEqual(-1.2) - expect(sat2btc(-112356780)).toEqual(-1.1235678) - }) - }) - - describe("elapsedFromTimestamp", () => { - it("returns expected number of seconds for elapsed time", () => { - const timestamp = new Date() - const expectedElapsed = 20 - - const mockNowDate = new Date(timestamp) - mockNowDate.setSeconds(mockNowDate.getSeconds() + expectedElapsed) - jest - .spyOn(global.Date, "now") - .mockImplementationOnce(() => new Date(mockNowDate).valueOf()) - - const elapsed = elapsedSinceTimestamp(timestamp) - expect(elapsed).toBe(expectedElapsed) - }) - }) -})