-
Notifications
You must be signed in to change notification settings - Fork 323
CM-1099 LiveArt External Adapter #4018
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 21 commits
5c5c573
a64af31
ba0ef5c
e4bb691
372f03f
05d634b
2cc1535
4ad49a9
7e6998c
a775786
3406afd
41a1c4c
ff5e8bf
e2c52cb
2fae932
3233279
45dd724
d1d79e1
a9a3faf
731f1d7
90b6795
26d82b9
b38beb2
415ee4e
7e18493
a12dc0a
e5062ff
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/liveart-external-adapter': major | ||
| --- | ||
|
|
||
| LiveArt EA initial release | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "@chainlink/liveart-external-adapter", | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "version": "1.0.0", | ||
| "description": "LiveArt NAV external adapter for Chainlink", | ||
| "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": { | ||
| "test": "echo \"Error: no test specified\" && exit 1", | ||
markhoangcll marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo", | ||
| "prepack": "yarn build", | ||
| "build": "tsc -b", | ||
| "server": "node -e 'require(\"./src/index.ts\").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", | ||
| "tslib": "2.4.1" | ||
| } | ||
| } | ||
| 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: AdapterConfig = new AdapterConfig({ | ||
| API_BASE_URL: { | ||
| description: 'The API URL for the LiveArt data provider', | ||
| type: 'string', | ||
| required: true, | ||
| default: 'https://artwork-price-oracle-api-dev-ms.liveart.ai', | ||
|
||
| }, | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' | ||
| import { InputParameters } from '@chainlink/external-adapter-framework/validation' | ||
|
|
||
| import { httpTransport } from '../transport/asset' | ||
|
|
||
| export const inputParameters = new InputParameters( | ||
| { | ||
| asset_id: { | ||
| required: true, | ||
| type: 'string', | ||
| description: 'The ID of the artwork asset to fetch', | ||
| }, | ||
| }, | ||
| [ | ||
| { | ||
| asset_id: 'KUSPUM', | ||
| }, | ||
| ], | ||
| ) | ||
|
|
||
| export const asset = new AdapterEndpoint({ | ||
| name: 'asset', | ||
| transport: httpTransport, | ||
| inputParameters, | ||
| }) |
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' | ||
| import { httpTransport } from '../transport/assets' | ||
|
|
||
| // Assets endpoint has no input params | ||
| export const assets = new AdapterEndpoint({ | ||
| name: 'assets', | ||
| transport: httpTransport, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { expose, ServerInstance } from '@chainlink/external-adapter-framework' | ||
| import { Adapter } from '@chainlink/external-adapter-framework/adapter' | ||
|
|
||
| import { config } from './config/config' | ||
| import { asset } from './endpoint/asset' | ||
| import { assets } from './endpoint/assets' | ||
|
|
||
| export const adapter = new Adapter({ | ||
| defaultEndpoint: assets.name, | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| name: 'LIVE_ART', | ||
| config, | ||
| endpoints: [asset, assets], | ||
| }) | ||
|
|
||
| export const server = (): Promise<ServerInstance | undefined> => expose(adapter) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { HttpTransport } from '@chainlink/external-adapter-framework/transports/http' | ||
| import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params' | ||
| import { config } from '../config/config' | ||
| import { inputParameters } from '../endpoint/asset' | ||
| import { Asset } from './types' | ||
|
|
||
| export type BaseEndpointTypes = { | ||
| Parameters: typeof inputParameters.definition | ||
| Response: { | ||
| Data: Asset | ||
| Result: null | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Settings: typeof config.settings | ||
| } | ||
|
|
||
| export type HttpTransportTypes = BaseEndpointTypes & { | ||
| Provider: { | ||
| RequestBody: never | ||
| ResponseBody: Asset | ||
| } | ||
| } | ||
|
|
||
| export const httpTransport = new HttpTransport<HttpTransportTypes>({ | ||
| prepareRequests: (params, adapterSettings) => { | ||
| return params.map((param) => { | ||
| return { | ||
| params: [param], | ||
| request: { | ||
| baseURL: adapterSettings.API_BASE_URL, | ||
| url: `/asset/${param.asset_id}`, | ||
| }, | ||
| } | ||
| }) | ||
| }, | ||
| parseResponse: (params, response) => { | ||
| return params.map((param: TypeFromDefinition<typeof inputParameters.definition>) => { | ||
| if (param.asset_id !== response.data.asset_id) { | ||
| return { | ||
| params: param, | ||
| response: { | ||
| errorMessage: `Mismatched asset_id in response. Expected ${param.asset_id}, got ${response.data.asset_id}`, | ||
| statusCode: 500, | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| params: param, | ||
| response: { | ||
| result: null, | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| data: response.data, | ||
| }, | ||
| } | ||
| }) | ||
| }, | ||
| }) | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { HttpTransport } from '@chainlink/external-adapter-framework/transports/http' | ||
| import { EmptyInputParameters } from '@chainlink/external-adapter-framework/validation/input-params' | ||
| import { config } from '../config/config' | ||
| import { Asset } from './types' | ||
|
|
||
| export type BaseEndpointTypes = { | ||
| Parameters: EmptyInputParameters | ||
| Response: { | ||
| Data: Asset[] | ||
| Result: null | ||
| } | ||
| Settings: typeof config.settings | ||
| } | ||
|
|
||
| export type HttpTransportTypes = BaseEndpointTypes & { | ||
| Provider: { | ||
| RequestBody: never | ||
| ResponseBody: Asset[] | ||
| } | ||
| } | ||
|
|
||
| export const httpTransport = new HttpTransport<HttpTransportTypes>({ | ||
| prepareRequests: (params, adapterSettings) => { | ||
| return { | ||
| params, | ||
| request: { | ||
| baseURL: adapterSettings.baseURL, | ||
| url: '/assets', | ||
| method: 'GET', | ||
| }, | ||
| } | ||
| }, | ||
| parseResponse: (params, response) => { | ||
| return [ | ||
| { | ||
| params, | ||
| response: { | ||
| data: response.data, | ||
| result: null, | ||
| }, | ||
| }, | ||
| ] | ||
| }, | ||
| }) |
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| export interface Asset { | ||
| asset_id: string | ||
| asset_info_category: string | ||
| asset_info_creator: string | ||
| asset_info_title: string | ||
| asset_info_year_created: string | ||
| asset_info_description: string | ||
| asset_info_url: string | ||
| current_estimated_nav_usd: string | ||
| current_estimated_nav_updated_at: string | ||
| token_total_shares: number | ||
| token_current_estimated_nav_per_share_usd: string | ||
| offering_price_usd: string | ||
| success: boolean | ||
| message: string | ||
| response_timestamp: string | ||
| } |
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "requests": [{ | ||
| "artwork_id": "banksy" | ||
| }] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
|
||
| exports[`LiveArt NAV endpoints /asset/\${asset_id} should handle upstream bad response 1`] = ` | ||
markhoangcll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| "data": { | ||
| "asset_id": "ROLSUB", | ||
| "asset_info_category": "", | ||
| "asset_info_creator": "", | ||
| "asset_info_description": "", | ||
| "asset_info_title": "", | ||
| "asset_info_url": "", | ||
| "asset_info_year_created": "", | ||
| "current_estimated_nav_updated_at": "", | ||
| "current_estimated_nav_usd": "", | ||
| "message": "Asset ID 'AssetId.ROLSUB' not found", | ||
| "offering_price_usd": "", | ||
| "response_timestamp": "2025-10-21T11:45:51.122126", | ||
| "success": false, | ||
| "token_current_estimated_nav_per_share_usd": "", | ||
| "token_total_shares": 0, | ||
| }, | ||
| "result": null, | ||
| "statusCode": 200, | ||
| "timestamps": { | ||
| "providerDataReceivedUnixMs": 978347471111, | ||
| "providerDataRequestedUnixMs": 978347471111, | ||
| }, | ||
| } | ||
| `; | ||
|
|
||
| exports[`LiveArt NAV endpoints /asset/\${asset_id} should return success for valid asset_id 1`] = ` | ||
| { | ||
| "data": { | ||
| "asset_id": "KUSPUM", | ||
| "asset_info_category": "Artwork", | ||
| "asset_info_creator": "Yayoi Kusama", | ||
| "asset_info_description": "Yayoi Kusama is a living legend whose polka-dotted universe has taken over museums, fashion, and the auction block—generating a $2.4B market cap. From MoMA to Louis Vuitton, her iconic Infinity Rooms and Pumpkin artworks have made her one of the most collected artists alive", | ||
| "asset_info_title": "Pumpkin (2)", | ||
| "asset_info_url": "https://liveart.io/analytics/artworks/wDigKX/yayoi-kusama", | ||
| "asset_info_year_created": "1990", | ||
| "current_estimated_nav_updated_at": "2025-10-13T08:50:09.546855", | ||
| "current_estimated_nav_usd": "67410.81654052", | ||
| "message": "", | ||
| "offering_price_usd": "70000.00000000", | ||
| "response_timestamp": "2025-10-20T13:01:12.095377", | ||
| "success": true, | ||
| "token_current_estimated_nav_per_share_usd": "0.09630117", | ||
| "token_total_shares": 700000, | ||
| }, | ||
| "result": null, | ||
| "statusCode": 200, | ||
| "timestamps": { | ||
| "providerDataReceivedUnixMs": 978347471111, | ||
| "providerDataRequestedUnixMs": 978347471111, | ||
| }, | ||
| } | ||
| `; | ||
Uh oh!
There was an error while loading. Please reload this page.