Skip to content

Commit 33e54d5

Browse files
authored
fix(backend): update minSendAmount in local and ILP payments (#3480)
* chore(backend): return minSendAmount in local payment when debitAmount < 0 * chore(backend): return at least 2 as minSendAmount in ILP payment
1 parent 2386dba commit 33e54d5

File tree

4 files changed

+64
-13
lines changed

4 files changed

+64
-13
lines changed

packages/backend/src/payment-method/ilp/service.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { IlpPaymentService, retryableIlpErrors } from './service'
1+
import {
2+
IlpPaymentService,
3+
calculateMinSendAmount,
4+
retryableIlpErrors
5+
} from './service'
26
import { initIocContainer } from '../../'
37
import { createTestApp, TestContainer } from '../../tests/app'
48
import { IAppConfig, Config } from '../../config/app'
@@ -972,4 +976,32 @@ describe('IlpPaymentService', (): void => {
972976
}
973977
})
974978
})
979+
980+
describe('calculateMinSendAmount', (): void => {
981+
test('returns reciprocal of highEstimatedExchangeRate', async (): Promise<void> => {
982+
expect(
983+
calculateMinSendAmount({
984+
highEstimatedExchangeRate: Pay.Ratio.from(0.05)
985+
} as Pay.Quote)
986+
).toBe(20n)
987+
expect(
988+
calculateMinSendAmount({
989+
highEstimatedExchangeRate: Pay.Ratio.from(0.01)
990+
} as Pay.Quote)
991+
).toBe(100n)
992+
})
993+
994+
test('returns at least 2 even if highEstimatedExchangeRate reciprocal under 2', async (): Promise<void> => {
995+
expect(
996+
calculateMinSendAmount({
997+
highEstimatedExchangeRate: Pay.Ratio.from(1)
998+
} as Pay.Quote)
999+
).toBe(2n)
1000+
expect(
1001+
calculateMinSendAmount({
1002+
highEstimatedExchangeRate: Pay.Ratio.from(20)
1003+
} as Pay.Quote)
1004+
).toBe(2n)
1005+
})
1006+
})
9751007
})

packages/backend/src/payment-method/ilp/service.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,7 @@ async function getQuote(
126126
retryable: false,
127127
code: PaymentMethodHandlerErrorCode.QuoteNonPositiveReceiveAmount,
128128
details: {
129-
minSendAmount: BigInt(
130-
Math.ceil(ilpQuote.highEstimatedExchangeRate.reciprocal().valueOf())
131-
)
129+
minSendAmount: calculateMinSendAmount(ilpQuote)
132130
}
133131
})
134132
}
@@ -172,9 +170,7 @@ async function getQuote(
172170
retryable: false,
173171
code: PaymentMethodHandlerErrorCode.QuoteNonPositiveReceiveAmount,
174172
details: {
175-
minSendAmount: BigInt(
176-
Math.ceil(ilpQuote.highEstimatedExchangeRate.reciprocal().valueOf())
177-
)
173+
minSendAmount: calculateMinSendAmount(ilpQuote)
178174
}
179175
})
180176
}
@@ -205,9 +201,7 @@ async function getQuote(
205201
retryable: false,
206202
code: PaymentMethodHandlerErrorCode.QuoteNonPositiveReceiveAmount,
207203
details: {
208-
minSendAmount: BigInt(
209-
Math.ceil(ilpQuote.highEstimatedExchangeRate.reciprocal().valueOf())
210-
)
204+
minSendAmount: calculateMinSendAmount(ilpQuote)
211205
}
212206
})
213207
}
@@ -392,6 +386,14 @@ async function pay(
392386
}
393387
}
394388

389+
export function calculateMinSendAmount(quote: Pay.Quote): bigint {
390+
const minSendAmount = Math.ceil(
391+
quote.highEstimatedExchangeRate.reciprocal().valueOf()
392+
)
393+
394+
return BigInt(Math.max(minSendAmount, 2)) // because of rounding in ILP pay, you must always send at least 2 units of an asset
395+
}
396+
395397
export function canRetryError(err: Error | Pay.PaymentError): boolean {
396398
return err instanceof Error || !!retryableIlpErrors[err]
397399
}

packages/backend/src/payment-method/local/service.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,14 @@ describe('LocalPaymentService', (): void => {
185185
})
186186

187187
test('fails if debit amount is non-positive', async (): Promise<void> => {
188-
expect.assertions(4)
188+
jest
189+
.spyOn(await deps.use('ratesService'), 'convertDestination')
190+
.mockImplementation(() =>
191+
Promise.resolve({ amount: 0n, scaledExchangeRate: 1 })
192+
)
193+
194+
expect.assertions(5)
195+
189196
try {
190197
await localPaymentService.getQuote({
191198
walletAddress: walletAddressMap['USD'],
@@ -205,6 +212,9 @@ describe('LocalPaymentService', (): void => {
205212
'debit amount of local quote is non-positive'
206213
)
207214
expect((err as PaymentMethodHandlerError).retryable).toBe(false)
215+
expect((err as PaymentMethodHandlerError).details).toEqual({
216+
minSendAmount: 1n
217+
})
208218
}
209219
})
210220
test('fails if receive amount is non-positive', async (): Promise<void> => {
@@ -214,7 +224,7 @@ describe('LocalPaymentService', (): void => {
214224
.mockImplementation(() =>
215225
Promise.resolve({ amount: 100n, scaledExchangeRate: 1 })
216226
)
217-
expect.assertions(4)
227+
expect.assertions(5)
218228
try {
219229
await localPaymentService.getQuote({
220230
walletAddress: walletAddressMap['USD'],
@@ -234,6 +244,9 @@ describe('LocalPaymentService', (): void => {
234244
'receive amount of local quote is non-positive'
235245
)
236246
expect((err as PaymentMethodHandlerError).retryable).toBe(false)
247+
expect((err as PaymentMethodHandlerError).details).toEqual({
248+
minSendAmount: 1n
249+
})
237250
}
238251
})
239252

packages/backend/src/payment-method/local/service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,11 @@ async function getQuote(
173173
if (debitAmountValue <= BigInt(0)) {
174174
throw new PaymentMethodHandlerError('Received error during local quoting', {
175175
description: 'debit amount of local quote is non-positive',
176-
retryable: false
176+
retryable: false,
177+
code: PaymentMethodHandlerErrorCode.QuoteNonPositiveReceiveAmount,
178+
details: {
179+
minSendAmount: BigInt(Math.ceil(1 / exchangeRate))
180+
}
177181
})
178182
}
179183

0 commit comments

Comments
 (0)