-
Notifications
You must be signed in to change notification settings - Fork 325
Opdata 3775 add functionality query decimals #4107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2f3d47a
326db4c
032b30e
83b5554
498693d
5cd2836
a3eece4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@chainlink/view-function-multi-chain-adapter': minor | ||
--- | ||
|
||
Add functionality to query decimals from contract |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,10 @@ import { | |
} from '@chainlink/external-adapter-framework/transports' | ||
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription' | ||
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util' | ||
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error' | ||
import { | ||
AdapterError, | ||
AdapterInputError, | ||
} from '@chainlink/external-adapter-framework/validation/error' | ||
import { ethers } from 'ethers' | ||
|
||
const logger = makeLogger('View Function Multi Chain') | ||
|
@@ -16,6 +19,7 @@ interface RequestParams { | |
inputParams?: Array<string> | ||
network: string | ||
resultField?: string | ||
data?: Record<string, RequestParams> | ||
} | ||
|
||
export type RawOnchainResponse = { | ||
|
@@ -80,7 +84,36 @@ export class MultiChainFunctionTransport< | |
} | ||
|
||
async _handleRequest(param: RequestParams): Promise<AdapterResponse<T['Response']>> { | ||
const { address, signature, inputParams, network } = param | ||
const { address, signature, inputParams, network, data } = param | ||
|
||
const mainResult = await this._executeFunction({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can run these two in parallel |
||
address, | ||
signature, | ||
inputParams, | ||
network, | ||
resultField: param.resultField, | ||
}) | ||
|
||
const nestedResults = await this._processNestedDataRequest(data, address, network) | ||
|
||
const combinedData = { result: mainResult.result, ...nestedResults } | ||
|
||
return { | ||
data: combinedData, | ||
statusCode: 200, | ||
result: mainResult.result, | ||
timestamps: mainResult.timestamps, | ||
} | ||
} | ||
|
||
private async _executeFunction(params: { | ||
address: string | ||
signature: string | ||
inputParams?: Array<string> | ||
network: string | ||
resultField?: string | ||
}) { | ||
const { address, signature, inputParams, network, resultField } = params | ||
|
||
const networkName = network.toUpperCase() | ||
const networkEnvName = `${networkName}_RPC_URL` | ||
|
@@ -102,30 +135,66 @@ export class MultiChainFunctionTransport< | |
|
||
const iface = new ethers.Interface([signature]) | ||
const fnName = iface.getFunctionName(signature) | ||
const encoded = iface.encodeFunctionData(fnName, [...(inputParams || [])]) | ||
const encoded = iface.encodeFunctionData(fnName, inputParams || []) | ||
|
||
const providerDataRequestedUnixMs = Date.now() | ||
const encodedResult = await this.providers[networkName].call({ | ||
to: address, | ||
data: encoded, | ||
}) | ||
|
||
let encodedResult | ||
try { | ||
encodedResult = await this.providers[networkName].call({ to: address, data: encoded }) | ||
} catch (err) { | ||
throw new AdapterError({ | ||
statusCode: 500, | ||
message: `RPC call failed for ${fnName} on ${networkName}: ${err}`, | ||
}) | ||
} | ||
|
||
const timestamps = { | ||
providerDataRequestedUnixMs, | ||
providerDataReceivedUnixMs: Date.now(), | ||
providerIndicatedTimeUnixMs: undefined, | ||
} | ||
|
||
const result = this.hexResultPostProcessor({ iface, fnName, encodedResult }, param.resultField) | ||
const result = this.hexResultPostProcessor({ iface, fnName, encodedResult }, resultField) | ||
|
||
return { | ||
data: { | ||
result, | ||
}, | ||
statusCode: 200, | ||
result, | ||
timestamps, | ||
return { result, timestamps } | ||
} | ||
|
||
private async _processNestedDataRequest( | ||
data: Record<string, unknown> | undefined, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be typed as |
||
parentAddress: string, | ||
parentNetwork: string, | ||
): Promise<Record<string, any>> { | ||
if (!data || typeof data !== 'object') return {} | ||
|
||
const results: Record<string, any> = {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we not use any here? |
||
|
||
for (const [key, subReq] of Object.entries(data)) { | ||
try { | ||
const req = subReq as RequestParams | ||
|
||
if (!req.signature) { | ||
logger.warn(`Skipping nested key "${key}" — no signature provided.`) | ||
continue | ||
} | ||
|
||
const nestedParam = { | ||
address: req.address || parentAddress, | ||
network: req.network || parentNetwork, | ||
signature: req.signature, | ||
inputParams: req.inputParams, | ||
resultField: req.resultField, | ||
} | ||
|
||
const subRes = await this._executeFunction(nestedParam) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we run these in parallel? |
||
results[key] = subRes.result | ||
} catch (err) { | ||
logger.warn(`Nested function "${key}" failed: ${err}`) | ||
results[key] = null | ||
} | ||
} | ||
|
||
return results | ||
} | ||
|
||
getSubscriptionTtlFromConfig(adapterSettings: T['Settings']): number { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,57 @@ describe('execute', () => { | |
expect(response.json()).toMatchSnapshot() | ||
}) | ||
|
||
it('should return success with additional data requests ', async () => { | ||
const data = { | ||
contract: '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c', | ||
function: 'function latestAnswer() external view returns (int256)', | ||
network: 'ethereum_mainnet', | ||
data: { | ||
decimals: { | ||
signature: 'function decimals() view returns (uint8)', | ||
}, | ||
}, | ||
} | ||
mockETHMainnetContractCallResponseSuccess() | ||
const response = await testAdapter.request(data) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.json()).toMatchSnapshot() | ||
}) | ||
|
||
it('should return success with additional data requests ', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicated test case? |
||
const data = { | ||
contract: '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c', | ||
function: 'function latestAnswer() external view returns (int256)', | ||
network: 'ethereum_mainnet', | ||
data: { | ||
decimals: { | ||
signature: 'function decimals() view returns (uint8)', | ||
}, | ||
}, | ||
} | ||
mockETHMainnetContractCallResponseSuccess() | ||
const response = await testAdapter.request(data) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.json()).toMatchSnapshot() | ||
}) | ||
|
||
it('should skip additional data requests in case of missing signature', async () => { | ||
const data = { | ||
contract: '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c', | ||
function: 'function latestAnswer() external view returns (int256)', | ||
network: 'ethereum_mainnet', | ||
data: { | ||
decimals: { | ||
signature: '', | ||
}, | ||
}, | ||
} | ||
mockETHMainnetContractCallResponseSuccess() | ||
const response = await testAdapter.request(data) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.json()).toMatchSnapshot() | ||
}) | ||
|
||
it('should return success for different network', async () => { | ||
const data = { | ||
contract: '0x779877a7b0d9e8603169ddbd7836e478b4624789', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data seems to be very generic name, maybe
additionalRequests
?Also it is possible to type this correctly without using
any
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggested
data
because in the response these fields would also appear indata
.additionalRequests
is also fine.