Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .pnp.cjs

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

Empty file.
104 changes: 104 additions & 0 deletions packages/sources/basic-link-price-source/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Chainlink External Adapter: Basic LINK Price Source

This project documents my hands-on experience developing a basic Chainlink price feed adapter within the `external-adapters-js` monorepo. The primary goal was to familiarize myself with the monorepo structure, Chainlink's External Adapter framework, and demonstrate my ability to onboard independently without external assistance. By building this adapter from scratch, I aimed to showcase rapid learning, problem-solving, and proficiency in TypeScript based blockchain development skills directly aligned with roles at Chainlink Labs.

The adapter fetches LINK prices against USDC and ETH from Uniswap V3 pools on Ethereum and Arbitrum, using direct RPC calls for efficiency and decentralization. This exercise not only deepened my understanding of source adapters but also prepared me to contribute to more complex integrations like composites and targets.

## Adapter Generation and Development Notes

- Initiated the adapter using `yarn new source basic-link-price-source` to scaffold the basic structure.
- Followed the generator prompt by running:
`yo ./.yarn/cache/node_modules/@chainlink/external-adapter-framework/generator-adapter/generators/app/index.js packages/sources && yarn new tsconfig`
- Leveraged the External Adapter Framework (https://github.com/smartcontractkit/ea-framework-js/tree/main) for oracle interactions, referencing endpoint types (e.g., crypto/price) to ensure compatibility.
- Adapted the logic to a custom crypto price endpoint tailored to LINK-specific queries.
- Encountered an LSP (Language Server Protocol) issue where cached libraries weren't recognized in my editor (Neo Vim)—**TODO: Investigate deeper into LSP configuration for monorepo environments.**
- Temporarily hardcoded parameter descriptions (e.g., "The symbol of symbols of the currency to query") to resolve runtime failures during `yarn server:dist`; **TODO: Review how other adapters handle dynamic descriptions.**
- For testing individual adapters: `export adapter=basic-link-price-source; yarn test $adapter/test/integration`—**TODO: Implement integration tests for this adapter.**

### General Thoughts on the Repository

The CONTRIBUTING.md is straightforward and well-organized, providing clear guidelines for setup, PR processes, and best practices. Having access to numerous existing adapters within the monorepo was invaluable, allowing me to reference real world examples for transports, endpoints, and configurations without needing external documentation. This structure facilitated quick iteration and independent problem-solving, highlighting the repo's design for scalability and collaboration.

## Building and Running the Server

To build and run the adapter:

- Navigate to the adapter's root directory (`packages/sources/basic-link-price-source`).
- Run `yarn build` to compile the TypeScript code.
- Then, start the server with `yarn server:dist` (runs on localhost:8080 by default; override RPC URLs via environment variables if needed, e.g., `RPC_URL_ETHEREUM=https://ethereum-rpc.publicnode.com yarn server:dist`).

## Calling the Adapter

Call the endpoints locally using cURL (assuming the server is running on localhost:8080). These commands query `link-usdc` and `link-eth` on Ethereum and Arbitrum, leveraging default base/quote values for simplicity.

1. **link-usdc on ethereum**:

```bash
curl -X POST http://localhost:8080/ \
-H 'Content-Type: application/json' \
-d '{
"id": "1",
"data": {
"endpoint": "link-usdc",
"base": "LINK",
"quote": "USDC",
"chain": "ethereum"
}
}'
```

2. **link-usdc on arbitrum**:

```bash
curl -X POST http://localhost:8080/ \
-H 'Content-Type: application/json' \
-d '{
"id": "1",
"data": {
"endpoint": "link-usdc",
"base": "LINK",
"quote": "USDC",
"chain": "arbitrum"
}
}'
```

3. **link-eth on ethereum**:

```bash
curl -X POST http://localhost:8080/ \
-H 'Content-Type: application/json' \
-d '{
"id": "1",
"data": {
"endpoint": "link-eth",
"base": "LINK",
"quote": "ETH",
"chain": "ethereum"
}
}'
```

4. **link-eth on arbitrum**:
```bash
curl -X POST http://localhost:8080/ \
-H 'Content-Type: application/json' \
-d '{
"id": "1",
"data": {
"endpoint": "link-eth",
"base": "LINK",
"quote": "ETH",
"chain": "arbitrum"
}
}'
```

## Proposed Enhancements

To extend this project and demonstrate broader expertise in Chainlink's ecosystem:

- **Composite Adapter for Automated Selling**: Build a composite adapter that chains this source adapter to fetch LINK prices, then triggers a target adapter to execute a sell order if the price exceeds predefined thresholds (e.g., x or y). This could be tested on Sepolia testnet, showcasing integration of source, composite, and target adapters for automated trading logic.
- **Cross-Chain Transfer Integration**: Develop an additional composite adapter that, post-sell, transfers the resulting tokens from Ethereum Sepolia to Arbitrum Sepolia using Chainlink CCIP (Cross-Chain Interoperability Protocol). This enhancement would highlight familiarity with testnets, CCIP for secure cross-chain operations, and end-to-end adapter composition for real-world DeFi workflows.

This project underscores my technical curious and self-driven approach to as a long time Fan of Chainlink and experienced engineer
41 changes: 41 additions & 0 deletions packages/sources/basic-link-price-source/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@chainlink/basic-link-price-source-adapter",
"version": "0.0.0",
"description": "Chainlink basic-link-price-source adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"basic-link-price-source"
],
"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": "^29.5.14",
"@types/node": "22.14.1",
"nock": "13.5.6",
"typescript": "5.8.3"
},
"dependencies": {
"@chainlink/external-adapter-framework": "2.8.0",
"ethers": "^6.15.0",
"tslib": "2.4.1"
}
}
14 changes: 14 additions & 0 deletions packages/sources/basic-link-price-source/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
RPC_URL_ETHEREUM: {
type: 'string',
description: 'Ethereum RPC URL',
default: 'https://ethereum-rpc.publicnode.com',
},
RPC_URL_ARBITRUM: {
type: 'string',
description: 'Arbitrum RPC URL',
default: 'https://arbitrum-one-rpc.publicnode.com',
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"basic-link-price-source": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { endpoint as linkEth } from './link-eth'
export { endpoint as linkUsdc } from './link-usdc'
50 changes: 50 additions & 0 deletions packages/sources/basic-link-price-source/src/endpoint/link-eth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PriceEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { linkEthTransport } from '../transport/link-eth'

export const inputParameters = new InputParameters(
{
base: {
aliases: ['from', 'coin'],
type: 'string',
description: 'The symbol of symbols of the currency to query',
default: 'LINK',
required: false,
},
quote: {
aliases: ['to', 'market'],
type: 'string',
description: 'The symbol of the currency to convert to',
default: 'ETH',
required: false,
},
chain: {
type: 'string',
description: 'Blockchain to query',
required: false,
default: 'ethereum',
options: ['ethereum', 'arbitrum'],
},
},
[
{
base: 'LINK',
quote: 'ETH',
chain: 'ethereum',
},
],
)

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: SingleNumberResultResponse
Settings: typeof config.settings
}

