Skip to content

Commit

Permalink
Feat/DF-20745 token balance EA (#3570)
Browse files Browse the repository at this point in the history
* DF-20745 token-balance EA initial commit

* resolve remaining issues, add tests

* add changeset

* fix to fw v 1.7.1

* small fixes

* some review fixes

* timestamp review fix

* add resultDecimals field

* scale decimals with BigInt math

* refactor decimals logic into separate function

* throw error if decimals can't be found

* update known-issues doc & readme

* resultDecimals to decimals

* move time

* fix comment

---------

Co-authored-by: Michael Xiao <michael.xiao@smartcontract.com>
  • Loading branch information
mmcallister-cll and mxiao-cll authored Nov 19, 2024
1 parent fa47637 commit 1cd8872
Show file tree
Hide file tree
Showing 24 changed files with 1,014 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-rocks-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/token-balance-adapter': major
---

Initial release of token-balance EA
45 changes: 45 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
80 changes: 80 additions & 0 deletions packages/sources/token-balance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# TOKEN_BALANCE

![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/token-balance/package.json) ![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet)

This document was generated automatically. Please see [README Generator](../../scripts#readme-generator) for more info.

## Usage Notes

### evm endpoint network vs chainId input param

At least one of [`chainId` and `network`] must be present when using the `evm` endpoint.

The result is scaled to 18 decimals.

Additional env vars in the form `${NETWORK}_RPC_URL` and `${NETWORK}_RPC_CHAIN_ID` are required for each supported network.

## Environment Variables

| Required? | Name | Description | Type | Options | Default |
| :-------: | :-------------------: | :---------------------------------------------------------------------------------------: | :----: | :-----: | :-----: |
| | BACKGROUND_EXECUTE_MS | The amount of time the background execute should sleep before performing the next request | number | | `10000` |

---

## Data Provider Rate Limits

There are no rate limits for this adapter.

---

## Input Parameters

| Required? | Name | Description | Type | Options | Default |
| :-------: | :------: | :-----------------: | :----: | :------------------------------------------: | :-----: |
| | endpoint | The endpoint to use | string | [erc20](#evm-endpoint), [evm](#evm-endpoint) | `evm` |

## Evm Endpoint

Supported names for this endpoint are: `erc20`, `evm`.

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :--------------------------: | :-----: | :---------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :-----: | :-------------------------------------------------------------------: | :--------: | :------------: |
|| addresses | | List of addresses to read | object[] | | | | |
| | addresses.network | `chain` | Network of the contract | string | | | | |
| | addresses.chainId | | Chain ID of the network | string | | | | |
|| addresses.contractAddress | | Address of token contract | string | | | | |
|| addresses.wallets | | Array of wallets to sum balances | string[] | | | | |
| | addresses.balanceOfSignature | | Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts) | string | | `function balanceOf(address account) external view returns (uint256)` | | |
| | addresses.decimalsSignature | | Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts) | string | | `function decimals() external pure returns (uint8)` | | |

### Example

Request:

```json
{
"data": {
"endpoint": "evm",
"addresses": [
{
"network": "ethereum",
"chainId": "1",
"contractAddress": "0x514910771af9ca656af840dff83e8264ecf986ca",
"wallets": [
"0xBc10f2E862ED4502144c7d632a3459F49DFCDB5e",
"0xF977814e90dA44bFA03b6295A0616a897441aceC"
],
"balanceOfSignature": "function balanceOf(address account) external view returns (uint256)",
"decimalsSignature": "function decimals() external pure returns (uint8)"
}
]
}
}
```

---

MIT License
9 changes: 9 additions & 0 deletions packages/sources/token-balance/docs/known-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Usage Notes

### evm endpoint network vs chainId input param

At least one of [`chainId` and `network`] must be present when using the `evm` endpoint.

The result is scaled to 18 decimals.

Additional env vars in the form `${NETWORK}_RPC_URL` and `${NETWORK}_RPC_CHAIN_ID` are required for each supported network.
41 changes: 41 additions & 0 deletions packages/sources/token-balance/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@chainlink/token-balance-adapter",
"version": "0.0.0",
"description": "Chainlink token-balance adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"token-balance"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"url": "https://github.com/smartcontractkit/external-adapters-js",
"type": "git"
},
"license": "MIT",
"scripts": {
"clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo",
"prepack": "yarn build",
"build": "tsc -b",
"server": "node -e 'require(\"./index.js\").server()'",
"server:dist": "node -e 'require(\"./dist/index.js\").server()'",
"start": "yarn server:dist"
},
"devDependencies": {
"@types/jest": "27.5.2",
"@types/node": "16.18.115",
"nock": "13.5.4",
"typescript": "5.5.4"
},
"dependencies": {
"@chainlink/external-adapter-framework": "1.7.1",
"ethers": "^6.13.2",
"tslib": "2.4.1"
}
}
10 changes: 10 additions & 0 deletions packages/sources/token-balance/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
BACKGROUND_EXECUTE_MS: {
description:
'The amount of time the background execute should sleep before performing the next request',
type: 'number',
default: 10_000,
},
})
113 changes: 113 additions & 0 deletions packages/sources/token-balance/src/endpoint/evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { erc20TokenBalanceTransport } from '../transport/evm'
import { AdapterRequest } from '@chainlink/external-adapter-framework/util'
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'

