diff --git a/packages/backend/src/open_payments/quote/service.test.ts b/packages/backend/src/open_payments/quote/service.test.ts index 13d2daa423..eb239231e3 100644 --- a/packages/backend/src/open_payments/quote/service.test.ts +++ b/packages/backend/src/open_payments/quote/service.test.ts @@ -377,7 +377,7 @@ describe('QuoteService', (): void => { test.each` expiryDate | description ${new Date(new Date().getTime() + Config.quoteLifespan - 2 * 60_000)} | ${"the incoming payment's expirataion date"} - ${new Date(new Date().getTime() + Config.quoteLifespan + 2 * 60_000)} | ${"the quotation's creation date plus its lifespan"} + ${new Date(new Date().getTime() + Config.quoteLifespan + 2 * 60_000)} | ${"the quote's creation date plus its lifespan"} `( 'sets expiry date to $description', async ({ expiryDate }): Promise => { @@ -406,11 +406,13 @@ describe('QuoteService', (): void => { .spyOn(paymentMethodHandlerService, 'getQuote') .mockResolvedValueOnce(mockedQuote) + jest.useFakeTimers() + const now = Date.now() + jest.spyOn(global.Date, 'now').mockImplementation(() => now) + const quote = await quoteService.create(options) assert.ok(!isQuoteError(quote)) - const maxExpiration = new Date( - quote.createdAt.getTime() + config.quoteLifespan - ) + const maxExpiration = new Date(now + config.quoteLifespan) expect(quote).toMatchObject({ walletAddressId: sendingWalletAddress.id, receiver: options.receiver, diff --git a/packages/backend/src/open_payments/quote/service.ts b/packages/backend/src/open_payments/quote/service.ts index 61ebf48b6c..084e10dfc7 100644 --- a/packages/backend/src/open_payments/quote/service.ts +++ b/packages/backend/src/open_payments/quote/service.ts @@ -56,7 +56,9 @@ async function getQuote( deps: ServiceDependencies, options: GetOptions ): Promise { - const quote = await Quote.query(deps.knex).get(options) + const quote = await Quote.query(deps.knex) + .get(options) + .withGraphFetched('fee') if (quote) { const asset = await deps.assetService.get(quote.assetId) if (asset) quote.asset = asset @@ -64,10 +66,6 @@ async function getQuote( quote.walletAddress = await deps.walletAddressService.get( quote.walletAddressId ) - - if (quote.feeId) { - quote.fee = await deps.feeService.get(quote.feeId) - } } return quote } @@ -93,167 +91,6 @@ export type CreateQuoteOptions = | QuoteOptionsWithDebitAmount | QuoteOptionsWithReceiveAmount -// async function createQuote( -// deps: ServiceDependencies, -// options: CreateQuoteOptions -// ): Promise { -// const stopTimer = deps.telemetry.startTimer('quote_service_create_time_ms', { -// callName: 'QuoteService:create', -// description: 'Time to create a quote' -// }) -// if (options.debitAmount && options.receiveAmount) { -// stopTimer() -// return QuoteError.InvalidAmount -// } -// const walletAddress = await deps.walletAddressService.get( -// options.walletAddressId -// ) -// if (!walletAddress) { -// stopTimer() -// return QuoteError.UnknownWalletAddress -// } -// if (!walletAddress.isActive) { -// stopTimer() -// return QuoteError.InactiveWalletAddress -// } -// if (options.debitAmount) { -// if ( -// options.debitAmount.value <= BigInt(0) || -// options.debitAmount.assetCode !== walletAddress.asset.code || -// options.debitAmount.assetScale !== walletAddress.asset.scale -// ) { -// stopTimer() -// return QuoteError.InvalidAmount -// } -// } -// if (options.receiveAmount) { -// if (options.receiveAmount.value <= BigInt(0)) { -// stopTimer() -// return QuoteError.InvalidAmount -// } -// } - -// try { -// const stopTimerReceiver = deps.telemetry.startTimer( -// 'quote_service_create_resolve_receiver_time_ms', -// { -// callName: 'QuoteService:resolveReceiver', -// description: 'Time to resolve receiver' -// } -// ) -// const receiver = await resolveReceiver(deps, options) -// stopTimerReceiver() - -// const paymentMethod = receiver.isLocal ? 'LOCAL' : 'ILP' -// const quoteId = uuid() - -// return await Quote.transaction(deps.knex, async (trx) => { -// const stopQuoteCreate = deps.telemetry.startTimer( -// 'quote_service_create_insert_time_ms', -// { -// callName: 'QuoteModel.insert', -// description: 'Time to insert quote' -// } -// ) -// const stopTimerQuote = deps.telemetry.startTimer( -// 'quote_service_create_get_quote_time_ms', -// { -// callName: 'PaymentMethodHandlerService:getQuote', -// description: 'Time to getQuote' -// } -// ) -// const quote = await deps.paymentMethodHandlerService.getQuote( -// paymentMethod, -// { -// quoteId, -// walletAddress, -// receiver, -// receiveAmount: options.receiveAmount, -// debitAmount: options.debitAmount -// }, -// trx -// ) -// stopTimerQuote() - -// const stopTimerFee = deps.telemetry.startTimer( -// 'quote_service_create_get_latest_fee_time_ms', -// { -// callName: 'FeeService:getLatestFee', -// description: 'Time to getLatestFee' -// } -// ) -// const sendingFee = await deps.feeService.getLatestFee( -// walletAddress.assetId, -// FeeType.Sending -// ) -// stopTimerFee() - -// const createdQuote = await Quote.query(trx).insertAndFetch({ -// id: quoteId, -// walletAddressId: options.walletAddressId, -// assetId: walletAddress.assetId, -// receiver: options.receiver, -// debitAmount: quote.debitAmount, -// receiveAmount: quote.receiveAmount, -// expiresAt: new Date(0), // expiresAt is patched in finalizeQuote -// client: options.client, -// feeId: sendingFee?.id, -// estimatedExchangeRate: quote.estimatedExchangeRate -// }) -// const asset = await deps.assetService.get(createdQuote.assetId) -// if (asset) createdQuote.asset = asset - -// createdQuote.walletAddress = await deps.walletAddressService.get( -// createdQuote.walletAddressId -// ) - -// createdQuote.fee = sendingFee - -// stopQuoteCreate() - -// const stopFinalize = deps.telemetry.startTimer( -// 'quote_service_finalize_quote_ms', -// { -// callName: 'QuoteService:finalizedQuote', -// description: 'Time to finalize quote' -// } -// ) -// const finalizedQuote = await finalizeQuote( -// { -// ...deps, -// knex: trx -// }, -// options, -// createdQuote, -// receiver -// ) -// stopFinalize() -// return finalizedQuote -// }) -// } catch (err) { -// if (isQuoteError(err)) { -// return err -// } - -// if ( -// err instanceof PaymentMethodHandlerError && -// err.code === PaymentMethodHandlerErrorCode.QuoteNonPositiveReceiveAmount -// ) { -// return QuoteError.NonPositiveReceiveAmount -// } - -// deps.logger.error({ err }, 'error creating a quote') -// throw err -// } finally { -// stopTimer() -// } -// } - -// - get fee before creation (getFeeById(sendingFee?.id)) -// - initialize obj for insert which is the current object but -// - call finalizeQuote with the object for insert and return the stuff thats currently patched (the amounts and expiry) -// - merge those things into the quote input and create the quote. - interface UnfinalizedQuote { id: string walletAddressId: string @@ -355,7 +192,6 @@ async function createQuote( receiveAmount: options.receiveAmount, debitAmount: options.debitAmount } - // trx ) stopTimerQuote() @@ -379,7 +215,7 @@ async function createQuote( description: 'Time to finalize quote' } ) - const patchOptions = await finalizeQuote2( + const finalQuoteOptions = await finalizeQuote( deps, options, unfinalizedQuote, @@ -387,45 +223,6 @@ async function createQuote( ) stopFinalize() - // const stopTimerQuoteTrx = deps.telemetry.startTimer( - // 'quote_service_create_get_quote_time_ms', - // { - // callName: 'QuoteService:create:transaction', - // description: 'Time to complete quote transaction' - // } - // ) - // const stopTimerQuote = deps.telemetry.startTimer( - // 'quote_service_create_get_quote_time_ms', - // { - // callName: 'PaymentMethodHandlerService:getQuote', - // description: 'Time to getQuote' - // } - // ) - - // // TODO: rm getQuote from this trx and change to return IlpQuoteDetails - // // instead to take the connector network calls out of th trx? - - // console.log('calling paymentMethodHandlerService.getQuote wtih', { - // quoteId, - // walletAddress, - // receiver, - // receiveAmount: options.receiveAmount, - // debitAmount: options.debitAmount - // }) - // const quote = await deps.paymentMethodHandlerService.getQuote( - // paymentMethod, - // { - // quoteId, - // walletAddress, - // receiver, - // receiveAmount: options.receiveAmount, - // debitAmount: options.debitAmount - // }, - // trx - // ) - // console.log('getQuote finished') - // stopTimerQuote() - const stopQuoteCreate = deps.telemetry.startTimer( 'quote_service_create_insert_time_ms', { @@ -433,16 +230,16 @@ async function createQuote( description: 'Time to insert quote' } ) - const createdQuote = await Quote.query(deps.knex).insertAndFetch({ - ...unfinalizedQuote, - ...patchOptions - }) + const createdQuote = await Quote.query(deps.knex) + .insertAndFetch({ + ...unfinalizedQuote, + ...finalQuoteOptions + }) + .withGraphFetched('fee') createdQuote.asset = walletAddress.asset createdQuote.walletAddress = walletAddress - if (sendingFee) createdQuote.fee = sendingFee stopQuoteCreate() - // stopTimerQuoteTrx() return createdQuote } catch (err) { if (isQuoteError(err)) { @@ -508,62 +305,7 @@ interface CalculateQuoteAmountsWithFeesResult { debitAmountMinusFees: bigint } -/** - * Calculate fixed-send quote amounts: debitAmount is locked, - * subtract fees (considering the exchange rate) from the receiveAmount. - */ -// function calculateFixedSendQuoteAmounts( -// deps: ServiceDependencies, -// quote: Quote, -// maxReceiveAmountValue: bigint -// ): CalculateQuoteAmountsWithFeesResult { -// // TODO: derive fee from debitAmount instead? Current behavior/tests may be wrong with basis point fees. -// const fees = quote.fee?.calculate(quote.receiveAmount.value) ?? BigInt(0) - -// const { estimatedExchangeRate } = quote - -// const exchangeAdjustedFees = BigInt( -// Math.ceil(Number(fees) * estimatedExchangeRate) -// ) -// const receiveAmountValue = -// BigInt(quote.receiveAmount.value) - exchangeAdjustedFees - -// if (receiveAmountValue <= BigInt(0)) { -// deps.logger.info( -// { fees, exchangeAdjustedFees, estimatedExchangeRate, receiveAmountValue }, -// 'Negative receive amount when calculating quote amount' -// ) -// throw QuoteError.NonPositiveReceiveAmount -// } - -// if (receiveAmountValue > maxReceiveAmountValue) { -// throw QuoteError.InvalidAmount -// } - -// const debitAmountMinusFees = -// quote.debitAmount.value - -// (quote.fee?.calculate(quote.debitAmount.value) ?? 0n) - -// deps.logger.debug( -// { -// 'quote.receiveAmount.value': quote.receiveAmount.value, -// debitAmountValue: quote.debitAmount.value, -// debitAmountMinusFees, -// receiveAmountValue, -// fees, -// exchangeAdjustedFees -// }, -// 'Calculated fixed-send quote amount with fees' -// ) - -// return { -// debitAmountValue: quote.debitAmount.value, -// debitAmountMinusFees, -// receiveAmountValue -// } -// } - -function calculateFixedSendQuoteAmounts2( +function calculateFixedSendQuoteAmounts( deps: ServiceDependencies, quote: UnfinalizedQuote, maxReceiveAmountValue: bigint @@ -614,65 +356,12 @@ function calculateFixedSendQuoteAmounts2( } } -/** - * Calculate fixed-delivery quote amounts: receiveAmount is locked, - * add fees to the the debitAmount. - */ -// function calculateFixedDeliveryQuoteAmounts( -// deps: ServiceDependencies, -// quote: Quote -// ): CalculateQuoteAmountsWithFeesResult { -// const fees = quote.fee?.calculate(quote.debitAmount.value) ?? BigInt(0) - -// const debitAmountValue = BigInt(quote.debitAmount.value) + fees - -// if (debitAmountValue <= BigInt(0)) { -// deps.logger.info( -// { fees, debitAmountValue }, -// 'Received negative debitAmount receive amount when calculating quote amount' -// ) -// throw QuoteError.InvalidAmount -// } - -// deps.logger.debug( -// { debitAmountValue, receiveAmountValue: quote.receiveAmount.value, fees }, -// `Calculated fixed-delivery quote amount with fees` -// ) - -// return { -// debitAmountValue, -// debitAmountMinusFees: quote.debitAmount.value, -// receiveAmountValue: quote.receiveAmount.value -// } -// } - -// function calculateExpiry( -// deps: ServiceDependencies, -// quote: Quote, -// receiver: Receiver -// ): Date { -// const quoteExpiry = new Date( -// quote.createdAt.getTime() + deps.config.quoteLifespan -// ) - -// const incomingPaymentExpiresEarlier = -// receiver.incomingPayment?.expiresAt && -// receiver.incomingPayment.expiresAt.getTime() < quoteExpiry.getTime() - -// return incomingPaymentExpiresEarlier -// ? receiver.incomingPayment!.expiresAt! -// : quoteExpiry -// } - -function calculateExpiry2( +function calculateExpiry( deps: ServiceDependencies, quote: UnfinalizedQuote, receiver: Receiver ): Date { - const quoteExpiry = new Date( - // quote.createdAt.getTime() + deps.config.quoteLifespan - Date.now() + deps.config.quoteLifespan - ) + const quoteExpiry = new Date(Date.now() + deps.config.quoteLifespan) const incomingPaymentExpiresEarlier = receiver.incomingPayment?.expiresAt && @@ -694,7 +383,7 @@ interface QuotePatchOptions { * Calculate fixed-delivery quote amounts: receiveAmount is locked, * add fees to the the debitAmount. */ -function calculateFixedDeliveryQuoteAmounts2( +function calculateFixedDeliveryQuoteAmounts( deps: ServiceDependencies, quote: UnfinalizedQuote ): CalculateQuoteAmountsWithFeesResult { @@ -722,52 +411,7 @@ function calculateFixedDeliveryQuoteAmounts2( } } -// async function finalizeQuote( -// deps: ServiceDependencies, -// options: CreateQuoteOptions, -// quote: Quote, -// receiver: Receiver -// ): Promise { -// let maxReceiveAmountValue: bigint | undefined - -// if (options.debitAmount) { -// const receivingPaymentValue = -// receiver.incomingAmount && receiver.receivedAmount -// ? receiver.incomingAmount.value - receiver.receivedAmount.value -// : undefined -// maxReceiveAmountValue = -// receivingPaymentValue && receivingPaymentValue < quote.receiveAmount.value -// ? receivingPaymentValue -// : quote.receiveAmount.value -// } - -// deps.logger.debug( -// { -// debitAmountValue: quote.debitAmount.value, -// receiveAmountValue: quote.receiveAmount.value, -// maxReceiveAmountValue -// }, -// `Calculating ${maxReceiveAmountValue ? 'fixed-send' : 'fixed-delivery'} quote amount with fees` -// ) - -// const { debitAmountValue, debitAmountMinusFees, receiveAmountValue } = -// maxReceiveAmountValue -// ? calculateFixedSendQuoteAmounts(deps, quote, maxReceiveAmountValue) -// : calculateFixedDeliveryQuoteAmounts(deps, quote) - -// const patchOptions = { -// debitAmountMinusFees, -// debitAmountValue, -// receiveAmountValue, -// expiresAt: calculateExpiry(deps, quote, receiver) -// } - -// await quote.$query(deps.knex).patch(patchOptions) - -// return quote -// } - -async function finalizeQuote2( +async function finalizeQuote( deps: ServiceDependencies, options: CreateQuoteOptions, quote: UnfinalizedQuote, @@ -797,14 +441,14 @@ async function finalizeQuote2( const { debitAmountValue, debitAmountMinusFees, receiveAmountValue } = maxReceiveAmountValue - ? calculateFixedSendQuoteAmounts2(deps, quote, maxReceiveAmountValue) - : calculateFixedDeliveryQuoteAmounts2(deps, quote) + ? calculateFixedSendQuoteAmounts(deps, quote, maxReceiveAmountValue) + : calculateFixedDeliveryQuoteAmounts(deps, quote) const patchOptions = { debitAmountMinusFees, debitAmountValue, receiveAmountValue, - expiresAt: calculateExpiry2(deps, quote, receiver) + expiresAt: calculateExpiry(deps, quote, receiver) } return patchOptions