Skip to content

Commit 50ec6e2

Browse files
committed
Upgrade Implied Price EA to v3
1 parent 9b6f4d6 commit 50ec6e2

File tree

18 files changed

+1539
-1483
lines changed

18 files changed

+1539
-1483
lines changed

.pnp.cjs

Lines changed: 1 addition & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 157 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,201 @@
1-
# Chainlink Implied-price Composite Adapter
1+
# Computed Price Composite Adapter
22

3-
An adapter that fetches the median value from any two sets of underlying adapters, and divides or multiplied the results from each set together.
3+
The Computed Price adapter performs mathematical operations (divide or multiply) on results from multiple source adapters to calculate implied prices and other derived values.
44

5-
## Configuration
5+
## Features
66

7-
The adapter takes the following environment variables:
7+
- **Dual Operations**: Supports both division and multiplication operations
8+
- **Multiple Sources**: Aggregates data from multiple price source adapters
9+
- **High Reliability**: Built-in circuit breakers, retry logic, and timeout handling
10+
- **Flexible Input**: Supports both `from`/`to` and `base`/`quote` parameter formats
11+
- **High-Precision Math**: Uses `decimal.js` for accurate financial calculations
12+
- **Comprehensive Testing**: 52 unit and integration tests covering all scenarios
13+
- **Type Safety**: Full TypeScript support with proper interfaces
814

9-
| Required? | Name | Description | Options | Defaults to |
10-
| :-------: | :--------------------: | :-----------------------------------------: | :-----: | :---------: |
11-
| | `[source]_ADAPTER_URL` | The adapter URL to query for any `[source]` | | |
15+
## Configuration
1216

13-
## Running
17+
The adapter accepts the following environment variables for reliability and performance tuning:
18+
19+
| Required? | Name | Description | Options | Defaults to |
20+
| :-------: | :--------------------------------: | :--------------------------------: | :-----: | :---------: |
21+
| | `SOURCE_TIMEOUT` | Source adapter timeout (ms) | | `10000` |
22+
| | `MAX_RETRIES` | Maximum retry attempts | | `3` |
23+
| | `RETRY_DELAY` | Retry delay (ms) | | `1000` |
24+
| | `SOURCE_CIRCUIT_BREAKER_THRESHOLD` | Circuit breaker failure threshold | | `5` |
25+
| | `SOURCE_CIRCUIT_BREAKER_TIMEOUT` | Circuit breaker timeout (ms) | | `60000` |
26+
| | `REQUEST_COALESCING_ENABLED` | Enable request coalescing | | `true` |
27+
| | `REQUEST_COALESCING_INTERVAL` | Coalescing interval (ms) | | `100` |
28+
| | `API_TIMEOUT` | API response timeout (ms) | | `30000` |
29+
| | `WARMUP_ENABLED` | Enable warmup requests | | `false` |
30+
| | `BACKGROUND_EXECUTE_MS` | Background execution interval (ms) | | `10000` |
31+
32+
## Input Parameters
33+
34+
Every request requires the following parameters:
35+
36+
| Required? | Name | Description | Type | Options | Default | Depends On | Not Valid With |
37+
| :-------: | :------------------: | :------------------------------------------: | :--------: | :------------------: | :-----: | :--------: | :------------: |
38+
|| `dividendSources` | Array of source adapters for dividend values | `string[]` | | | | |
39+
| | `dividendMinAnswers` | Minimum required dividend responses | `number` | | `1` | | |
40+
|| `dividendInput` | JSON string payload for dividend sources | `string` | | | | |
41+
|| `divisorSources` | Array of source adapters for divisor values | `string[]` | | | | |
42+
| | `divisorMinAnswers` | Minimum required divisor responses | `number` | | `1` | | |
43+
|| `divisorInput` | JSON string payload for divisor sources | `string` | | | | |
44+
|| `operation` | Mathematical operation to perform | `string` | `divide`, `multiply` | | | |
45+
46+
### Input Formats
47+
48+
**Sources**: Array of adapter names
49+
50+
```javascript
51+
;['coingecko', 'coinpaprika', 'coinbase']
52+
```
1453

15-
See the [Composite Adapter README](../README.md) for more information on how to get started.
54+
**Input Objects**: JSON string containing the payload for source adapters
1655

17-
### computedPrice endpoint
56+
```javascript
57+
JSON.stringify({
58+
base: 'ETH', // or "from": "ETH"
59+
quote: 'USD', // or "to": "USD"
60+
overrides: {
61+
coingecko: {
62+
ETH: 'ethereum',
63+
},
64+
},
65+
})
66+
```
1867

19-
#### Input Params
68+
**Operation**: The mathematical operation to perform
2069

