|
1 | | -# Chainlink Implied-price Composite Adapter |
| 1 | +# Computed Price Composite Adapter |
2 | 2 |
|
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. |
4 | 4 |
|
5 | | -## Configuration |
| 5 | +## Features |
6 | 6 |
|
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 |
8 | 14 |
|
9 | | -| Required? | Name | Description | Options | Defaults to | |
10 | | -| :-------: | :--------------------: | :-----------------------------------------: | :-----: | :---------: | |
11 | | -| | `[source]_ADAPTER_URL` | The adapter URL to query for any `[source]` | | | |
| 15 | +## Configuration |
12 | 16 |
|
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 | +``` |
14 | 53 |
|
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 |
16 | 55 |
|
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 | +``` |
18 | 67 |
|
19 | | -#### Input Params |
| 68 | +**Operation**: The mathematical operation to perform |
20 | 69 |
|
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) |
30 | 72 |
|
31 | | -Each source in `sources` needs to have a defined `*_ADAPTER_URL` defined as an env var. |
| 73 | +## Sample Input |
32 | 74 |
|
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) |
34 | 76 |
|
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 | +} |
38 | 87 | ``` |
39 | 88 |
|
40 | | -#### Sample Input |
| 89 | +### Multiplication Example |
41 | 90 |
|
42 | 91 | ```json |
43 | 92 | { |
44 | | - "id": "1", |
45 | 93 | "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" |
67 | 99 | } |
68 | 100 | } |
69 | 101 | ``` |
70 | 102 |
|
71 | | -#### Sample Output |
| 103 | +## Sample Output |
72 | 104 |
|
73 | 105 | ```json |
74 | 106 | { |
75 | | - "jobRunID": "1", |
76 | | - "result": "0.005204390891874140333", |
| 107 | + "result": 0.0652, |
77 | 108 | "statusCode": 200, |
78 | 109 | "data": { |
79 | | - "result": "0.005204390891874140333" |
| 110 | + "result": 0.0652 |
80 | 111 | } |
81 | 112 | } |
82 | 113 | ``` |
83 | 114 |
|
84 | | -### impliedPrice endpoint |
| 115 | +## Calculation |
85 | 116 |
|
86 | | -This legacy endpoint is the default endpoint for backward compatibility. |
| 117 | +The adapter: |
87 | 118 |
|
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` |
89 | 125 |
|
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 |
98 | 127 |
|
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 |
100 | 132 |
|
101 | | -_E.g. for a request with `"dividendSources": ["coingecko", "coinpaprika"]`, you will need to have pre-set the following env vars:_ |
| 133 | +## Usage |
102 | 134 |
|
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" |
106 | 177 | ``` |
107 | 178 |
|
108 | | -#### Sample Input |
| 179 | +## Development |
109 | 180 |
|
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 |
136 | 189 | ``` |
137 | 190 |
|
138 | | -#### Sample Output |
| 191 | +### Production Deployment |
139 | 192 |
|
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 |
149 | 199 | ``` |
| 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. |
0 commit comments