Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ CRYPTO_COMPARE_KEY='YOUR_CRYPTOCOMPARE_API_KEY'
LIVE_COIN_WATCH_KEY='YOUR_LIVECOINWATCH_API_KEY'
NODE_ENV=development
BASE_URL=http://localhost:3333

# Kill switch for the Binance bStocks synthetic markets in /v2/rates.
# Set to "false" (case-insensitive) to stop serving bstock-* entries without
# a redeploy. Any other value, or unset, leaves them enabled.
BSTOCKS_ENABLED=true
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config/coinsSimple.js
config/coinsSimple.js
docs/
165 changes: 137 additions & 28 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,151 @@
// Rules the repo has chosen for itself. They have to be applied inside the
// TypeScript override as well: an override's `extends` is resolved after the
// top-level `rules`, so airbnb's own defaults would otherwise win there (a
// 100-character max-len, for one).
const projectRules = {
'max-len': [
'error',
{
code: 300,
ignoreUrls: true,
ignoreTrailingComments: true,
},
],
'no-console': 'off',
'linebreak-style': [
'error',
'unix',
],

// A leading underscore marks an internal or test-only name here. TypeScript's
// `private` covers real privacy on the provider classes, and `__retryCount`
// is axios's own convention for the counter it hangs off a request config.
'no-underscore-dangle': [
'error',
{
allow: ['__retryCount'],
allowAfterThis: true,
enforceInMethodNames: false,
},
],

// airbnb bans for..of because transpiling it used to pull in
// regenerator-runtime. This service runs ES2020 on Node and pays no such
// cost; the rest of airbnb's restrictions are kept as-is.
'no-restricted-syntax': [
'error',
{
selector: 'ForInStatement',
message: 'for..in iterates the prototype chain and needs a hasOwnProperty guard. Use Object.{keys,values,entries} instead.',
},
{
selector: 'LabeledStatement',
message: 'Labels are a form of GOTO; use a function instead.',
},
{
selector: 'WithStatement',
message: '`with` is disallowed in strict mode and makes scope ambiguous.',
},
],

// Worth enforcing when introducing a binding, but rewriting an assignment
// such as `rates[2] = fetched[2]` as destructuring reads worse than the
// line it replaces.
'prefer-destructuring': [
'error',
{
VariableDeclarator: {
array: true,
object: true,
},
AssignmentExpression: {
array: false,
object: false,
},
},
],

// This repo allows 300-character lines; airbnb's rule additionally breaks
// any object literal with four or more properties, which contradicts that
// and turns compact fixtures into three-line blocks. Keep the consistency
// checks, drop the property-count trigger.
'object-curly-newline': [
'error',
{
multiline: true,
consistent: true,
},
],

// config/index.ts and lib/axios.ts deliberately export the same value both
// named and default, which is the entirety of what this rule sees.
'import/no-named-as-default': 'off',

// Named exports are the convention here. How many exports a module happens
// to have today is not a reason to change how callers import it.
'import/prefer-default-export': 'off',
};

