diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index fbaa03c0b8..d2a7083171 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -323,10 +323,16 @@ export class App { process.nextTick(() => this.processWalletAddress()) } for (let i = 0; i < this.config.outgoingPaymentWorkers; i++) { - process.nextTick(() => this.processOutgoingPayment()) + setTimeout( + () => this.processOutgoingPayment(), + this.config.outgoingPaymentProcessingIntervalMs + ) } for (let i = 0; i < this.config.incomingPaymentWorkers; i++) { - process.nextTick(() => this.processIncomingPayment()) + setTimeout( + () => this.processIncomingPayment(), + this.config.incomingPaymentProcessingIntervalMs + ) } for (let i = 0; i < this.config.webhookWorkers; i++) { process.nextTick(() => this.processWebhook()) @@ -888,11 +894,15 @@ export class App { return true }) .then((hasMoreWork) => { - if (hasMoreWork) process.nextTick(() => this.processOutgoingPayment()) + if (hasMoreWork) + setTimeout( + () => this.processOutgoingPayment(), + this.config.outgoingPaymentProcessingIntervalMs + ) else setTimeout( () => this.processOutgoingPayment(), - this.config.outgoingPaymentWorkerIdle + this.config.outgoingPaymentProcessingIntervalMs ).unref() }) } @@ -938,11 +948,15 @@ export class App { return true }) .then((hasMoreWork) => { - if (hasMoreWork) process.nextTick(() => this.processIncomingPayment()) + if (hasMoreWork) + setTimeout( + () => this.processIncomingPayment(), + this.config.incomingPaymentProcessingIntervalMs + ) else setTimeout( () => this.processIncomingPayment(), - this.config.incomingPaymentWorkerIdle + this.config.incomingPaymentProcessingIntervalMs ).unref() }) } diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 6a4b6e5218..b61335863b 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -134,9 +134,19 @@ export const Config = { outgoingPaymentWorkers: envInt('OUTGOING_PAYMENT_WORKERS', 1), outgoingPaymentWorkerIdle: envInt('OUTGOING_PAYMENT_WORKER_IDLE', 10), // milliseconds + outgoingPaymentBatchSize: envInt('OUTGOING_PAYMENT_WORKER_BATCH_SIZE', 250), + outgoingPaymentProcessingIntervalMs: envInt( + 'OUTGOING_PAYMENT_PROCESSING_INTERVAL_MS', + 200 + ), // milliseconds incomingPaymentWorkers: envInt('INCOMING_PAYMENT_WORKERS', 1), incomingPaymentWorkerIdle: envInt('INCOMING_PAYMENT_WORKER_IDLE', 200), // milliseconds + incomingPaymentBatchSize: envInt('INCOMING_PAYMENT_WORKER_BATCH_SIZE', 250), + incomingPaymentProcessingIntervalMs: envInt( + 'INCOMING_PAYMENT_PROCESSING_INTERVAL_MS', + 200 + ), // milliseconds pollIncomingPaymentCreatedWebhook: envBool( 'POLL_INCOMING_PAYMENT_CREATED_WEBHOOK', false diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 9f8654a747..52930be254 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -755,6 +755,11 @@ export const start = async ( logger.info(`Connector listening on ${config.connectorPort}`) logger.info('🐒 has 🚀. Get ready for 🍌🍌🍌🍌🍌') + logger.info( + `Running the Rafiki WW 2026 build with an outgoing payment batch size of ${config.outgoingPaymentBatchSize}.` + ) + logger.info(`Happy Testing - Nathan 🫡`) + if (config.enableAutoPeering) { await app.startAutoPeeringServer(config.autoPeeringServerPort) logger.info( diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index a9cc51e43a..b201e41246 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -772,7 +772,7 @@ describe('Incoming Payment Service', (): void => { jest.useFakeTimers() jest.setSystemTime(incomingPayment.expiresAt) - await expect(incomingPaymentService.processNext()).resolves.toBe( + await expect(incomingPaymentService.processNext()).resolves.toContain( incomingPayment.id ) await expect( @@ -803,7 +803,7 @@ describe('Incoming Payment Service', (): void => { }) jest.useFakeTimers() jest.setSystemTime(incomingPayment.expiresAt) - await expect(incomingPaymentService.processNext()).resolves.toBe( + await expect(incomingPaymentService.processNext()).resolves.toContain( incomingPayment.id ) @@ -886,9 +886,9 @@ describe('Incoming Payment Service', (): void => { if (eventType === IncomingPaymentEventType.IncomingPaymentExpired) { jest.useFakeTimers() jest.setSystemTime(incomingPayment.expiresAt) - await expect(incomingPaymentService.processNext()).resolves.toBe( - incomingPayment.id - ) + await expect( + incomingPaymentService.processNext() + ).resolves.toContain(incomingPayment.id) } else { await incomingPayment.onCredit({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -923,7 +923,7 @@ describe('Incoming Payment Service', (): void => { assert.ok(incomingPayment.processAt) jest.useFakeTimers() jest.setSystemTime(incomingPayment.processAt) - await expect(incomingPaymentService.processNext()).resolves.toBe( + await expect(incomingPaymentService.processNext()).resolves.toContain( incomingPayment.id ) const events = await IncomingPaymentEvent.query(knex) @@ -974,9 +974,9 @@ describe('Incoming Payment Service', (): void => { assert.ok(incomingPayment.processAt) jest.useFakeTimers() jest.setSystemTime(incomingPayment.processAt) - await expect(incomingPaymentService.processNext()).resolves.toBe( - incomingPayment.id - ) + await expect( + incomingPaymentService.processNext() + ).resolves.toContain(incomingPayment.id) const events = await IncomingPaymentEvent.query(knex) .where({ incomingPaymentId: incomingPayment.id, @@ -1127,7 +1127,7 @@ describe('Incoming Payment Service', (): void => { const future = new Date(Date.now() + 40_000) jest.useFakeTimers() jest.setSystemTime(future) - await expect(incomingPaymentService.processNext()).resolves.toBe( + await expect(incomingPaymentService.processNext()).resolves.toContain( incomingPayment.id ) await expect( diff --git a/packages/backend/src/open_payments/payment/incoming/service.ts b/packages/backend/src/open_payments/payment/incoming/service.ts index 3965a4c403..6498172387 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.ts @@ -76,7 +76,7 @@ export interface IncomingPaymentService id: string, tenantId?: string ): Promise - processNext(): Promise + processNext(): Promise update( options: UpdateOptions ): Promise @@ -313,43 +313,46 @@ async function getApprovedOrCanceledIncomingPayment( // Returns the id of the processed incoming payment (if any). async function processNextIncomingPayment( deps_: ServiceDependencies -): Promise { +): Promise { return deps_.knex.transaction(async (trx) => { const now = new Date(Date.now()).toISOString() const incomingPayments = await IncomingPayment.query(trx) - .limit(1) + .limit(deps_.config.incomingPaymentBatchSize) // Ensure the incoming payments cannot be processed concurrently by multiple workers. .forUpdate() // If an incoming payment is locked, don't wait — just come back for it later. .skipLocked() .where('processAt', '<=', now) - const incomingPayment = incomingPayments[0] - if (!incomingPayment) return + if (incomingPayments.length === 0) return - const asset = await deps_.assetService.get(incomingPayment.assetId) - if (asset) incomingPayment.asset = asset - - incomingPayment.walletAddress = await deps_.walletAddressService.get( - incomingPayment.walletAddressId - ) + const processIncoming = async (incomingPayment: IncomingPayment) => { + const asset = await deps_.assetService.get(incomingPayment.assetId) + if (asset) incomingPayment.asset = asset + incomingPayment.walletAddress = await deps_.walletAddressService.get( + incomingPayment.walletAddressId + ) - const deps = { - ...deps_, - knex: trx, - logger: deps_.logger.child({ - incomingPayment: incomingPayment.id - }) - } - if ( - incomingPayment.state === IncomingPaymentState.Expired || - incomingPayment.state === IncomingPaymentState.Completed - ) { - await handleDeactivated(deps, incomingPayment) - } else { - await handleExpired(deps, incomingPayment) + const deps = { + ...deps_, + knex: trx, + logger: deps_.logger.child({ + incomingPayment: incomingPayment.id + }) + } + if ( + incomingPayment.state === IncomingPaymentState.Expired || + incomingPayment.state === IncomingPaymentState.Completed + ) { + await handleDeactivated(deps, incomingPayment) + } else { + await handleExpired(deps, incomingPayment) + } + return incomingPayment.id } - return incomingPayment.id + + const promises = incomingPayments.map(processIncoming) + return Promise.all(promises) }) } diff --git a/packages/backend/src/open_payments/payment/outgoing/lifecycle.test.ts b/packages/backend/src/open_payments/payment/outgoing/lifecycle.test.ts index 56029348f0..5f8829e5af 100644 --- a/packages/backend/src/open_payments/payment/outgoing/lifecycle.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/lifecycle.test.ts @@ -171,7 +171,7 @@ describe('Lifecycle', (): void => { describe('Grant Spent Amounts', (): void => { beforeAll(async (): Promise => { - deps = initIocContainer(Config) + deps = initIocContainer({ ...Config, outgoingPaymentBatchSize: 1 }) appContainer = await createTestApp(deps) outgoingPaymentService = await deps.use('outgoingPaymentService') accountingService = await deps.use('accountingService') @@ -270,8 +270,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finalPayment = await outgoingPaymentService.get({ id: payment.id @@ -336,8 +336,8 @@ describe('Lifecycle', (): void => { }) jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finalPayment = await outgoingPaymentService.get({ id: payment.id @@ -418,8 +418,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finalPayment = await outgoingPaymentService.get({ id: payment.id @@ -494,8 +494,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finalPayment = await outgoingPaymentService.get({ id: payment.id @@ -582,8 +582,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const latestGrantSpentAmounts = await OutgoingPaymentGrantSpentAmounts.query(knex) @@ -681,8 +681,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(secondPayment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(secondPayment.id) const finalPayment = await outgoingPaymentService.get({ id: secondPayment.id @@ -771,8 +771,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(secondPayment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(secondPayment.id) const finalPayment = await outgoingPaymentService.get({ id: secondPayment.id @@ -875,8 +875,8 @@ describe('Lifecycle', (): void => { // advance time to ensure spents amounts created by processNext, if any, have // later createdAt so that fetching latest is accurate jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(secondPayment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(secondPayment.id) const finalPayment = await outgoingPaymentService.get({ id: secondPayment.id @@ -949,9 +949,9 @@ describe('Lifecycle', (): void => { firstPayment ) ) - const id = await outgoingPaymentService.processNext() + const ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(firstPayment.id) + expect(ids).toContain(firstPayment.id) // Grant spent amounts should correspond to the first payment // Should not detect a difference and insert a new spent amount. @@ -1066,9 +1066,9 @@ describe('Lifecycle', (): void => { receive: firstPaymentSettledAmount })(accountingService, receiverWalletAddressId, firstPayment) ) - let id = await outgoingPaymentService.processNext() + let ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(firstPayment.id) + expect(ids).toContain(firstPayment.id) latestSpentAmounts[2] = await OutgoingPaymentGrantSpentAmounts.query(knex) @@ -1114,9 +1114,9 @@ describe('Lifecycle', (): void => { receive: secondPaymentSettledAmount })(accountingService, receiverWalletAddressId, secondPayment) ) - id = await outgoingPaymentService.processNext() + ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(secondPayment.id) + expect(ids).toContain(secondPayment.id) latestSpentAmounts[3] = await OutgoingPaymentGrantSpentAmounts.query(knex) @@ -1261,9 +1261,9 @@ describe('Lifecycle', (): void => { receive: firstPaymentSettledAmount })(accountingService, receiverWalletAddressId, firstPayment) ) - let id = await outgoingPaymentService.processNext() + let ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(firstPayment.id) + expect(ids).toContain(firstPayment.id) latestSpentAmounts[2] = await OutgoingPaymentGrantSpentAmounts.query(knex) @@ -1357,9 +1357,9 @@ describe('Lifecycle', (): void => { receive: secondPaymentSettledAmount })(accountingService, receiverWalletAddressId, secondPayment) ) - id = await outgoingPaymentService.processNext() + ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(secondPayment.id) + expect(ids).toContain(secondPayment.id) latestSpentAmounts[4] = await OutgoingPaymentGrantSpentAmounts.query(knex) @@ -1501,9 +1501,9 @@ describe('Lifecycle', (): void => { jest .spyOn(paymentMethodHandlerService, 'pay') .mockImplementationOnce(mockPayErrorFactory()()) - let id = await outgoingPaymentService.processNext() + let ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(firstPayment.id) + expect(ids).toContain(firstPayment.id) const failedPayment = await outgoingPaymentService.get({ id: firstPayment.id @@ -1552,9 +1552,9 @@ describe('Lifecycle', (): void => { secondPayment ) ) - id = await outgoingPaymentService.processNext() + ids = await outgoingPaymentService.processNext() jest.advanceTimersByTime(500) - expect(id).toBe(secondPayment.id) + expect(ids).toContain(secondPayment.id) const completedPayment = await outgoingPaymentService.get({ id: secondPayment.id @@ -1716,8 +1716,8 @@ describe('Lifecycle', (): void => { // Process payment after interval boundary (in February) jest.setSystemTime(new Date('2025-02-01T00:00:01Z')) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finishSpentAmounts = await OutgoingPaymentGrantSpentAmounts.query( knex @@ -1799,8 +1799,8 @@ describe('Lifecycle', (): void => { // Process payment after interval boundary (in February) jest.setSystemTime(new Date('2025-02-01T00:00:01Z')) jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const endSpentAmounts = await OutgoingPaymentGrantSpentAmounts.query( knex @@ -1899,8 +1899,8 @@ describe('Lifecycle', (): void => { // Process payment after interval boundary (in February) jest.setSystemTime(new Date('2025-02-01T00:00:01Z')) jest.advanceTimersByTime(500) - const processedPaymentId = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(payment.id) + const processedPaymentIds = await outgoingPaymentService.processNext() + expect(processedPaymentIds).toContain(payment.id) const finalPayment = await outgoingPaymentService.get({ id: payment.id @@ -2090,9 +2090,9 @@ describe('Lifecycle', (): void => { })(accountingService, receiverWalletAddressId, firstPayment) ) - const processedPaymentId = + const processedPaymentIds = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(firstPayment.id) + expect(processedPaymentIds).toContain(firstPayment.id) const completedPayment = await outgoingPaymentService.get({ id: firstPayment.id @@ -2279,9 +2279,9 @@ describe('Lifecycle', (): void => { .spyOn(paymentMethodHandlerService, 'pay') .mockImplementationOnce(mockPayErrorFactory()()) - const processedPaymentId = + const processedPaymentIds = await outgoingPaymentService.processNext() - expect(processedPaymentId).toBe(firstPayment.id) + expect(processedPaymentIds).toContain(firstPayment.id) const failedPayment = await outgoingPaymentService.get({ id: firstPayment.id diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index 2ece3667cd..d083b15225 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -126,7 +126,9 @@ describe('OutgoingPaymentService', (): void => { expectState: OutgoingPaymentState, expectedError?: string ): Promise { - await expect(outgoingPaymentService.processNext()).resolves.toBe(paymentId) + await expect(outgoingPaymentService.processNext()).resolves.toEqual([ + paymentId + ]) const payment = await outgoingPaymentService.get({ id: paymentId }) diff --git a/packages/backend/src/open_payments/payment/outgoing/service.ts b/packages/backend/src/open_payments/payment/outgoing/service.ts index af3eb68714..a8d4061d18 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.ts @@ -70,7 +70,7 @@ export interface OutgoingPaymentService fund( options: FundOutgoingPaymentOptions ): Promise - processNext(): Promise + processNext(): Promise getGrantSpentAmounts(options: { grantId: string limits?: Limits @@ -103,7 +103,7 @@ export async function createOutgoingPaymentService( create: (options) => createOutgoingPayment(deps, options), cancel: (options) => cancelOutgoingPayment(deps, options), fund: (options) => fundPayment(deps, options), - processNext: () => worker.processPendingPayment(deps), + processNext: () => worker.processPendingPayments(deps), getWalletAddressPage: (options) => getWalletAddressPage(deps, options), getGrantSpentAmounts: (options) => getGrantSpentAmounts(deps, options) } diff --git a/packages/backend/src/open_payments/payment/outgoing/worker.ts b/packages/backend/src/open_payments/payment/outgoing/worker.ts index 516e096890..a31edae60c 100644 --- a/packages/backend/src/open_payments/payment/outgoing/worker.ts +++ b/packages/backend/src/open_payments/payment/outgoing/worker.ts @@ -11,9 +11,9 @@ import { trace, Span } from '@opentelemetry/api' export const RETRY_BACKOFF_SECONDS = 10 // Returns the id of the processed payment (if any). -export async function processPendingPayment( +export async function processPendingPayments( deps_: ServiceDependencies -): Promise { +): Promise { const tracer = trace.getTracer('outgoing_payment_worker') return tracer.startActiveSpan( @@ -25,42 +25,45 @@ export async function processPendingPayment( callName: 'OutgoingPaymentWorker:processPendingPayment' } ) - const paymentId = await deps_.knex.transaction(async (trx) => { - const payment = await getPendingPayment(trx, deps_) - if (!payment) return - - await handlePaymentLifecycle( - { - ...deps_, - knex: trx, - logger: deps_.logger.child({ - payment: payment.id, - from_state: payment.state - }) - }, - payment + const paymentIds = await deps_.knex.transaction(async (trx) => { + const payments = await getPendingPayments(trx, deps_) + if (payments.length === 0) return + + const promises = payments.map((payment) => + handlePaymentLifecycle( + { + ...deps_, + knex: trx, + logger: deps_.logger.child({ + payment: payment.id, + from_state: payment.state + }) + }, + payment + ) ) - return payment.id + await Promise.all(promises) + return payments.map((payment) => payment.id) }) stopTimer() span.end() - return paymentId + return paymentIds } ) } // Fetch (and lock) a payment for work. -async function getPendingPayment( +async function getPendingPayments( trx: Knex.Transaction, deps: ServiceDependencies -): Promise { +): Promise { const stopTimer = deps.telemetry.startTimer('get_pending_payment_ms', { callName: 'OutoingPaymentWorker:getPendingPayment' }) const now = new Date(Date.now()).toISOString() const payments = await OutgoingPayment.query(trx) - .limit(1) + .limit(deps.config.outgoingPaymentBatchSize) // Ensure the payment cannot be processed concurrently by multiple workers. .forUpdate() // Don't wait for a payment that is already being processed. @@ -77,7 +80,7 @@ async function getPendingPayment( }) .withGraphFetched('quote') stopTimer() - return payments[0] + return payments } async function handlePaymentLifecycle( diff --git a/test/performance/scripts/create-outgoing-payments.js b/test/performance/scripts/create-outgoing-payments.js index 5815dc565e..25fa5878b1 100644 --- a/test/performance/scripts/create-outgoing-payments.js +++ b/test/performance/scripts/create-outgoing-payments.js @@ -161,7 +161,27 @@ export default function (data) { } } - request(createOutgoingPaymentPayload) + const createOutgoingPaymentResponse = request(createOutgoingPaymentPayload) + const outgoingPayment = + createOutgoingPaymentResponse.createOutgoingPayment.payment + + const fundOutgoingPaymentPayload = { + query: ` + mutation DepositOutgoingPaymentLiquidity($input: DepositOutgoingPaymentLiquidityInput!) { + depositOutgoingPaymentLiquidity(input: $input) { + success + } + } + `, + variables: { + input: { + outgoingPaymentId: outgoingPayment.id, + idempotencyKey: uuidv4() + } + } + } + + request(fundOutgoingPaymentPayload) } export function handleSummary(data) {