export const endpoint = new PriceEndpoint({
name: 'link-eth',
transport: linkEthTransport,
inputParameters,
})
50 changes: 50 additions & 0 deletions packages/sources/basic-link-price-source/src/endpoint/link-usdc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PriceEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { linkUsdcTransport } from '../transport/link-usdc'

export const inputParameters = new InputParameters(
{
base: {
aliases: ['from', 'coin'],
type: 'string',
description: 'The symbol of symbols of the currency to query',
default: 'LINK',
required: false,
},
quote: {
aliases: ['to', 'market'],
type: 'string',
description: 'The symbol of the currency to convert to',
default: 'USDC',
required: false,
},
chain: {
type: 'string',
description: 'Blockchain to query',
required: false,
default: 'ethereum',
options: ['ethereum', 'arbitrum'],
},
},
[
{
base: 'LINK',
quote: 'USDC',
chain: 'ethereum',
},
],
)

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: SingleNumberResultResponse
Settings: typeof config.settings
}

export const endpoint = new PriceEndpoint({
name: 'link-usdc',
transport: linkUsdcTransport,
inputParameters,
})
15 changes: 15 additions & 0 deletions packages/sources/basic-link-price-source/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { PriceAdapter } from '@chainlink/external-adapter-framework/adapter' // Use PriceAdapter instead
import { config } from './config'
import { linkEth, linkUsdc } from './endpoint' // Ensure this exports the endpoints correctly (e.g., via index.ts or direct imports)

export const adapter = new PriceAdapter({
// Switch to PriceAdapter
defaultEndpoint: linkUsdc.name,
name: 'BASIC_LINK-PRICE-SOURCE',
config,
endpoints: [linkUsdc, linkEth],
// includes: [...] // Optional: Add if you have an includes.json for inverse pairs (e.g., ETH/LINK as 1 / LINK/ETH)
})

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