Trader Joe Price Feed API

Overview

This repository is for implementing the Price Feed API to participate in the Price Feed API Bounty.
Below, you will find details about the API specifications and implementation methods.

API Interface

Request and Response

URL path Request Response
GET /v1/prices/:base/:quote
URL path params:
{
    "base": Address,
    "quote": Address
}
{
    "pair_address": Address,
    "base_address": Address,
    "quote_address": Address,
    "price": Number
}
POST /v1/batch-prices
body:
[
    Address,
]
[
    {
        "pair_address": Address,
        "base_address": Address,
        "quote_address": Address,
        "price": Number
    },
]
GET /v2/prices/:base/:quote/:binstep
URL path params:
{
    "base": Address,
    "quote": Address,
    "binstep": Number,
}
{
    "pair_address": Address,
    "base_address": Address,
    "quote_address": Address,
    "binstep": Number,
    "price": Number
}
POST /v2/batch-prices
body:
[
    Address,
]
[
    {
        "pair_address": Address,
        "base_address": Address,
        "quote_address": Address,
        "binstep": Number,
        "price": Number
    },
]
GET /v2_1/prices/:base/:quote/:binstep
URL path params:
{
    "base": Address,
    "quote": Address,
    "binstep": Number,
}
{
    "pair_address": Address,
    "base_address": Address,
    "quote_address": Address,
    "binstep": Number,
    "price": Number
}
POST /v2_1/batch-prices
body:
[
    Address,
]
[
    {
        "pair_address": Address,
        "base_address": Address,
        "quote_address": Address,
        "binstep": Number,
        "price": Number
    },
]

Client Errors

Name HTTP Status Code Client Error Code Response Description
RequestInputValidationError 400 100
{
    "code": 100,
    "reason": "Bad Request",
    "validationErrors": {
        "formErrors": [],
        "fieldErrors": {
            "params": [
                {
                    "path": [String],
                    "reason": String,
                    "code": String (ValidationErrorCode)
                }
            ]
        }
    }
}

Returned this error in case of validation errors on URL path parameters or request body.

NotFoundError 404 101
{
  "code": 101,
  "reason": "Not Found"
}
RequestTimeoutError 408 102
{
  "code": 102,
  "reason": "Request Timeout"
}
Returned this error in case of a timeout.
Default timeout is 5 seconds.
TooManyRequestsError 429 103
{
  "code": 103,
  "reason": "Too many requests, please try again later"
}
Returned this error when rate limit is exceeded.
PairNoLiquidityError 400 104
{
  "code": 104,
  "reason": "Pair no liquidity"
}
Returned this error when there is no liquidity for a Liquidity Book Pair.
TokenInfoFetchError 400 105
{
  "code": 105,
  "reason": "Failed to fetch token information..."
}
Returned this error when the requested token does not exist.
PairInfoFetchError 400 106
{
  "code": 106,
  "reason": "Failed to fetch pair information..."
}
Returned this error when the requested Liquidity Book Pair Contract does not exist.

Design

Philosophy

Cache

  • Pair Asset Price information is cached until a new block is generated. This cache expiration varies per chain.
  • Immutable information in Token and Pair details is cached. This cache has no expiration. (ex. Token's decimals, Pair's token0/token1 addresses, etc...)

Other Features

Development and Production Environments

Prerequisites

Development Environment

Start local server

$cp .env.development.example .env.development.local
# And replace $JSON_RPC_URL value if you want to use your own Avalanche mainnet node.

$pnpm install

$pnpm run start:dev

$curl --location 'localhost:3000/v1/prices/0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7/0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E' \
--header 'Content-Type: application/json'
$curl --location 'localhost:3000/v1/batch-prices' \
--header 'Content-Type: application/json' \
--data '["0xf4003F4efbe8691B60249E6afBd307ABe7758ADb"]'

Testing

Testing needs an Avalanche mainnet "archive" node and anvil to fetch the same on-chain data for test.

$cp .env.test.example .env.test.local
# And you must replace $ANVIL_FORK_URL value with your own Avalanche "archive" node URL.
# (ex. https://avalanche-mainnet.infura.io/v3/xxxx)

$pnpm install

$pnpm run test:anvil
$pnpm run test
$pnpm run test:watch

Load Testing (Locally 😭)

install k6

$cp .env.development.example .env
# And replace $JSON_RPC_URL value if you want to use your own Avalanche mainnet node.

$pnpm install
$pnpm build

$LOG_LEVEL=silent API_RATE_LIMIT_SKIP=true node dist/src/index.js

$k6 run -e WORKLOAD=smoke ./test/k6-scripts/main.js
$k6 run -e WORKLOAD=breaking ./test/k6-scripts/main.js
$k6 run -e WORKLOAD=spike ./test/k6-scripts/main.js

layered lifegame

Production

Build & Launch Server

$pnpm install

$pnpm run build
$node dist/src/index.js | pnpm exec pino-pretty

References