module.exports = {
root: true,
env: {
commonjs: true,
node: true,
mocha: true,
es2022: true,
jest: true,
},
extends: [
'airbnb-base',
],
rules: {
'max-len': [
'error',
{
code: 300,
ignoreUrls: true,
ignoreTrailingComments: true,
},
],
'no-console': 'off',
'import/extensions': [
'error',
'never',
],
'linebreak-style': [
'error',
'unix',
],
},
parserOptions: {
parser: 'babel-eslint',
},
rules: projectRules,
overrides: [
// TypeScript sources. The parser and the type-aware config live here
// rather than at the top level so plain JS (this file, config/*.js) is
// still linted without having to be part of the tsconfig project.
{
files: [
'**/__tests__/*.{j,t}s?(x)',
files: ['**/*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
},
plugins: [
'@typescript-eslint',
],
extends: [
'airbnb-base',
'airbnb-typescript/base',
],
env: {
mocha: true,
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
},
rules: {
...projectRules,
// TypeScript resolves module specifiers without a file extension, and
// writing one would break `module: commonjs` resolution.
'import/extensions': [
'error',
'ignorePackages',
{
ts: 'never',
js: 'never',
},
],
},
},
// Tests import jest and the other devDependencies by design.
{
files: ['tests/**/*.ts'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
},
},
],
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ Example: http://localhost:3333/rates
docker run -e API_KEY=yourApiKey -p 4444:3333 zelcash/rates-api
```

## bStocks (Binance tokenized equities)

`GET /v2/rates` emits one synthetic `crypto` entry per Binance bStock — a
tokenized US equity on BNB Smart Chain (e.g. `bstock-tslab` for Tesla). Prices
come straight from Binance's public Spot API (no API key required):

- Universe: the intersection of Binance's tokenised-asset list
(`GET https://www.binance.com/bapi/asset/v2/public/asset/asset/get-tokenised-asset`,
filtered to assets with a BSC contract) with Spot symbols currently in
`TRADING` status — about 56 of the ~66 listed assets qualify today.
- Quote currency: **USDT**, not USDC — verified live, no USDC pairs exist for
these symbols.
- `rates.usd` = `<CODE>USDT` last price; `rates.btc` = that price divided by
`BTCUSDT` from the same ticker batch (same venue, no cross-venue basis).
`change24h`/`change7d` come from Binance's 24h ticker and 7d rolling-window
ticker respectively.
- `provider` is always the literal string `"coingecko"`, never `"binance"`.
The ZelCore client keys its market store on `${provider}-${id}` and the
sibling `api` repo advertises each bStock's `coinInfo.coingeckoID` as
`bstock-<code>`; the two literals only meet if the provider here is exactly
`"coingecko"`. This is a cross-repo contract — do not change it in
isolation.
- Binance does **not** omit a halted symbol (e.g. during a stock split) from
its ticker response — it returns the symbol present with
`lastPrice: "0.00000000"`. Prices are therefore accepted only when finite
and strictly positive; a halted/zero-priced symbol keeps serving its last
known-good price rather than a stale zero or a dropped entry, per the
bStocks partner guide's "display-only during halts is acceptable" allowance.
- Toggle via `config.bStocksEnabled` (`config/index.ts`).

## Update Documentation

To update typedoc documentation please run.
Expand Down
16 changes: 15 additions & 1 deletion config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ export const config = {
liveCoinWatchUrl: 'https://api.livecoinwatch.com/',
zelCoinsUrl: 'https://raw.githubusercontent.com/ZelCore-io/Zelcore/master/coins.json',
zelCoinInfoUrl: 'https://raw.githubusercontent.com/ZelCore-io/Zelcore/master/coininfo.json',
binanceApiUrl: 'https://api.binance.com/',
binanceAssetUrl: 'https://www.binance.com/',
// Env kill switch: disabling in production should not require a code
// change + redeploy. Defaults to enabled when unset.
bStocksEnabled: (process.env.BSTOCKS_ENABLED ?? '').toLowerCase() !== 'false',
// How long a failed Binance request (tokenised-asset list / trading-symbol
// set) is negatively-cached before retrying, so an outage doesn't re-spend
// the full AxiosWrapper retry budget on every 30s refresh cycle.
binanceFailureCacheMs: 60 * 1000,
// How long a bStock's last-known-good price is served after Binance stops
// pricing it fresh. The outage this exists for (a stock split halt,
// exchange maintenance) is naturally multi-day, so this is deliberately far
// longer than the ticker windows themselves.
bstocksLastGoodMaxAgeMs: 7 * 24 * 60 * 60 * 1000,
};

export default config;
export default config;
32 changes: 31 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**rates-api v3.0.0** • [**Docs**](modules.md)
**rates-api v3.0.0**

***

Expand Down Expand Up @@ -35,6 +35,36 @@ Example: http://localhost:3333/rates
docker run -e API_KEY=yourApiKey -p 4444:3333 zelcash/rates-api
```

## bStocks (Binance tokenized equities)

`GET /v2/rates` emits one synthetic `crypto` entry per Binance bStock — a
tokenized US equity on BNB Smart Chain (e.g. `bstock-tslab` for Tesla). Prices
come straight from Binance's public Spot API (no API key required):

- Universe: the intersection of Binance's tokenised-asset list
(`GET https://www.binance.com/bapi/asset/v2/public/asset/asset/get-tokenised-asset`,
filtered to assets with a BSC contract) with Spot symbols currently in
`TRADING` status — about 56 of the ~66 listed assets qualify today.
- Quote currency: **USDT**, not USDC — verified live, no USDC pairs exist for
these symbols.
- `rates.usd` = `<CODE>USDT` last price; `rates.btc` = that price divided by
`BTCUSDT` from the same ticker batch (same venue, no cross-venue basis).
`change24h`/`change7d` come from Binance's 24h ticker and 7d rolling-window
ticker respectively.
- `provider` is always the literal string `"coingecko"`, never `"binance"`.
The ZelCore client keys its market store on `${provider}-${id}` and the
sibling `api` repo advertises each bStock's `coinInfo.coingeckoID` as
`bstock-<code>`; the two literals only meet if the provider here is exactly
`"coingecko"`. This is a cross-repo contract — do not change it in
isolation.
- Binance does **not** omit a halted symbol (e.g. during a stock split) from
its ticker response — it returns the symbol present with
`lastPrice: "0.00000000"`. Prices are therefore accepted only when finite
and strictly positive; a halted/zero-priced symbol keeps serving its last
known-good price rather than a stale zero or a dropped entry, per the
bStocks partner guide's "display-only during halts is acceptable" allowance.
- Toggle via `config.bStocksEnabled` (`config/index.ts`).

## Update Documentation

To update typedoc documentation please run.
Expand Down
4 changes: 2 additions & 2 deletions docs/index/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[**rates-api v3.0.0**](../README.md) • **Docs**
[**rates-api v3.0.0**](../README.md)

***

[rates-api v3.0.0](../modules.md) / index
[rates-api](../modules.md) / index

# index
4 changes: 3 additions & 1 deletion docs/modules.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[**rates-api v3.0.0**](README.md) • **Docs**
[**rates-api v3.0.0**](README.md)

***

Expand All @@ -14,9 +14,11 @@
- [src/lib/utils](src/lib/utils/README.md)
- [src/routes](src/routes/README.md)
- [src/services/apiServices](src/services/apiServices/README.md)
- [src/services/bstocks](src/services/bstocks/README.md)
- [src/services/coinAggregatorIDs](src/services/coinAggregatorIDs/README.md)
- [src/services/newContracts](src/services/newContracts/README.md)
- [src/services/providers](src/services/providers/README.md)
- [src/services/providers/binance](src/services/providers/binance/README.md)
- [src/services/providers/bitpay](src/services/providers/bitpay/README.md)
- [src/services/providers/coinGecko](src/services/providers/coinGecko/README.md)
- [src/services/providers/cryptoCompare](src/services/providers/cryptoCompare/README.md)
Expand Down
8 changes: 3 additions & 5 deletions docs/src/lib/axios/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
[**rates-api v3.0.0**](../../../README.md) • **Docs**
[**rates-api v3.0.0**](../../../README.md)

***

[rates-api v3.0.0](../../../modules.md) / src/lib/axios
[rates-api](../../../modules.md) / src/lib/axios

# src/lib/axios

## Index

### Classes
## Classes

- [AxiosWrapper](classes/AxiosWrapper.md)

Expand Down
Loading