bitpay-rates 1.2.18 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) colkito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,79 +1,141 @@
1
1
  # bitpay-rates
2
2
 
3
- ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/colkito/bitpay-rates/npm-publish.yml?style=flat-square)
3
+ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/colkito/bitpay-rates/ci.yml?style=flat-square)
4
+ [![npm](https://img.shields.io/npm/v/bitpay-rates.svg?style=flat-square)](https://www.npmjs.com/package/bitpay-rates)
4
5
  [![BundlePhobia](https://img.shields.io/bundlephobia/min/bitpay-rates.svg?style=flat-square)](https://bundlephobia.com/result?p=bitpay-rates)
5
6
  [![BundlePhobia](https://img.shields.io/bundlephobia/minzip/bitpay-rates.svg?style=flat-square)](https://bundlephobia.com/result?p=bitpay-rates)
6
7
 
7
- A lightweight Node.js wrapper for [BitPay's](https://bitpay.com/rates) exchange rates API, now in TypeScript.
8
+ A lightweight Node.js wrapper for [BitPay exchange rates](https://www.bitpay.com/exchange-rates), written in TypeScript.
8
9
 
9
- Zero-dependency, `promise` and `callback` support for easy integration into your project. ✨
10
+ Zero runtime dependencies, promise-based, dual ESM + CommonJS. Talks to the
11
+ official public [Rates API](https://developer.bitpay.com/reference/rates)
12
+ (`X-Accept-Version: 2.0.0`).
10
13
 
11
14
  ## Requirements
12
15
 
13
- - nodejs >= 12.x
16
+ - Node.js >= 22
14
17
 
15
- ## Examples
18
+ ```bash
19
+ npm install bitpay-rates
20
+ ```
16
21
 
17
- Getting a rate by `code`:
22
+ ## Breaking changes in v3
18
23
 
19
- ```js
20
- import bitpayRates from 'bitpay-rates';
24
+ - **Promise-only.** The legacy callback signature (`get(code, cb)`) is gone —
25
+ use `async/await` or `.then()` / `.catch()`.
26
+ - **Named arguments.** `get()` now takes a single `{ base, quote }` object, so
27
+ there is no argument order to remember: `get('USD', 'ETH')` becomes
28
+ `get({ base: 'ETH', quote: 'USD' })`.
29
+ - `get({ base })` returns the **whole table** for that base, which v2 could not
30
+ express. `get({ quote })` returns that one rate against BTC.
31
+ - Dual ESM + CJS with an `exports` map (`import` and `require` both work).
32
+ - Node.js >= 22.
33
+ - Requests time out after 10 seconds.
34
+ - Currency codes are validated (`/^[A-Z0-9]{2,10}$/`); anything else rejects
35
+ with a `TypeError` before a request is made.
36
+
37
+ ## Usage
38
+
39
+ ### ESM / TypeScript
40
+
41
+ ```ts
42
+ import { get, type RateObj } from 'bitpay-rates';
43
+
44
+ const all: RateObj[] = await get();
45
+ // GET /rates/BTC → every rate against BTC
21
46
 
22
- const code = 'ARS'; // see list of codes bellow
47
+ const vsEth: RateObj[] = await get({ base: 'ETH' });
48
+ // GET /rates/ETH → every rate against ETH
23
49
 
24
- // Using promise
25
- bitpayRates
26
- .get(code)
27
- .then((rate) => console.log('Promise Rate:', rate))
28
- .catch((err) => console.error('Promise Error:', err));
50
+ const usd: RateObj = await get({ quote: 'USD' });
51
+ // GET /rates/BTC/USD → { code: 'USD', name: 'US Dollar', rate: 76471.42 }
52
+
53
+ const ethUsd: RateObj = await get({ base: 'ETH', quote: 'USD' });
54
+ // GET /rates/ETH/USD
29
55
  ```
30
56
 
31
- Successful response:
57
+ `base` is the cryptocurrency you are pricing (default `BTC`); `quote` is the
58
+ currency you want the price in. Omitting `quote` gives the full table. The
59
+ return type follows from that: `RateObj[]` without `quote`, `RateObj` with it.
60
+
61
+ The default export is a namespace object holding the same function, so the v2
62
+ import style keeps working:
63
+
64
+ ```ts
65
+ import bitpayRates from 'bitpay-rates';
32
66
 
33
- ```json
34
- {
35
- "code": "ARS",
36
- "name": "Argentine Peso",
37
- "rate": 3793422.92
38
- }
67
+ const usd = await bitpayRates.get({ quote: 'USD' });
39
68
  ```
40
69
 
41
- Getting `all` the rates:
70
+ ### CommonJS
42
71
 
43
72
  ```js
44
- import bitpayRates from 'bitpay-rates';
73
+ const { get } = require('bitpay-rates');
74
+ // or: const bitpayRates = require('bitpay-rates'); bitpayRates.get({ quote: 'USD' })
45
75
 
46
- // Using callback
47
- bitpayRates.get((err, res) => {
48
- console.error('Callback Error:', err);
49
- console.log('Callback Rates:', res);
50
- });
76
+ get({ quote: 'USD' })
77
+ .then((rate) => console.log(rate))
78
+ .catch((err) => console.error(err));
51
79
  ```
52
80
 
53
- Successful response:
54
-
55
- ```json
56
- [
57
- {
58
- "code": "ARS",
59
- "name": "Argentine Peso",
60
- "rate": 5291987.02
61
- },
62
- {
63
- "code": "BUSD",
64
- "name": "Binance USD",
65
- "rate": 57818.28
66
- },
67
- {...}
68
- ]
81
+ All four styles — named or default, ESM or CommonJS — are asserted against the
82
+ built artifact on every CI run and before every publish (`npm run smoke`).
83
+
84
+ ### Errors
85
+
86
+ `get()` rejects when BitPay returns a non-2xx status, an `{ error }` payload,
87
+ malformed JSON, a network failure, or when the request exceeds 10 seconds. It
88
+ rejects with a `TypeError` — before any request — when a code is not 2-10
89
+ alphanumeric characters.
90
+
91
+ It also rejects when the response shape does not match what you asked for.
92
+ `GET /rates/{code}` is polymorphic: a base with a rate table answers with a
93
+ list, anything else answers with a single rate. So `get({ base: 'USD' })`
94
+ rejects rather than handing you a `RateObj` typed as `RateObj[]`.
95
+
96
+ ```js
97
+ import { get } from 'bitpay-rates';
98
+
99
+ get({ quote: 'INVALID' })
100
+ .then((rate) => console.log(rate))
101
+ .catch((err) => console.error(err));
69
102
  ```
70
103
 
71
- More examples [here](example/rates-example.js).
104
+ More examples in [`example/rates-example.mjs`](example/rates-example.mjs)
105
+ (run `npm run build` first).
106
+
107
+ ## Types
108
+
109
+ ```ts
110
+ type RateObj = { code: string; name: string; rate: number };
111
+ type RateQuery = { base?: string; quote?: string };
112
+
113
+ function get(): Promise<RateObj[]>;
114
+ function get(query: { base?: string; quote?: undefined }): Promise<RateObj[]>;
115
+ function get(query: { base?: string; quote: string }): Promise<RateObj>;
116
+ ```
117
+
118
+ Both codes are uppercased automatically and must match `/^[A-Z0-9]{2,10}$/`.
119
+ Default `base` is `BTC`.
120
+
121
+ ## Available codes
122
+
123
+ See [CODES.md](CODES.md). It is regenerated from `GET /rates/BTC` on every
124
+ release PR (`npm run update-codes`). Codes containing `_` (chain-specific
125
+ variants such as `USDC_arb`) appear in that table, but BitPay rejects them as a
126
+ `base` or `quote`, so they cannot be queried individually.
127
+
128
+ ## Security
129
+
130
+ Zero runtime dependencies, published from CI only via npm Trusted Publishing
131
+ (OIDC) with a provenance attestation, and every release is gated on a human
132
+ publishing the draft GitHub Release. See
133
+ [SECURITY.md](.github/SECURITY.md) to report a vulnerability.
72
134
 
73
- ## Available Codes (updated: 2024-01-24)
135
+ ## Contributing
74
136
 
75
- [Follow this link](CODES.md) to see the complete list of codes.
137
+ PRs only — see [CONTRIBUTING.md](.github/CONTRIBUTING.md). MIT licensed.
76
138
 
77
- ## Related Packages
139
+ ## Related packages
78
140
 
79
141
  - [Blockchain Exchange Rates API](https://npmjs.com/blockchain-rates)
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=`https://bitpay.com/rates`,t=1e4,n=/^[A-Z0-9]{2,10}$/,r={"X-Accept-Version":`2.0.0`,Accept:`application/json`,"User-Agent":`bitpay-rates`};async function i(n={}){let{quote:r}=n,i=a(n.base??`BTC`,`base`),s=r===void 0,c=s?`${e}/${i}`:`${e}/${i}/${a(r,`quote`)}`,l=new AbortController,u,d=new Promise((e,n)=>{u=setTimeout(()=>{let e=Error(`Request to ${c} timed out after ${t}ms`);l.abort(e),n(e)},t)});try{return await Promise.race([o(c,l.signal,s),d])}finally{u!==void 0&&clearTimeout(u)}}function a(e,t){let r=e.toUpperCase();if(!n.test(r))throw TypeError(`Invalid ${t} currency code: ${JSON.stringify(e)}`);return r}async function o(e,t,n){let i=await fetch(e,{headers:r,signal:t}),a=await i.text(),o;try{o=JSON.parse(a)}catch(t){throw i.ok?t:Error(`Request to ${e} failed with HTTP ${i.status}`)}if(s(o)&&o.error!=null)throw Error(String(o.error));if(!i.ok)throw Error(`Request to ${e} failed with HTTP ${i.status}`);let l=s(o)?o.data:void 0;if(n){if(!Array.isArray(l)||!l.every(c))throw Error(`Unexpected response from ${e}: expected a list of rates`)}else if(!c(l))throw Error(`Unexpected response from ${e}: expected a single rate`);return l}function s(e){return typeof e==`object`&&!!e}function c(e){return s(e)&&typeof e.code==`string`&&typeof e.name==`string`&&typeof e.rate==`number`}var l={get:i};exports.default=l,exports.get=i;
@@ -0,0 +1,53 @@
1
+ //#region src/index.d.mts
2
+ export type RateObj = {
3
+ code: string;
4
+ name: string;
5
+ rate: number;
6
+ };
7
+ export type RateResponse = RateObj | RateObj[];
8
+ /** Arguments for {@link get}. Named so neither code can be passed in the wrong position. */
9
+ export type RateQuery = {
10
+ /**
11
+ * Base cryptocurrency: the asset being priced.
12
+ *
13
+ * @defaultValue `'BTC'`
14
+ */
15
+ base?: string;
16
+ /**
17
+ * Quote currency: what to price `base` in. Omit for every rate against
18
+ * `base`.
19
+ */
20
+ quote?: string;
21
+ };
22
+ /**
23
+ * Fetch BitPay exchange rates.
24
+ *
25
+ * @param query - `{ base, quote }`. Omit `quote` for the full table, omit both for BTC.
26
+ * @returns Every rate for `base` when `quote` is omitted, otherwise that single {@link RateObj}.
27
+ * @throws `TypeError` if a code is not 2-10 alphanumeric characters, before any request is made.
28
+ *
29
+ * @example
30
+ * await get(); // every rate against BTC
31
+ * await get({ base: 'ETH' }); // every rate against ETH
32
+ * await get({ quote: 'USD' }); // BTC/USD
33
+ * await get({ base: 'ETH', quote: 'USD' }); // ETH/USD
34
+ */
35
+ export declare function get(): Promise<RateObj[]>;
36
+ export declare function get(query: {
37
+ base?: string;
38
+ quote?: undefined;
39
+ }): Promise<RateObj[]>;
40
+ export declare function get(query: {
41
+ base?: string;
42
+ quote: string;
43
+ }): Promise<RateObj>;
44
+ /**
45
+ * Namespace object, so both import styles work in ESM and CommonJS:
46
+ * `import { get }` / `const { get } = require(...)`, and
47
+ * `import bitpayRates` / `const bitpayRates = require(...)` + `bitpayRates.get()`.
48
+ */
49
+ declare const _default: {
50
+ get: typeof get;
51
+ };
52
+ //#endregion
53
+ export { _default as default };
@@ -0,0 +1,53 @@
1
+ //#region src/index.d.mts
2
+ export type RateObj = {
3
+ code: string;
4
+ name: string;
5
+ rate: number;
6
+ };
7
+ export type RateResponse = RateObj | RateObj[];
8
+ /** Arguments for {@link get}. Named so neither code can be passed in the wrong position. */
9
+ export type RateQuery = {
10
+ /**
11
+ * Base cryptocurrency: the asset being priced.
12
+ *
13
+ * @defaultValue `'BTC'`
14
+ */
15
+ base?: string;
16
+ /**
17
+ * Quote currency: what to price `base` in. Omit for every rate against
18
+ * `base`.
19
+ */
20
+ quote?: string;
21
+ };
22
+ /**
23
+ * Fetch BitPay exchange rates.
24
+ *
25
+ * @param query - `{ base, quote }`. Omit `quote` for the full table, omit both for BTC.
26
+ * @returns Every rate for `base` when `quote` is omitted, otherwise that single {@link RateObj}.
27
+ * @throws `TypeError` if a code is not 2-10 alphanumeric characters, before any request is made.
28
+ *
29
+ * @example
30
+ * await get(); // every rate against BTC
31
+ * await get({ base: 'ETH' }); // every rate against ETH
32
+ * await get({ quote: 'USD' }); // BTC/USD
33
+ * await get({ base: 'ETH', quote: 'USD' }); // ETH/USD
34
+ */
35
+ export declare function get(): Promise<RateObj[]>;
36
+ export declare function get(query: {
37
+ base?: string;
38
+ quote?: undefined;
39
+ }): Promise<RateObj[]>;
40
+ export declare function get(query: {
41
+ base?: string;
42
+ quote: string;
43
+ }): Promise<RateObj>;
44
+ /**
45
+ * Namespace object, so both import styles work in ESM and CommonJS:
46
+ * `import { get }` / `const { get } = require(...)`, and
47
+ * `import bitpayRates` / `const bitpayRates = require(...)` + `bitpayRates.get()`.
48
+ */
49
+ declare const _default: {
50
+ get: typeof get;
51
+ };
52
+ //#endregion
53
+ export { _default as default };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ const e=`https://bitpay.com/rates`,t=1e4,n=/^[A-Z0-9]{2,10}$/,r={"X-Accept-Version":`2.0.0`,Accept:`application/json`,"User-Agent":`bitpay-rates`};async function i(n={}){let{quote:r}=n,i=a(n.base??`BTC`,`base`),s=r===void 0,c=s?`${e}/${i}`:`${e}/${i}/${a(r,`quote`)}`,l=new AbortController,u,d=new Promise((e,n)=>{u=setTimeout(()=>{let e=Error(`Request to ${c} timed out after ${t}ms`);l.abort(e),n(e)},t)});try{return await Promise.race([o(c,l.signal,s),d])}finally{u!==void 0&&clearTimeout(u)}}function a(e,t){let r=e.toUpperCase();if(!n.test(r))throw TypeError(`Invalid ${t} currency code: ${JSON.stringify(e)}`);return r}async function o(e,t,n){let i=await fetch(e,{headers:r,signal:t}),a=await i.text(),o;try{o=JSON.parse(a)}catch(t){throw i.ok?t:Error(`Request to ${e} failed with HTTP ${i.status}`)}if(s(o)&&o.error!=null)throw Error(String(o.error));if(!i.ok)throw Error(`Request to ${e} failed with HTTP ${i.status}`);let l=s(o)?o.data:void 0;if(n){if(!Array.isArray(l)||!l.every(c))throw Error(`Unexpected response from ${e}: expected a list of rates`)}else if(!c(l))throw Error(`Unexpected response from ${e}: expected a single rate`);return l}function s(e){return typeof e==`object`&&!!e}function c(e){return s(e)&&typeof e.code==`string`&&typeof e.name==`string`&&typeof e.rate==`number`}var l={get:i};export{l as default,i as get};
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "bitpay-rates",
3
- "version": "1.2.18",
3
+ "version": "3.0.0",
4
4
  "description": "A tiny Node.js wrapper for the BitPay Rates API",
5
5
  "engines": {
6
- "node": ">=12"
6
+ "node": ">=22"
7
7
  },
8
8
  "keywords": [
9
9
  "bitcoin",
@@ -12,10 +12,23 @@
12
12
  "countries",
13
13
  "price"
14
14
  ],
15
- "author": "Mario Colque <dev@colkito.com>",
15
+ "author": "colkito <dev@colkito.com>",
16
16
  "license": "MIT",
17
- "main": "dist/index.js",
18
- "types": "dist/index.d.ts",
17
+ "type": "commonjs",
18
+ "main": "./dist/index.cjs",
19
+ "types": "./dist/index.d.cts",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/index.d.mts",
24
+ "default": "./dist/index.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/index.d.cts",
28
+ "default": "./dist/index.cjs"
29
+ }
30
+ }
31
+ },
19
32
  "repository": {
20
33
  "type": "git",
21
34
  "url": "https://github.com/colkito/bitpay-rates.git"
@@ -25,38 +38,40 @@
25
38
  },
26
39
  "homepage": "https://github.com/colkito/bitpay-rates#readme",
27
40
  "scripts": {
28
- "test": "jest",
29
- "test:watch": "jest --watch",
30
- "test:coverage": "jest --coverage",
31
- "build": "tsc --declaration && tsc --removeComments && npm run minifyDeclarations && npm run minify",
32
- "format": "prettier --write \"src/**/*.ts\"",
33
- "lint": "tsc --noEmit && eslint '*/**/*.{js,ts}' --quiet --fix",
34
- "minify": "jsmin -l 3 -o dist/index.js dist/index.js",
35
- "minifyDeclarations": "jsmin -l 3 -o dist/index.d.ts dist/index.d.ts",
36
- "prepublishOnly": "npm test && npm run lint && npm run build",
37
- "preversion": "npm run lint",
38
- "version": "npm run format && git add -A src package.json",
39
- "postversion": "git push && git push --tags"
41
+ "verify": "npm run lint && npm run knip && npm test && npm run build && npm run smoke",
42
+ "test": "node --test src/index.test.mts scripts/codes-markdown.test.mts scripts/guard-protected-refs.test.mts",
43
+ "test:watch": "node --test --watch src/index.test.mts scripts/codes-markdown.test.mts scripts/guard-protected-refs.test.mts",
44
+ "test:coverage": "node --test --experimental-test-coverage src/index.test.mts scripts/codes-markdown.test.mts scripts/guard-protected-refs.test.mts",
45
+ "lint": "tsc --noEmit && biome check",
46
+ "format": "biome check --write",
47
+ "knip": "knip",
48
+ "build": "tsdown",
49
+ "smoke": "node scripts/smoke.mts",
50
+ "update-codes": "node scripts/update-codes.mts",
51
+ "prepare": "node scripts/install-git-hooks.mjs",
52
+ "prepublishOnly": "npm run build && npm run smoke"
40
53
  },
41
- "devDependencies": {
42
- "@types/jest": "^26.0.21",
43
- "@types/node": "^14.14.37",
44
- "@typescript-eslint/eslint-plugin": "^4.18.0",
45
- "@typescript-eslint/parser": "^4.18.0",
46
- "eslint": "^7.21.0",
47
- "eslint-config-prettier": "^8.1.0",
48
- "eslint-plugin-prettier": "^3.3.1",
49
- "jest": "^26.6.3",
50
- "jsmin": "^1.0.1",
51
- "nock": "^13.0.11",
52
- "prettier": "^2.2.1",
53
- "ts-jest": "^26.5.4",
54
- "typescript": "^4.2.3"
54
+ "knip": {
55
+ "ignore": [
56
+ "example/**"
57
+ ]
55
58
  },
56
- "resolutions": {
57
- "json-schema": "^0.4.0"
59
+ "devDependencies": {
60
+ "@biomejs/biome": "2.5.13",
61
+ "@types/node": "26.4.1",
62
+ "knip": "6.35.1",
63
+ "tsdown": "0.23.0",
64
+ "typescript": "7.0.2"
58
65
  },
66
+ "sideEffects": false,
59
67
  "files": [
60
- "dist/**/*"
61
- ]
68
+ "dist"
69
+ ],
70
+ "devEngines": {
71
+ "runtime": {
72
+ "name": "node",
73
+ "version": ">=22.18.0",
74
+ "onFail": "error"
75
+ }
76
+ }
62
77
  }
package/dist/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export type RateObj={code:string;name:string;rate:number;};export type RateResponse=RateObj|[RateObj];export type Callback=(error:Error|null,data?:RateResponse)=>void;export declare const get:(code?:string|Callback,callback?:Callback)=>Promise<RateResponse>|void;declare const _default:{get:(code?:string|Callback|undefined,callback?:Callback|undefined)=>void|Promise<RateResponse>;};export default _default;
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=(this&&this.__importDefault)||function(mod){return(mod&&mod.__esModule)?mod:{"default":mod};};Object.defineProperty(exports,"__esModule",{value:true});exports.get=void 0;const https_1=__importDefault(require("https"));const defaultOptions={host:'bitpay.com',path:'/rates',headers:{},agent:false,};const returnPromise=(options)=>{return new Promise((resolve,reject)=>{returnCallback(options,(err,data)=>{if(err)return reject(err);return resolve(data);});});};const returnCallback=(options,callback)=>{https_1.default.get(options,(res)=>{let dataBuffer='';res.on('data',(chunk)=>{dataBuffer+=chunk.toString('utf8');});res.on('end',()=>{try{const{data}=JSON.parse(dataBuffer);return callback(null,data);}catch(err){return callback(err);}});}).on('error',(err)=>{return callback(err);});};const get=(code,callback)=>{const options={...defaultOptions};if(typeof code==='string'){options.path+=`/${code.toUpperCase()}`;}if(typeof code==='function'){return returnCallback(options,code);}else if(callback){return returnCallback(options,callback);}else{return returnPromise(options);}};exports.get=get;exports.default={get:exports.get};