Skip to content

Commit 7d0a177

Browse files
committed
feat: tenanted rates endpoints - first iteration, WIP
1 parent 358332e commit 7d0a177

File tree

8 files changed

+322
-151
lines changed

8 files changed

+322
-151
lines changed

packages/backend/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,18 @@ export function initIocContainer(
253253
return createRatesService({
254254
logger: await deps.use('logger'),
255255
exchangeRatesUrl: config.exchangeRatesUrl,
256-
exchangeRatesLifetime: config.exchangeRatesLifetime
256+
exchangeRatesLifetime: config.exchangeRatesLifetime,
257+
tenantSettingService: await deps.use('tenantSettingService')
257258
})
258259
})
259260

260261
container.singleton('internalRatesService', async (deps) => {
261262
return createRatesService({
262263
logger: await deps.use('logger'),
263264
exchangeRatesUrl: config.telemetryExchangeRatesUrl,
264-
exchangeRatesLifetime: config.telemetryExchangeRatesLifetime
265+
exchangeRatesLifetime: config.telemetryExchangeRatesLifetime,
266+
//TODO maybe not use this for the internal rates service?
267+
tenantSettingService: await deps.use('tenantSettingService')
265268
})
266269
})
267270

packages/backend/src/payment-method/ilp/connector/core/middleware/balance.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ export function createBalanceMiddleware(): ILPMiddleware {
4040
}
4141