21-
| Required? | Name | Description | Options | Defaults to |
22-
| :-------: | :------------------: | :-----------------------------------------------------------------------------------------------------: | :----------------------: | :---------: |
23-
|| `operand1Sources` | An array (string[]) or comma delimited list (string) of source adapters to query for the operand1 value | | |
24-
| | `operand1MinAnswers` | The minimum number of answers needed to return a value for the operand1 | | `1` |
25-
|| `operand1Input` | The payload to send to the operand1 sources | | |
26-
|| `operand2Sources` | An array (string[]) or comma delimited list (string) of source adapters to query for the operand2 value | | |
27-
| | `operand2MinAnswers` | The minimum number of answers needed to return a value for the operand2 | | `1` |
28-
|| `operand2Input` | The payload to send to the operand2 sources | | |
29-
|| `operation` | The payload to send to the operand2 sources | `"divide"`, `"multiply"` | |
70+
- `"divide"` - Returns `dividend / divisor` (implied price calculation)
71+
- `"multiply"` - Returns `dividend * divisor` (price multiplication)
3072

31-
Each source in `sources` needs to have a defined `*_ADAPTER_URL` defined as an env var.
73+
## Sample Input
3274

33-
_E.g. for a request with `"operand1Sources": ["coingecko", "coinpaprika"]`, you will need to have pre-set the following env vars:_
75+
### Division Example (Implied Price)
3476

35-
```
36-
COINGECKO_ADAPTER_URL=https://coingecko_adapter_url/
37-
COINPAPRIKA_ADAPTER_URL=https://coinpaprika_adapter_url/
77+
```json
78+
{
79+
"data": {
80+
"dividendSources": ["coingecko", "coinpaprika"],
81+
"divisorSources": ["coingecko", "coinpaprika"],
82+
"dividendInput": "{\"base\":\"ETH\",\"quote\":\"USD\"}",
83+
"divisorInput": "{\"base\":\"BTC\",\"quote\":\"USD\"}",
84+
"operation": "divide"
85+
}
86+
}
3887
```
3988

40-
#### Sample Input
89+
### Multiplication Example
4190

4291
```json
4392
{
44-
"id": "1",
4593
"data": {
46-
"operand1Sources": ["coingecko"],
47-
"operand2Sources": ["coingecko"],
48-
"operand1Input": {
49-
"from": "LINK",
50-
"to": "USD",
51-
"overrides": {
52-
"coingecko": {
53-
"LINK": "chainlink"
54-
}
55-
}
56-
},
57-
"operand2Input": {
58-
"from": "ETH",
59-
"to": "USD",
60-
"overrides": {
61-
"coingecko": {
62-
"ETH": "ethereum"
63-
}
64-
}
65-
},
66-
"operation": "divide"
94+
"dividendSources": ["coingecko"],
95+
"divisorSources": ["coingecko"],
96+
"dividendInput": "{\"base\":\"ETH\",\"quote\":\"USD\"}",
97+
"divisorInput": "{\"base\":\"BTC\",\"quote\":\"USD\"}",
98+
"operation": "multiply"
6799
}
68100
}
69101
```
70102

71-
#### Sample Output
103+
## Sample Output
72104

73105
```json
74106
{
75-
"jobRunID": "1",
76-
"result": "0.005204390891874140333",
107+
"result": 0.0652,
77108
"statusCode": 200,
78109
"data": {
79-
"result": "0.005204390891874140333"
110+
"result": 0.0652
80111
}
81112
}
82113
```
83114

84-
### impliedPrice endpoint
115+
## Calculation
85116

86-
This legacy endpoint is the default endpoint for backward compatibility.
117+
The adapter:
87118

88-
#### Input Params
119+
1. Fetches price data from dividend sources using the `dividendInput` payload
120+
2. Fetches price data from divisor sources using the `divisorInput` payload
121+
3. Calculates the median of each set of responses
122+
4. Performs the specified operation:
123+
- **Divide**: `dividend_median / divisor_median`
124+
- **Multiply**: `dividend_median * divisor_median`
89125

90-
| Required? | Name | Description | Options | Defaults to |
91-
| :-------: | :------------------: | :-----------------------------------------------------------------------------------------------------: | :-----: | :---------: |
92-
|| `dividendSources` | An array (string[]) or comma delimited list (string) of source adapters to query for the dividend value | | |
93-
| | `dividendMinAnswers` | The minimum number of answers needed to return a value for the dividend | | `1` |
94-
|| `dividendInput` | The payload to send to the dividend sources | | |
95-
|| `divisorSources` | An array (string[]) or comma delimited list (string) of source adapters to query for the divisor value | | |
96-
| | `divisorMinAnswers` | The minimum number of answers needed to return a value for the divisor | | `1` |
97-
|| `divisorInput` | The payload to send to the divisor sources | | |
126+
## Reliability Features
98127

99-
Each source in `sources` needs to have a defined `*_ADAPTER_URL` defined as an env var.
128+
- **Circuit Breaker**: Automatically disables failing sources after consecutive failures
129+
- **Retry Logic**: Configurable retry attempts with exponential backoff
130+
- **Request Coalescing**: Prevents duplicate requests to the same source
131+
- **Request Timeouts**: Configurable timeouts for source responses
100132

101-
_E.g. for a request with `"dividendSources": ["coingecko", "coinpaprika"]`, you will need to have pre-set the following env vars:_
133+
## Usage
102134

103-
```
104-
COINGECKO_ADAPTER_URL=https://coingecko_adapter_url/
105-
COINPAPRIKA_ADAPTER_URL=https://coinpaprika_adapter_url/
135+
The adapter is designed to calculate implied prices and perform mathematical operations on price data from multiple sources. Common use cases include:
136+
137+
- **Implied Price Calculation**: Calculate ETH/BTC implied price by dividing ETH/USD by BTC/USD
138+
- **Portfolio Valuation**: Multiply token quantities by their USD prices
139+
- **Cross-Rate Calculation**: Derive exchange rates between different currency pairs
140+
- **Risk Metrics**: Calculate price ratios for volatility analysis
141+
142+
The adapter supports both `from`/`to` and `base`/`quote` parameter formats for maximum compatibility with different price sources.
143+
144+
## Testing
145+
146+
The adapter includes comprehensive test coverage with 52 tests (39 unit tests and 13 integration tests):
147+
148+
### Unit Tests
149+
150+
- **`calculateMedian`**: Tests median calculation with various scenarios (odd/even arrays, sorting, precision, edge cases)
151+
- **`normalizeInput`**: Tests input format conversion, property preservation, error handling
152+
- **`parseSources`**: Tests array/string parsing, comma-delimited strings, whitespace handling
153+
154+
### Integration Tests
155+
156+
- **Full Request Cycle**: Tests complete adapter functionality with mocked HTTP transport
157+
- **Operation Support**: Tests both `divide` and `multiply` operations with various data combinations
158+
- **Input Format Compatibility**: Tests both `base`/`quote` and `from`/`to` formats
159+
- **Override Support**: Tests that override configurations work correctly with both operations
160+
- **Error Handling**: Comprehensive error scenario testing with proper status codes (400, 422, 500, etc.)
161+
- **Edge Cases**: Tests invalid operations, missing parameters, various crypto pairs, and malformed inputs
162+
163+
### Running Tests
164+
165+
```bash
166+
# All tests
167+
yarn test packages/composites/implied-price
168+
169+
# Unit tests only (39 tests)
170+
yarn test packages/composites/implied-price/test/unit
171+
172+
# Integration tests only (13 tests)
173+
yarn test packages/composites/implied-price/test/integration
174+
175+
# Specific test pattern
176+
yarn test packages/composites/implied-price --testNamePattern="median"
106177
```
107178

