diff --git a/.changeset/few-vans-rule.md b/.changeset/few-vans-rule.md new file mode 100644 index 0000000000..58e20daa1c --- /dev/null +++ b/.changeset/few-vans-rule.md @@ -0,0 +1,5 @@ +--- +'@chainlink/proof-of-reserves-adapter': minor +--- + +Add multiReserves endpoint diff --git a/.changeset/large-ghosts-build.md b/.changeset/large-ghosts-build.md new file mode 100644 index 0000000000..5d2fc573da --- /dev/null +++ b/.changeset/large-ghosts-build.md @@ -0,0 +1,5 @@ +--- +'@chainlink/por-address-list-adapter': patch +--- + +Fix input convension diff --git a/packages/composites/proof-of-reserves/src/endpoint/index.ts b/packages/composites/proof-of-reserves/src/endpoint/index.ts index b7b25d2e1d..9c7b4252ca 100644 --- a/packages/composites/proof-of-reserves/src/endpoint/index.ts +++ b/packages/composites/proof-of-reserves/src/endpoint/index.ts @@ -1,5 +1,7 @@ -import * as reserves from './reserves' +import type { TInputParameters as SingleTInputParameters } from './reserves' +import type { TInputParameters as MultiTInputParameters } from './multiReserves' -export type TInputParameters = reserves.TInputParameters +export type TInputParameters = SingleTInputParameters | MultiTInputParameters export * as reserves from './reserves' +export * as multiReserves from './multiReserves' diff --git a/packages/composites/proof-of-reserves/src/endpoint/multiReserves.ts b/packages/composites/proof-of-reserves/src/endpoint/multiReserves.ts new file mode 100644 index 0000000000..c1c4798020 --- /dev/null +++ b/packages/composites/proof-of-reserves/src/endpoint/multiReserves.ts @@ -0,0 +1,77 @@ +import type { ExecuteWithConfig, InputParameters } from '@chainlink/ea-bootstrap' +import type { TInputParameters as SingleTInputParameters } from './reserves' +import { execute as singleExecute } from './reserves' +import { Validator } from '@chainlink/ea-bootstrap' +import { Config } from '../config' +import { AdapterError } from '@chainlink/ea-bootstrap' + +export const supportedEndpoints = ['multiReserves'] + +export type TInputParameters = { + input: SingleTInputParameters[] +} + +const inputParameters: InputParameters = { + input: { + required: true, + type: 'array', + description: 'An array of PoR request, each request is a request to the reserves endpoint', + }, +} + +export const execute: ExecuteWithConfig = async (input, context, config) => { + const providerDataRequestedUnixMs = Date.now() + + const validator = new Validator(input, inputParameters) + const jobRunID = validator.validated.id + + const results = await Promise.all( + validator.validated.data.input.map((input) => + singleExecute( + { + id: jobRunID, + data: input, + }, + context, + config, + ), + ), + ) + + const result = results + .map((result) => { + if (result.statusCode != 200 || !result.result) { + throw new AdapterError({ + ...result, + }) + } else { + return scale(BigInt(result.result.toString()), result.data.decimals?.toString()) + } + }) + .reduce((s, e) => s + e) + .toString() + + return { + jobRunID, + result: result, + statusCode: 200, + data: { + result: result, + statusCode: 200, + decimals: 18, + timestamps: { + providerDataRequestedUnixMs, + providerDataReceivedUnixMs: Date.now(), + }, + }, + } +} + +const scale = (value: bigint, decimal?: string) => { + if (decimal) { + // Scale to 18 decimals + return value * 10n ** (18n - BigInt(decimal)) + } else { + return value + } +} diff --git a/packages/composites/proof-of-reserves/src/utils/reduce.ts b/packages/composites/proof-of-reserves/src/utils/reduce.ts index 29e2fa95ef..f4dbc01c9b 100644 --- a/packages/composites/proof-of-reserves/src/utils/reduce.ts +++ b/packages/composites/proof-of-reserves/src/utils/reduce.ts @@ -16,6 +16,7 @@ const returnParsedUnits = (jobRunID: string, result: string, units: number) => { data: { result: convertedResult, statusCode: 200, + decimals: units, }, } } diff --git a/packages/composites/proof-of-reserves/test/integration/__snapshots__/adapter.test.ts.snap b/packages/composites/proof-of-reserves/test/integration/__snapshots__/adapter.test.ts.snap index 1867a2d018..b63b8f4364 100644 --- a/packages/composites/proof-of-reserves/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/composites/proof-of-reserves/test/integration/__snapshots__/adapter.test.ts.snap @@ -3,6 +3,7 @@ exports[`execute Bitcoin list protocol should return success 1`] = ` { "data": { + "decimals": 8, "result": "75100045155", "statusCode": 200, }, @@ -27,6 +28,7 @@ exports[`execute Ethereum list protocol should return success 1`] = ` exports[`execute Filecoin Gemini protocol should return success 1`] = ` { "data": { + "decimals": 0, "result": "127530375584000000000000", "statusCode": 200, }, @@ -38,6 +40,7 @@ exports[`execute Filecoin Gemini protocol should return success 1`] = ` exports[`execute Filecoin list protocol w/ miner format address should return success 1`] = ` { "data": { + "decimals": 0, "result": "127530375584000000000000", "statusCode": 200, }, @@ -45,3 +48,20 @@ exports[`execute Filecoin list protocol w/ miner format address should return su "statusCode": 200, } `; + +exports[`execute multiReserves endpoint should return success 1`] = ` +{ + "data": { + "decimals": 18, + "result": "751221097708354962165", + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 1641035471111, + "providerDataRequestedUnixMs": 1641035471111, + }, + }, + "jobRunID": "1", + "result": "751221097708354962165", + "statusCode": 200, +} +`; diff --git a/packages/composites/proof-of-reserves/test/integration/adapter.test.ts b/packages/composites/proof-of-reserves/test/integration/adapter.test.ts index 2df15471ca..7499ded655 100644 --- a/packages/composites/proof-of-reserves/test/integration/adapter.test.ts +++ b/packages/composites/proof-of-reserves/test/integration/adapter.test.ts @@ -26,6 +26,17 @@ describe('execute', () => { setupExternalAdapterTest(envVariables, context) + let spy: jest.SpyInstance + beforeAll(async () => { + const mockDate = new Date('2022-01-01T11:11:11.111Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + }) + + afterAll((done) => { + spy.mockRestore() + done() + }) + describe('Bitcoin list protocol', () => { const data: AdapterRequest = { id: '1', @@ -101,7 +112,7 @@ describe('execute', () => { .set('Accept', '*/*') .set('Content-Type', 'application/json') .expect('Content-Type', /json/) - // .expect(200) + .expect(200) expect(response.body).toMatchSnapshot() }) }) @@ -119,14 +130,63 @@ describe('execute', () => { } it('should return success', async () => { - //mockLotusSuccess() + mockLotusSuccess() + const response = await (context.req as SuperTest) + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + expect(response.body).toMatchSnapshot() + }) + }) + + describe('multiReserves endpoint', () => { + it('should return success', async () => { + const data: AdapterRequest = { + id: '1', + data: { + endpoint: 'multiReserves', + input: [ + { + protocol: 'list', + indexer: 'por_indexer', + addresses: [ + { + address: '39e7mxbeNmRRnjfy1qkphv1TiMcztZ8VuE', + chainId: 'mainnet', + network: 'bitcoin', + }, + { + address: '35ULMyVnFoYaPaMxwHTRmaGdABpAThM4QR', + chainId: 'mainnet', + network: 'bitcoin', + }, + ], + }, + { + indexer: 'eth_balance', + protocol: 'list', + addresses: [ + '0x8288C280F35FB8809305906C79BD075962079DD8', + '0x81910675DbaF69deE0fD77570BFD07f8E436386A', + ], + confirmations: 5, + }, + ], + }, + } + mockPoRindexerSuccess() + mockEthBalanceSuccess() + const response = await (context.req as SuperTest) .post('/') .send(data) .set('Accept', '*/*') .set('Content-Type', 'application/json') .expect('Content-Type', /json/) - // .expect(200) + .expect(200) expect(response.body).toMatchSnapshot() }) }) diff --git a/packages/sources/por-address-list/src/transport/multichainAddress.ts b/packages/sources/por-address-list/src/transport/multichainAddress.ts index d50ccff6fb..c893645514 100644 --- a/packages/sources/por-address-list/src/transport/multichainAddress.ts +++ b/packages/sources/por-address-list/src/transport/multichainAddress.ts @@ -71,7 +71,7 @@ export class AddressTransport extends SubscriptionTransport