Skip to content

Commit 9218c86

Browse files
authored
Implement handleRequest of XrplTransport (#3866)
1 parent f62803d commit 9218c86

File tree

4 files changed

+190
-8
lines changed

4 files changed

+190
-8
lines changed

.changeset/strong-kings-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/token-balance-adapter': patch
3+
---
4+
5+
Adding not-yet used code for future new endpoint

packages/sources/token-balance/src/endpoint/xrpl.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ import { xrplTransport } from '../transport/xrpl'
55

66
export const inputParameters = new InputParameters(
77
{
8+
tokenIssuerAddress: {
9+
required: true,
10+
type: 'string',
11+
description: 'Identifies the token, e.g., TBILL, to fetch the balance of',
12+
},
13+
priceOracleAddress: {
14+
required: true,
15+
type: 'string',
16+
description: 'Address of the price oracle contract to use to convert the above token to USD',
17+
},
18+
priceOracleNetwork: {
19+
required: true,
20+
type: 'string',
21+
description: 'EVM network on which to query the price oracle (ethereum, arbitrum, etc.)',
22+
},
823
addresses: {
924
required: true,
1025
type: {
@@ -20,6 +35,9 @@ export const inputParameters = new InputParameters(
2035
},
2136
[
2237
{
38+
tokenIssuerAddress: 'rJNE2NNz83GJYtWVLwMvchDWEon3huWnFn',
39+
priceOracleAddress: '0xCe9a6626Eb99eaeA829D7fA613d5D0A2eaE45F40',
40+
priceOracleNetwork: 'ethereum',
2341
addresses: [
2442
{
2543
address: 'rGSA6YCGzywj2hsPA8DArSsLr1DMTBi2LH',

packages/sources/token-balance/src/transport/xrpl.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { Requester } from '@chainlink/external-adapter-framework/util/requester'
88
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
99
import Decimal from 'decimal.js'
1010
import { BaseEndpointTypes, inputParameters } from '../endpoint/xrpl'
11+
import { getTokenPrice } from './priceFeed'
12+
import { SharePriceType } from './utils'
1113

1214
const logger = makeLogger('Token Balance - XRPL')
1315

@@ -84,12 +86,19 @@ export class XrplTransport extends SubscriptionTransport<BaseEndpointTypes> {
8486
}
8587

8688
async _handleRequest(
87-
_param: RequestParams,
89+
param: RequestParams,
8890
): Promise<AdapterResponse<BaseEndpointTypes['Response']>> {
8991
const providerDataRequestedUnixMs = Date.now()
92+
const [tokenPriceInUsd, tokenBalance]: [SharePriceType, Decimal] = await Promise.all([
93+
getTokenPrice(param),
94+
this.getTotalTokenBalance(param),
95+
])
9096

91-
// TODO: Implement the logic
92-
const result = '0'
97+
const tokenBalanceInUsd = tokenBalance
98+
.times(new Decimal(tokenPriceInUsd.value.toString()))
99+
.times(10 ** (RESULT_DECIMALS - tokenPriceInUsd.decimal))
100+
101+
const result = tokenBalanceInUsd.toFixed(0)
93102

94103
return {
95104
data: {

packages/sources/token-balance/test/unit/xrpl.test.ts

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,40 @@ import { TransportDependencies } from '@chainlink/external-adapter-framework/tra
44
import { deferredPromise, LoggerFactoryProvider } from '@chainlink/external-adapter-framework/util'
55
import { makeStub } from '@chainlink/external-adapter-framework/util/testing-utils'
66
import Decimal from 'decimal.js'
7+
import { ethers } from 'ethers'
78
import { BaseEndpointTypes, inputParameters } from '../../src/endpoint/xrpl'
89
import { XrplTransport } from '../../src/transport/xrpl'
910

11+
const originalEnv = { ...process.env }
12+
13+
const restoreEnv = () => {
14+
for (const key of Object.keys(process.env)) {
15+
if (key in originalEnv) {
16+
process.env[key] = originalEnv[key]
17+
} else {
18+
delete process.env[key]
19+
}
20+
}
21+
}
22+
23+
const ethersNewContract = jest.fn()
24+
const ethersNewJsonRpcProvider = jest.fn()
25+
26+
const makeEthers = () => {
27+
return {
28+
JsonRpcProvider: function (...args: [string, number]) {
29+
return ethersNewJsonRpcProvider(...args)
30+
},
31+
Contract: function (...args: [string, unknown, ethers.JsonRpcProvider]) {
32+
return ethersNewContract(...args)
33+
},
34+
}
35+
}
36+
37+
jest.mock('ethers', () => ({
38+
ethers: makeEthers(),
39+
}))
40+
1041
const log = jest.fn()
1142
const logger = {
1243
fatal: log,
@@ -127,6 +158,7 @@ describe('XrplTransport', () => {
127158
}
128159

129160
beforeEach(async () => {
161+
restoreEnv()
130162
jest.resetAllMocks()
131163
jest.useFakeTimers()
132164

@@ -172,14 +204,36 @@ describe('XrplTransport', () => {
172204

173205
describe('handleRequest', () => {
174206
it('should cache response', async () => {
207+
const priceOracleAddress = '0x123'
208+
const priceOracleNetwork = 'arbitrum'
209+
const arbitrumRpcUrl = 'https://arb.rpc.url'
210+
const arbitrumChainId = 42161
211+
const tokenPrice = 200_000_000n
212+
const tokenDecimals = 8
175213
const address = 'r101'
214+
const tokenIssuerAddress = 'r456'
215+
const balance = 123
216+
217+
process.env.ARBITRUM_RPC_URL = arbitrumRpcUrl
218+
process.env.ARBITRUM_RPC_CHAIN_ID = arbitrumChainId.toString()
219+
220+
const contract = makeStub('contract', {
221+
decimals: jest.fn().mockResolvedValue(tokenDecimals),
222+
latestAnswer: jest.fn().mockResolvedValue(tokenPrice),
223+
})
224+
ethersNewContract.mockReturnValue(contract)
225+
226+
mockLineBalances([balance.toString()])
176227

177228
const param = makeStub('param', {
229+
priceOracleAddress,
230+
priceOracleNetwork,
231+
tokenIssuerAddress,
178232
addresses: [{ address }],
179233
})
180234
await transport.handleRequest(context, param)
181235

182-
const expectedResult = '0'
236+
const expectedResult = (246 * 10 ** 18).toString()
183237
const expectedResponse = {
184238
statusCode: 200,
185239
result: expectedResult,
@@ -201,19 +255,48 @@ describe('XrplTransport', () => {
201255
},
202256
])
203257
expect(responseCache.write).toBeCalledTimes(1)
258+
259+
expect(log).toBeCalledWith(expect.stringContaining('Generated HTTP request queue key:'))
260+
expect(log).toBeCalledTimes(1)
261+
log.mockClear()
204262
})
205263
})
206264

207265
describe('_handleRequest', () => {
208-
it('should return a response', async () => {
209-
const address = 'r101'
266+
it('should add balances and multiply by token price', async () => {
267+
const priceOracleAddress = '0x123'
268+
const priceOracleNetwork = 'arbitrum'
269+
const arbitrumRpcUrl = 'https://arb.rpc.url'
270+
const arbitrumChainId = 42161
271+
const tokenPrice = 200_000_000n
272+
const tokenDecimals = 8
273+
const address1 = 'r101'
274+
const address2 = 'r102'
275+
const tokenIssuerAddress = 'r456'
276+
const balance1 = 100
277+
const balance2 = 200
278+
279+
process.env.ARBITRUM_RPC_URL = arbitrumRpcUrl
280+
process.env.ARBITRUM_RPC_CHAIN_ID = arbitrumChainId.toString()
281+
282+
const contract = makeStub('contract', {
283+
decimals: jest.fn().mockResolvedValue(tokenDecimals),
284+
latestAnswer: jest.fn().mockResolvedValue(tokenPrice),
285+
})
286+
ethersNewContract.mockReturnValue(contract)
287+
288+
mockLineBalances([balance1.toString()])
289+
mockLineBalances([balance2.toString()])
210290

211291
const param = makeStub('param', {
212-
addresses: [{ address }],
292+
priceOracleAddress,
293+
priceOracleNetwork,
294+
tokenIssuerAddress,
295+
addresses: [{ address: address1 }, { address: address2 }],
213296
})
214297
const response = await transport._handleRequest(param)
215298

216-
const expectedResult = '0'
299+
const expectedResult = (600 * 10 ** 18).toString()
217300
expect(response).toEqual({
218301
statusCode: 200,
219302
result: expectedResult,
@@ -227,6 +310,73 @@ describe('XrplTransport', () => {
227310
providerIndicatedTimeUnixMs: undefined,
228311
},
229312
})
313+
314+
expect(log).toHaveBeenNthCalledWith(
315+
1,
316+
expect.stringContaining('Generated HTTP request queue key:'),
317+
)
318+
expect(log).toHaveBeenNthCalledWith(
319+
2,
320+
expect.stringContaining('Generated HTTP request queue key:'),
321+
)
322+
expect(log).toBeCalledTimes(2)
323+
log.mockClear()
324+
})
325+
326+
it('should record received timestamp separate from requested timestamp', async () => {
327+
const priceOracleAddress = '0x123'
328+
const priceOracleNetwork = 'arbitrum'
329+
const arbitrumRpcUrl = 'https://arb.rpc.url'
330+
const arbitrumChainId = 42161
331+
const tokenPrice = 200_000_000n
332+
const tokenDecimals = 8
333+
const address = 'r101'
334+
const tokenIssuerAddress = 'r456'
335+
const balance = 100
336+
337+
process.env.ARBITRUM_RPC_URL = arbitrumRpcUrl
338+
process.env.ARBITRUM_RPC_CHAIN_ID = arbitrumChainId.toString()
339+
340+
const contract = makeStub('contract', {
341+
decimals: jest.fn().mockResolvedValue(tokenDecimals),
342+
latestAnswer: jest.fn().mockResolvedValue(tokenPrice),
343+
})
344+
ethersNewContract.mockReturnValue(contract)
345+
346+
const [balancePromise, resolveBalance] = deferredPromise<string[]>()
347+
mockLineBalances(balancePromise)
348+
349+
const param = makeStub('param', {
350+
priceOracleAddress,
351+
priceOracleNetwork,
352+
tokenIssuerAddress,
353+
addresses: [{ address }],
354+
})
355+
356+
const requestTimestamp = Date.now()
357+
const responsePromise = transport._handleRequest(param)
358+
jest.advanceTimersByTime(1234)
359+
const responseTimestamp = Date.now()
360+
expect(responseTimestamp).toBeGreaterThan(requestTimestamp)
361+
362+
resolveBalance([balance.toString()])
363+
364+
const expectedResult = (200 * 10 ** 18).toString()
365+
expect(await responsePromise).toEqual({
366+
statusCode: 200,
367+
result: expectedResult,
368+
data: {
369+
decimals: 18,
370+
result: expectedResult,
371+
},
372+
timestamps: {
373+
providerDataRequestedUnixMs: requestTimestamp,
374+
providerDataReceivedUnixMs: responseTimestamp,
375+
providerIndicatedTimeUnixMs: undefined,
376+
},
377+
})
378+
379+
log.mockClear()
230380
})
231381
})
232382

0 commit comments

Comments
 (0)