export const inputParameters = new InputParameters(
{
addresses: {
required: true,
type: {
network: {
aliases: ['chain'],
required: false,
type: 'string',
description: 'Network of the contract',
},
chainId: {
required: false,
type: 'string',
description: 'Chain ID of the network',
},
contractAddress: {
required: true,
type: 'string',
description: 'Address of token contract',
},
wallets: {
required: true,
type: 'string',
array: true,
description: 'Array of wallets to sum balances',
},
balanceOfSignature: {
required: false,
type: 'string',
default: 'function balanceOf(address account) external view returns (uint256)',
description:
'Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts)',
},
decimalsSignature: {
required: false,
type: 'string',
default: 'function decimals() external pure returns (uint8)',
description:
'Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts)',
},
},
array: true,
description: 'List of addresses to read',
},
},
[
{
addresses: [
{
network: 'ethereum',
chainId: '1',
contractAddress: '0x514910771af9ca656af840dff83e8264ecf986ca',
wallets: [
'0xBc10f2E862ED4502144c7d632a3459F49DFCDB5e',
'0xF977814e90dA44bFA03b6295A0616a897441aceC',
],
balanceOfSignature: 'function balanceOf(address account) external view returns (uint256)',
decimalsSignature: 'function decimals() external pure returns (uint8)',
},
],
},
],
)

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: {
Result: string
Data: {
result: string
decimals: number
wallets: {
network: string
contractAddress: string
walletAddress: string
balance: string
decimals: number
}[]
}
}
Settings: typeof config.settings
}

export const endpoint = new AdapterEndpoint({
name: 'evm',
aliases: ['erc20'],
transport: erc20TokenBalanceTransport,
inputParameters,
customInputValidation: (
req: AdapterRequest<typeof inputParameters.validated>,
): AdapterInputError | undefined => {
const { addresses } = req.requestContext.data

// ensure each address has either chainId or network specified
for (const address of addresses) {
if (!address.chainId && !address.network) {
throw new AdapterInputError({
statusCode: 400,
message: "One or more addresses is missing one of ['chainId', 'network'].",
})
}
}
return
},
})
1 change: 1 addition & 0 deletions packages/sources/token-balance/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as evm } from './evm'
13 changes: 13 additions & 0 deletions packages/sources/token-balance/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
import { config } from './config'
import { evm } from './endpoint'

export const adapter = new Adapter({
defaultEndpoint: evm.name,
name: 'TOKEN_BALANCE',
config,
endpoints: [evm],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
Loading

0 comments on commit 1cd8872

Please sign in to comment.