108-
#### Sample Input
179+
## Development
109180

110-
```json
111-
{
112-
"id": "1",
113-
"data": {
114-
"dividendSources": ["coingecko"],
115-
"divisorSources": ["coingecko"],
116-
"dividendInput": {
117-
"from": "LINK",
118-
"to": "USD",
119-
"overrides": {
120-
"coingecko": {
121-
"LINK": "chainlink"
122-
}
123-
}
124-
},
125-
"divisorInput": {
126-
"from": "ETH",
127-
"to": "USD",
128-
"overrides": {
129-
"coingecko": {
130-
"ETH": "ethereum"
131-
}
132-
}
133-
}
134-
}
135-
}
181+
### Building
182+
183+
```bash
184+
# From adapter directory
185+
yarn build
186+
187+
# From project root
188+
yarn build
136189
```
137190

138-
#### Sample Output
191+
### Production Deployment
139192

140-
```json
141-
{
142-
"jobRunID": "1",
143-
"result": "0.005204390891874140333",
144-
"statusCode": 200,
145-
"data": {
146-
"result": "0.005204390891874140333"
147-
}
148-
}
193+
For production deployment, configure the following environment variables for each source adapter you plan to use:
194+
195+
```bash
196+
COINGECKO_ADAPTER_URL=http://localhost:8080/coingecko
197+
COINPAPRIKA_ADAPTER_URL=http://localhost:8080/coinpaprika
198+
COINBASE_ADAPTER_URL=http://localhost:8080/coinbase
149199
```
200+
201+
The adapter includes built-in reliability features such as circuit breakers, retry logic, and request coalescing that are automatically enabled in production environments.

packages/composites/implied-price/package.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,13 @@
3030
"start": "yarn server:dist"
3131
},
3232
"dependencies": {
33-
"@chainlink/ea-bootstrap": "workspace:*",
34-
"@chainlink/ea-test-helpers": "workspace:*",
35-
"axios": "1.9.0",
36-
"decimal.js": "^10.3.1",
37-
"tslib": "^2.3.1"
33+
"@chainlink/external-adapter-framework": "2.7.0",
34+
"decimal.js": "^10.3.1"
3835
},
3936
"devDependencies": {
4037
"@types/jest": "^29.5.14",
4138
"@types/node": "22.14.1",
42-
"@types/supertest": "2.0.16",
4339
"nock": "13.5.6",
44-
"supertest": "6.2.4",
4540
"typescript": "5.8.3"
4641
}
4742
}

packages/composites/implied-price/src/adapter.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)