4242
const sourceAmount = BigInt(amount)
43-
const destinationAmountOrError = await services.rates.convertSource({
44-
sourceAmount,
45-
sourceAsset: accounts.incoming.asset,
46-
destinationAsset: accounts.outgoing.asset
47-
})
43+
const destinationAmountOrError = await services.rates.convertSource(
44+
{
45+
sourceAmount,
46+
sourceAsset: accounts.incoming.asset,
47+
destinationAsset: accounts.outgoing.asset
48+
},
49+
AppConfig.operatorTenantId //TODO when tenanted peers gets merged, this will replaced by: services.peers.get(accounts.incoming.id).tenantId
50+
)
4851
if (isConvertError(destinationAmountOrError)) {
4952
logger.error(
5053
{

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import { truncateTables } from '../../tests/tableManager'
2727
import { createOutgoingPaymentWithReceiver } from '../../tests/outgoingPayment'
2828
import { v4 as uuid } from 'uuid'
2929
import { IlpQuoteDetails } from './quote-details/model'
30+
import { CreateOptions } from '../../tenants/settings/service'
31+
import {
32+
createTenantSettings,
33+
exchangeRatesSetting
34+
} from '../../tests/tenantSettings'
3035

3136
const nock = (global as unknown as { nock: typeof import('nock') }).nock
3237

@@ -38,7 +43,9 @@ describe('IlpPaymentService', (): void => {
3843
let config: IAppConfig
3944
let tenantId: string
4045

46+
//TODO tests for both default and tenanted exchange rates
4147
const exchangeRatesUrl = 'https://example-rates.com'
48+
let tenantExchangeRatesUrl: string
4249

4350
const assetMap: Record<string, Asset> = {}
4451
const walletAddressMap: Record<string, WalletAddress> = {}
@@ -77,6 +84,14 @@ describe('IlpPaymentService', (): void => {
7784
tenantId,
7885
assetId: assetMap['EUR'].id
7986
})
87+
88+
const createOptions: CreateOptions = {
89+
tenantId,
90+
setting: [exchangeRatesSetting()]
91+
}
92+
93+
const tenantSetting = createTenantSettings(deps, createOptions)
94+
tenantExchangeRatesUrl = (await tenantSetting).value
8095
})
8196

8297
afterEach(async (): Promise<void> => {
@@ -95,7 +110,7 @@ describe('IlpPaymentService', (): void => {
95110

96111
describe('getQuote', (): void => {
97112
test('calls rates service with correct base asset', async (): Promise<void> => {
98-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
113+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
99114

100115
const options: StartQuoteOptions = {
101116
quoteId: uuid(),
@@ -113,12 +128,12 @@ describe('IlpPaymentService', (): void => {
113128

114129
await ilpPaymentService.getQuote(options)
115130

116-
expect(ratesServiceSpy).toHaveBeenCalledWith('USD')
131+
expect(ratesServiceSpy).toHaveBeenCalledWith('USD', tenantId)
117132
ratesScope.done()
118133
})
119134

120135
test('inserts ilpQuoteDetails', async (): Promise<void> => {
121-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
136+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
122137
const quoteId = uuid()
123138
const options: StartQuoteOptions = {
124139
quoteId,
@@ -180,7 +195,7 @@ describe('IlpPaymentService', (): void => {
180195
})
181196

182197
test('creates a quote with large exchange rate amounts', async (): Promise<void> => {
183-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
198+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
184199
const quoteId = uuid()
185200
const options: StartQuoteOptions = {
186201
quoteId,
@@ -298,7 +313,7 @@ describe('IlpPaymentService', (): void => {
298313
})
299314

300315
test('returns all fields correctly', async (): Promise<void> => {
301-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
316+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
302317

303318
const options: StartQuoteOptions = {
304319
quoteId: uuid(),
@@ -330,7 +345,7 @@ describe('IlpPaymentService', (): void => {
330345
})
331346

332347
test('uses receiver.incomingAmount if receiveAmount is not provided', async (): Promise<void> => {
333-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
348+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
334349

335350
const incomingAmount = {
336351
assetCode: 'USD',
@@ -370,7 +385,7 @@ describe('IlpPaymentService', (): void => {
370385
() => config,
371386
{ slippage: 101 },
372387
async () => {
373-
mockRatesApi(exchangeRatesUrl, () => ({}))
388+
mockRatesApi(tenantExchangeRatesUrl, () => ({}))
374389

375390
expect.assertions(4)
376391
try {
@@ -398,7 +413,7 @@ describe('IlpPaymentService', (): void => {
398413
)())
399414

400415
test('throws if quote returns invalid maxSourceAmount', async (): Promise<void> => {
401-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
416+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
402417

403418
const options: StartQuoteOptions = {
404419
quoteId: uuid(),
@@ -428,7 +443,7 @@ describe('IlpPaymentService', (): void => {
428443
})
429444

430445
test('throws if quote returns invalid minDeliveryAmount', async (): Promise<void> => {
431-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
446+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
432447

433448
const options: StartQuoteOptions = {
434449
quoteId: uuid(),
@@ -469,7 +484,7 @@ describe('IlpPaymentService', (): void => {
469484
})
470485

471486
test('throws if quote returns with a non-positive estimated delivery amount', async (): Promise<void> => {
472-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({}))
487+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({}))
473488

474489
const options: StartQuoteOptions = {
475490
quoteId: uuid(),
@@ -524,7 +539,7 @@ describe('IlpPaymentService', (): void => {
524539
() => config,
525540
{ slippage },
526541
async () => {
527-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({
542+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({
528543
[incomingAssetCode]: exchangeRate
529544
}))
530545

@@ -584,7 +599,7 @@ describe('IlpPaymentService', (): void => {
584599
() => config,
585600
{ slippage },
586601
async () => {
587-
const ratesScope = mockRatesApi(exchangeRatesUrl, () => ({
602+
const ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({
588603
[incomingAssetCode]: exchangeRate
589604
}))
590605

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ async function getQuote(
6464

6565
let rates
6666
try {
67-
rates = await deps.ratesService.rates(options.walletAddress.asset.code)
67+
rates = await deps.ratesService.rates(
68+
options.walletAddress.asset.code,
69+
options.walletAddress.tenantId
70+
)
6871
} catch (_err) {
6972
throw new PaymentMethodHandlerError('Received error during ILP quoting', {
7073
description: 'Could not get rates from service',

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import { IncomingPaymentService } from '../../open_payments/payment/incoming/ser
2424
import { errorToMessage, TransferError } from '../../accounting/errors'
2525
import { PaymentMethodHandlerError } from '../handler/errors'
2626
import { ConvertError } from '../../rates/service'
27+
import {
28+
createTenantSettings,
29+
exchangeRatesSetting
30+
} from '../../tests/tenantSettings'
31+
import { CreateOptions } from '../../tenants/settings/service'
2732

2833
const nock = (global as unknown as { nock: typeof import('nock') }).nock
2934

@@ -35,7 +40,9 @@ describe('LocalPaymentService', (): void => {
3540
let incomingPaymentService: IncomingPaymentService
3641
let tenantId: string
3742

43+
//TODO tests for both default and tenanted exchange rates
3844
const exchangeRatesUrl = 'https://example-rates.com'
45+
let tenantExchangeRatesUrl: string
3946

4047
const assetMap: Record<string, Asset> = {}
4148
const walletAddressMap: Record<string, WalletAddress> = {}
@@ -84,6 +91,14 @@ describe('LocalPaymentService', (): void => {
8491
tenantId,
8592
assetId: assetMap['EUR'].id
8693
})
94+
95+
const createOptions: CreateOptions = {
96+
tenantId,
97+
setting: [exchangeRatesSetting()]
98+
}
99+
100+
const tenantSetting = createTenantSettings(deps, createOptions)
101+
tenantExchangeRatesUrl = (await tenantSetting).value
87102
})
88103

89104
afterEach(async (): Promise<void> => {
@@ -290,7 +305,7 @@ describe('LocalPaymentService', (): void => {
290305
let ratesScope
291306

292307
if (incomingAssetCode !== debitAssetCode) {
293-
ratesScope = mockRatesApi(exchangeRatesUrl, () => ({
308+
ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({
294309
[incomingAssetCode]: exchangeRate
295310
}))
296311
}
@@ -346,7 +361,7 @@ describe('LocalPaymentService', (): void => {
346361
let ratesScope
347362

348363
if (debitAssetCode !== incomingAssetCode) {
349-
ratesScope = mockRatesApi(exchangeRatesUrl, () => ({
364+
ratesScope = mockRatesApi(tenantExchangeRatesUrl, () => ({
350365
[incomingAssetCode]: exchangeRate
351366
}))
352367
}

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

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ async function getQuote(
6767
let exchangeRate: number
6868

6969
const convert = async (
70-
opts: RateConvertSourceOpts | RateConvertDestinationOpts
70+
opts: RateConvertSourceOpts | RateConvertDestinationOpts,
71+
tenantId?: string
7172
): Promise<ConvertResults | ConvertError> => {
7273
let convertResults: ConvertResults | ConvertError
7374
try {
7475
convertResults =
7576
'sourceAmount' in opts
76-
? await deps.ratesService.convertSource(opts)
77-
: await deps.ratesService.convertDestination(opts)
77+
? await deps.ratesService.convertSource(opts, tenantId)
78+
: await deps.ratesService.convertDestination(opts, tenantId)
7879
} catch (err) {
7980
deps.logger.error(
8081
{ opts, err },
@@ -93,17 +94,20 @@ async function getQuote(
9394

9495
if (debitAmount) {
9596
debitAmountValue = debitAmount.value
96-
const convertResults = await convert({
97-
sourceAmount: debitAmountValue,
98-
sourceAsset: {
99-
code: walletAddress.asset.code,
100-
scale: walletAddress.asset.scale
97+
const convertResults = await convert(
98+
{
99+
sourceAmount: debitAmountValue,
100+
sourceAsset: {
101+
code: walletAddress.asset.code,
102+
scale: walletAddress.asset.scale
103+
},
104+
destinationAsset: {
105+
code: receiver.assetCode,
106+
scale: receiver.assetScale
107+
}
101108
},
102-
destinationAsset: {
103-
code: receiver.assetCode,
104-
scale: receiver.assetScale
105-
}
106-
})
109+
options.walletAddress.tenantId
110+
)
107111
if (isConvertError(convertResults)) {
108112
throw new PaymentMethodHandlerError(
109113
'Received error during local quoting',
@@ -117,17 +121,20 @@ async function getQuote(
117121
exchangeRate = convertResults.scaledExchangeRate
118122
} else if (receiveAmount) {
119123
receiveAmountValue = receiveAmount.value
120-
const convertResults = await convert({
121-
destinationAmount: receiveAmountValue,
122-
sourceAsset: {
123-
code: walletAddress.asset.code,
124-
scale: walletAddress.asset.scale
124+
const convertResults = await convert(
125+
{
126+
destinationAmount: receiveAmountValue,
127+
sourceAsset: {
128+
code: walletAddress.asset.code,
129+
scale: walletAddress.asset.scale
130+
},
131+
destinationAsset: {
132+
code: receiveAmount.assetCode,
133+
scale: receiveAmount.assetScale
134+
}
125135
},
126-
destinationAsset: {
127-
code: receiveAmount.assetCode,
128-
scale: receiveAmount.assetScale
129-
}
130-
})
136+
options.walletAddress.tenantId
137+
)
131138
if (isConvertError(convertResults)) {
132139
throw new PaymentMethodHandlerError(
133140
'Received error during local quoting',
@@ -141,17 +148,20 @@ async function getQuote(
141148
exchangeRate = convertResults.scaledExchangeRate
142149
} else if (receiver.incomingAmount) {
143150
receiveAmountValue = receiver.incomingAmount.value
144-
const convertResults = await convert({
145-
destinationAmount: receiveAmountValue,
146-
sourceAsset: {
147-
code: walletAddress.asset.code,
148-
scale: walletAddress.asset.scale
151+
const convertResults = await convert(
152+
{
153+
destinationAmount: receiveAmountValue,
154+
sourceAsset: {
155+
code: walletAddress.asset.code,
156+
scale: walletAddress.asset.scale
157+
},
158+
destinationAsset: {
159+
code: receiver.incomingAmount.assetCode,
160+
scale: receiver.incomingAmount.assetScale
161+
}
149162
},
150-
destinationAsset: {
151-
code: receiver.incomingAmount.assetCode,
152-
scale: receiver.incomingAmount.assetScale
153-
}
154-
})
163+
options.walletAddress.tenantId
164+
)
155165
if (isConvertError(convertResults)) {
156166
throw new PaymentMethodHandlerError(
157167
'Received error during local quoting',

0 commit comments

Comments
 (0)