x402-crypto-derivatives-data 0.0.0-stage → 0.3.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 +21 -0
- package/README.md +74 -2
- package/dist/index.cjs +313 -0
- package/dist/index.d.cts +1032 -0
- package/dist/index.d.ts +1032 -0
- package/dist/index.js +271 -0
- package/package.json +96 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Duo Data Utilities
|
|
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,3 +1,75 @@
|
|
|
1
|
-
#
|
|
1
|
+
# x402-crypto-derivatives-data
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Crypto derivatives data API for AI agents and trading bots: perp funding rates, funding rate history, funding arbitrage, a perp screener, open interest, futures basis, implied volatility, liquidations and order book depth across Bybit, Hyperliquid, dYdX, Gate, Kraken, Bitfinex, Deribit and OKX. $0.003 or $0.005 per call, paid per request with [x402](https://x402.org) (USDC on Base). No API key, no signup.
|
|
4
|
+
|
|
5
|
+
This is the TypeScript client for https://derivs.duoleads.com. It is also part of [`duo-data-utilities`](https://www.npmjs.com/package/duo-data-utilities) (as `client.derivs`); this package has the derivatives client only.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install x402-crypto-derivatives-data viem
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`viem` creates the wallet account that pays. Node.js 20 or later.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
19
|
+
import { createClient } from "x402-crypto-derivatives-data";
|
|
20
|
+
|
|
21
|
+
// Your wallet holds USDC on Base. Read the key from the environment, never a literal.
|
|
22
|
+
const client = createClient({ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`) });
|
|
23
|
+
|
|
24
|
+
const funding = await client.fundingRates({ coin: "BTC" }); // $0.003
|
|
25
|
+
const arb = await client.fundingArbitrage({ min_apr: 10, limit: 20 }); // $0.005
|
|
26
|
+
const oi = await client.openInterest({ coin: "ETH" }); // $0.003
|
|
27
|
+
const snapshot = await client.derivativesSnapshot({ coin: "SOL" }); // $0.005
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Each method sends one GET, pays the 402 challenge with your wallet and resolves to the JSON answer. `client.callWithReceipt(path, params)` also returns the settlement receipt (transaction hash).
|
|
31
|
+
|
|
32
|
+
## Price guard
|
|
33
|
+
|
|
34
|
+
Before anything is signed, the client checks the payment requirement: Base USDC only, only to the service's address `0xCD673f7e1daD254347AEab6be0DEFe8b9C6b3A39`, and never above the cap (default $0.005; `maxPriceUsd: 0.003` refuses the $0.005 routes). A refused call signs nothing and throws `PriceGuardError`. The client never retries a paid call on its own.
|
|
35
|
+
|
|
36
|
+
## Errors and charges
|
|
37
|
+
|
|
38
|
+
A bad input is a free 400, an unknown coin a free 404, and too few venues answering a 503 that is not charged. Errors throw `DuoApiError` with `code`, `param`, `expected` and `requestId`.
|
|
39
|
+
|
|
40
|
+
## Routes
|
|
41
|
+
|
|
42
|
+
<!-- routes:derivs:client. -->
|
|
43
|
+
| Method | Route | Price | MCP tool | What it returns |
|
|
44
|
+
|---|---|---|---|---|
|
|
45
|
+
| `client.fundingRates` | `GET /v1/funding-rates` | $0.003 | `perp_funding_rates` | Perp funding rates by exchange (one coin) |
|
|
46
|
+
| `client.fundingHistory` | `GET /v1/funding-history` | $0.003 | `funding_rate_history` | Funding rate history (one coin, one venue, up to 30 days) |
|
|
47
|
+
| `client.fundingArbitrage` | `GET /v1/funding-arbitrage` | $0.005 | `funding_arbitrage_scan` | Funding arbitrage scanner (all coins) |
|
|
48
|
+
| `client.perpScreener` | `GET /v1/perp-screener` | $0.005 | `perp_screener` | Perp screener (every perpetual, sorted) |
|
|
49
|
+
| `client.openInterest` | `GET /v1/open-interest` | $0.003 | `open_interest` | Open interest by exchange (one coin) |
|
|
50
|
+
| `client.basis` | `GET /v1/basis` | $0.003 | `futures_basis` | Futures basis, term structure and perp premium (one coin) |
|
|
51
|
+
| `client.options` | `GET /v1/options` | $0.003 | `implied_volatility` | Implied volatility, skew and max pain (crypto options) |
|
|
52
|
+
| `client.liquidations` | `GET /v1/liquidations` | $0.003 | `liquidations` | Liquidations (one coin, last 1-24 h) |
|
|
53
|
+
| `client.orderbook` | `GET /v1/orderbook` | $0.003 | `order_book_depth` | Order book depth and slippage (one coin, 8 venues) |
|
|
54
|
+
| `client.derivativesSnapshot` | `GET /v1/derivatives-snapshot` | $0.005 | `derivatives_snapshot` | Crypto derivatives snapshot (one coin, one call) |
|
|
55
|
+
<!-- /routes -->
|
|
56
|
+
|
|
57
|
+
`DERIVS_ROUTES` holds this table as data (path, MCP tool, price).
|
|
58
|
+
|
|
59
|
+
## MCP and plain HTTP
|
|
60
|
+
|
|
61
|
+
The same 10 tools are a paid MCP server at `https://derivs.duoleads.com/mcp` (Streamable HTTP; `tools/list` is free). Every route also works without this package: an unpaid request returns `402 Payment Required` with a `PAYMENT-REQUIRED` header, and any x402 v2 client can pay it.
|
|
62
|
+
|
|
63
|
+
## Links
|
|
64
|
+
|
|
65
|
+
- Docs: https://derivs.duoleads.com/docs
|
|
66
|
+
- OpenAPI: https://derivs.duoleads.com/openapi.json
|
|
67
|
+
- Machine-readable index: https://derivs.duoleads.com/llms.txt
|
|
68
|
+
- Terms: https://derivs.duoleads.com/terms
|
|
69
|
+
- Privacy: https://derivs.duoleads.com/privacy
|
|
70
|
+
|
|
71
|
+
Data only: not investment advice, not a benchmark, not affiliated with any exchange.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT. See [LICENSE](LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BASE_NETWORK: () => BASE_NETWORK,
|
|
24
|
+
BASE_USDC_ADDRESS: () => BASE_USDC_ADDRESS,
|
|
25
|
+
DEFAULT_DERIVS_BASE_URL: () => DEFAULT_DERIVS_BASE_URL,
|
|
26
|
+
DEFAULT_DERIVS_MAX_PRICE_USD: () => DEFAULT_DERIVS_MAX_PRICE_USD,
|
|
27
|
+
DERIVS_MCP_URL: () => DERIVS_MCP_URL,
|
|
28
|
+
DERIVS_ROUTES: () => DERIVS_ROUTES,
|
|
29
|
+
DuoApiError: () => DuoApiError,
|
|
30
|
+
PriceGuardError: () => PriceGuardError,
|
|
31
|
+
SDK_USER_AGENT: () => SDK_USER_AGENT,
|
|
32
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
33
|
+
assertDerivsPath: () => assertDerivsPath,
|
|
34
|
+
buildDerivsQuery: () => buildDerivsQuery,
|
|
35
|
+
createClient: () => createDerivsClient,
|
|
36
|
+
createDerivsClient: () => createDerivsClient,
|
|
37
|
+
createDerivsGeneratedClient: () => createDerivsGeneratedClient,
|
|
38
|
+
derivsNumber: () => derivsNumber
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// ../../src/derivs-generated.ts
|
|
43
|
+
var DERIVS_ROUTES = [
|
|
44
|
+
{ path: "/v1/funding-rates", tool: "perp_funding_rates", method: "fundingRates", priceUsd: "0.003", amount: "3000" },
|
|
45
|
+
{ path: "/v1/funding-history", tool: "funding_rate_history", method: "fundingHistory", priceUsd: "0.003", amount: "3000" },
|
|
46
|
+
{ path: "/v1/funding-arbitrage", tool: "funding_arbitrage_scan", method: "fundingArbitrage", priceUsd: "0.005", amount: "5000" },
|
|
47
|
+
{ path: "/v1/perp-screener", tool: "perp_screener", method: "perpScreener", priceUsd: "0.005", amount: "5000" },
|
|
48
|
+
{ path: "/v1/open-interest", tool: "open_interest", method: "openInterest", priceUsd: "0.003", amount: "3000" },
|
|
49
|
+
{ path: "/v1/basis", tool: "futures_basis", method: "basis", priceUsd: "0.003", amount: "3000" },
|
|
50
|
+
{ path: "/v1/options", tool: "implied_volatility", method: "options", priceUsd: "0.003", amount: "3000" },
|
|
51
|
+
{ path: "/v1/liquidations", tool: "liquidations", method: "liquidations", priceUsd: "0.003", amount: "3000" },
|
|
52
|
+
{ path: "/v1/orderbook", tool: "order_book_depth", method: "orderbook", priceUsd: "0.003", amount: "3000" },
|
|
53
|
+
{ path: "/v1/derivatives-snapshot", tool: "derivatives_snapshot", method: "derivativesSnapshot", priceUsd: "0.005", amount: "5000" }
|
|
54
|
+
];
|
|
55
|
+
function createDerivsGeneratedClient(call) {
|
|
56
|
+
return {
|
|
57
|
+
fundingRates: (params) => call("/v1/funding-rates", params ?? {}),
|
|
58
|
+
fundingHistory: (params) => call("/v1/funding-history", params ?? {}),
|
|
59
|
+
fundingArbitrage: (params) => call("/v1/funding-arbitrage", params ?? {}),
|
|
60
|
+
perpScreener: (params) => call("/v1/perp-screener", params ?? {}),
|
|
61
|
+
openInterest: (params) => call("/v1/open-interest", params ?? {}),
|
|
62
|
+
basis: (params) => call("/v1/basis", params ?? {}),
|
|
63
|
+
options: (params) => call("/v1/options", params ?? {}),
|
|
64
|
+
liquidations: (params) => call("/v1/liquidations", params ?? {}),
|
|
65
|
+
orderbook: (params) => call("/v1/orderbook", params ?? {}),
|
|
66
|
+
derivativesSnapshot: (params) => call("/v1/derivatives-snapshot", params ?? {})
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ../../src/errors.ts
|
|
71
|
+
var DuoApiError = class extends Error {
|
|
72
|
+
/** Error code from the envelope, e.g. `INVALID_PARAM`, or `HTTP_<status>` when there is no envelope. */
|
|
73
|
+
code;
|
|
74
|
+
/** The offending query parameter, or `null`. */
|
|
75
|
+
param;
|
|
76
|
+
/** The accepted form, or `null`. */
|
|
77
|
+
expected;
|
|
78
|
+
/** HTTP status of the response. */
|
|
79
|
+
status;
|
|
80
|
+
/** Route path that was called, e.g. `/v1/id/iban`. */
|
|
81
|
+
route;
|
|
82
|
+
/** Request id from the envelope's `meta`, when present. */
|
|
83
|
+
requestId;
|
|
84
|
+
constructor(init) {
|
|
85
|
+
super(init.message);
|
|
86
|
+
this.name = "DuoApiError";
|
|
87
|
+
this.code = init.code;
|
|
88
|
+
this.param = init.param ?? null;
|
|
89
|
+
this.expected = init.expected ?? null;
|
|
90
|
+
this.status = init.status;
|
|
91
|
+
this.route = init.route;
|
|
92
|
+
this.requestId = init.requestId ?? null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var PriceGuardError = class extends Error {
|
|
96
|
+
/** One entry per refused requirement, e.g. `amount 60000 atomic units ($0.06) is above the maximum $0.05`. */
|
|
97
|
+
reasons;
|
|
98
|
+
/** The per-call maximum in effect, in USD. */
|
|
99
|
+
maxPriceUsd;
|
|
100
|
+
constructor(reasons, maxPriceUsd) {
|
|
101
|
+
super(`Refused to pay: ${reasons.join("; ")}. Nothing was signed (maximum $${maxPriceUsd} per call).`);
|
|
102
|
+
this.name = "PriceGuardError";
|
|
103
|
+
this.reasons = reasons;
|
|
104
|
+
this.maxPriceUsd = maxPriceUsd;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ../../src/price-guard.ts
|
|
109
|
+
var import_fetch = require("@x402/fetch");
|
|
110
|
+
var BASE_NETWORK = "eip155:8453";
|
|
111
|
+
var BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
112
|
+
var DEFAULT_PAY_TO = "0xCD673f7e1daD254347AEab6be0DEFe8b9C6b3A39";
|
|
113
|
+
function usdToAtomic(usd) {
|
|
114
|
+
if (!Number.isFinite(usd) || usd <= 0) throw new RangeError(`maxPriceUsd must be a positive number, got ${usd}`);
|
|
115
|
+
const atomic = BigInt(Math.floor(usd * 1e6 + 1e-6));
|
|
116
|
+
if (atomic < 1n) throw new RangeError("maxPriceUsd must be at least 0.000001 (one USDC base unit)");
|
|
117
|
+
return atomic;
|
|
118
|
+
}
|
|
119
|
+
function formatUsd(atomic) {
|
|
120
|
+
const whole = atomic / 1000000n;
|
|
121
|
+
const fraction = (atomic % 1000000n).toString().padStart(6, "0").replace(/0+$/, "").padEnd(2, "0");
|
|
122
|
+
return `$${whole}.${fraction}`;
|
|
123
|
+
}
|
|
124
|
+
function requirementProblem(requirement, maxAtomic, payTo = DEFAULT_PAY_TO) {
|
|
125
|
+
if (requirement.scheme !== "exact") return `scheme ${JSON.stringify(requirement.scheme ?? null)} is not "exact"`;
|
|
126
|
+
if (requirement.network !== BASE_NETWORK) {
|
|
127
|
+
return `network ${JSON.stringify(requirement.network ?? null)} is not ${BASE_NETWORK} (Base)`;
|
|
128
|
+
}
|
|
129
|
+
if (typeof requirement.asset !== "string" || requirement.asset.toLowerCase() !== BASE_USDC_ADDRESS.toLowerCase()) {
|
|
130
|
+
return `asset ${JSON.stringify(requirement.asset ?? null)} is not Base USDC (${BASE_USDC_ADDRESS})`;
|
|
131
|
+
}
|
|
132
|
+
if (typeof requirement.amount !== "string" || !/^[0-9]+$/.test(requirement.amount)) {
|
|
133
|
+
return `amount ${JSON.stringify(requirement.amount ?? null)} is not an integer in atomic USDC units`;
|
|
134
|
+
}
|
|
135
|
+
if (payTo !== null && (typeof requirement.payTo !== "string" || requirement.payTo.toLowerCase() !== payTo.toLowerCase())) {
|
|
136
|
+
return `payTo ${JSON.stringify(requirement.payTo ?? null)} is not the expected payee ${payTo}`;
|
|
137
|
+
}
|
|
138
|
+
const amount = BigInt(requirement.amount);
|
|
139
|
+
if (amount === 0n) return "amount is zero";
|
|
140
|
+
if (amount > maxAtomic) {
|
|
141
|
+
return `amount ${requirement.amount} atomic units (${formatUsd(amount)}) is above the maximum ${formatUsd(maxAtomic)}`;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
var decoder = new import_fetch.x402HTTPClient(new import_fetch.x402Client());
|
|
146
|
+
function guardChallenges(transport, maxAtomic, maxPriceUsd, payTo = DEFAULT_PAY_TO) {
|
|
147
|
+
return async (input, init) => {
|
|
148
|
+
const request = new Request(input, init);
|
|
149
|
+
const response = await transport(request);
|
|
150
|
+
const isPaidRetry = request.headers.has("payment-signature") || request.headers.has("x-payment");
|
|
151
|
+
if (response.status !== 402 || isPaidRetry) return response;
|
|
152
|
+
let required;
|
|
153
|
+
try {
|
|
154
|
+
let body;
|
|
155
|
+
if (!response.headers.get("PAYMENT-REQUIRED")) body = await response.clone().json().catch(() => void 0);
|
|
156
|
+
required = decoder.getPaymentRequiredResponse((name) => response.headers.get(name), body);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
159
|
+
throw new PriceGuardError([`the payment requirements could not be read: ${reason}`], maxPriceUsd);
|
|
160
|
+
}
|
|
161
|
+
const reasons = [];
|
|
162
|
+
if (required.x402Version !== 2) reasons.push(`x402Version ${String(required.x402Version)} is not 2`);
|
|
163
|
+
const options = Array.isArray(required.accepts) ? required.accepts : [];
|
|
164
|
+
const problems = options.map((option) => requirementProblem(option, maxAtomic, payTo));
|
|
165
|
+
if (options.length === 0) reasons.push("the challenge offers no payment option");
|
|
166
|
+
else if (problems.every((problem) => problem !== null)) reasons.push(...problems);
|
|
167
|
+
if (reasons.length > 0) throw new PriceGuardError(reasons, maxPriceUsd);
|
|
168
|
+
return response;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function guardPolicy(maxAtomic, payTo = DEFAULT_PAY_TO) {
|
|
172
|
+
return (_version, requirements) => requirements.filter((requirement) => requirementProblem(requirement, maxAtomic, payTo) === null);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ../../src/transport.ts
|
|
176
|
+
var import_fetch2 = require("@x402/fetch");
|
|
177
|
+
var import_client = require("@x402/evm/exact/client");
|
|
178
|
+
function canSetUserAgent() {
|
|
179
|
+
const scope = globalThis;
|
|
180
|
+
return typeof scope.document === "undefined" && typeof scope.WorkerGlobalScope === "undefined";
|
|
181
|
+
}
|
|
182
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
183
|
+
function normaliseBaseUrl(raw) {
|
|
184
|
+
const url = new URL(raw);
|
|
185
|
+
const plainLoopback = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
|
|
186
|
+
if (url.protocol !== "https:" && !plainLoopback) {
|
|
187
|
+
throw new TypeError(`baseUrl must use https (plain http is only allowed for localhost): ${raw}`);
|
|
188
|
+
}
|
|
189
|
+
if (url.search || url.hash) throw new TypeError("baseUrl must not contain a query or a fragment");
|
|
190
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
191
|
+
}
|
|
192
|
+
function buildPayingFetch(account, maxAtomic, maxPriceUsd, payTo) {
|
|
193
|
+
return (0, import_fetch2.wrapFetchWithPaymentFromConfig)(guardChallenges(globalThis.fetch, maxAtomic, maxPriceUsd, payTo), {
|
|
194
|
+
schemes: [{ network: BASE_NETWORK, client: new import_client.ExactEvmScheme(account) }],
|
|
195
|
+
policies: [guardPolicy(maxAtomic, payTo)],
|
|
196
|
+
// A second, independent cap in the payment layer, in USD for the default USDC asset.
|
|
197
|
+
spendControls: { maxAmountPerPayment: maxPriceUsd }
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
201
|
+
function parseJson(text) {
|
|
202
|
+
try {
|
|
203
|
+
return JSON.parse(text);
|
|
204
|
+
} catch {
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function receiptOf(response) {
|
|
209
|
+
const header = response.headers.get("payment-response");
|
|
210
|
+
if (!header) return null;
|
|
211
|
+
try {
|
|
212
|
+
return (0, import_fetch2.decodePaymentResponseHeader)(header);
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ../../src/version.ts
|
|
219
|
+
var SDK_VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
220
|
+
var SDK_USER_AGENT = `${true ? "x402-crypto-derivatives-data" : "duo-data-utilities"}-js/${SDK_VERSION} (+https://github.com/hiesem/duo-data-utilities)`;
|
|
221
|
+
|
|
222
|
+
// ../../src/derivs.ts
|
|
223
|
+
var DEFAULT_DERIVS_BASE_URL = "https://derivs.duoleads.com";
|
|
224
|
+
var DEFAULT_DERIVS_MAX_PRICE_USD = 5e-3;
|
|
225
|
+
var DERIVS_MCP_URL = "https://derivs.duoleads.com/mcp";
|
|
226
|
+
var DERIVS_PATH = /^\/v1\/[a-z0-9-]+$/;
|
|
227
|
+
function assertDerivsPath(path) {
|
|
228
|
+
if (!DERIVS_PATH.test(path)) throw new TypeError(`not a derivs route path: ${JSON.stringify(path)} (expected /v1/<name>)`);
|
|
229
|
+
}
|
|
230
|
+
function derivsNumber(name, value) {
|
|
231
|
+
if (!Number.isFinite(value)) throw new TypeError(`parameter ${name} must be a finite number`);
|
|
232
|
+
if (Number.isInteger(value)) return BigInt(value).toString();
|
|
233
|
+
return value.toFixed(8).replace(/0+$/, "").replace(/\.$/, "");
|
|
234
|
+
}
|
|
235
|
+
function buildDerivsQuery(params) {
|
|
236
|
+
const search = new URLSearchParams();
|
|
237
|
+
for (const [key, value] of Object.entries(params)) {
|
|
238
|
+
if (value === void 0 || value === null) continue;
|
|
239
|
+
if (typeof value === "number") search.append(key, derivsNumber(key, value));
|
|
240
|
+
else if (typeof value === "string" || typeof value === "boolean") search.append(key, String(value));
|
|
241
|
+
else throw new TypeError(`parameter ${key} must be a string, number or boolean`);
|
|
242
|
+
}
|
|
243
|
+
return search.toString();
|
|
244
|
+
}
|
|
245
|
+
function derivsError(path, status, ok, body) {
|
|
246
|
+
const requestId = isRecord(body) && typeof body.request_id === "string" ? body.request_id : null;
|
|
247
|
+
if (isRecord(body) && isRecord(body.error)) {
|
|
248
|
+
const error = body.error;
|
|
249
|
+
return new DuoApiError({
|
|
250
|
+
code: typeof error.code === "string" ? error.code : "UNKNOWN_ERROR",
|
|
251
|
+
message: typeof error.message === "string" ? error.message : `${path} failed with HTTP ${status}`,
|
|
252
|
+
param: typeof error.param === "string" ? error.param : null,
|
|
253
|
+
expected: typeof error.expected === "string" ? error.expected : null,
|
|
254
|
+
status,
|
|
255
|
+
route: path,
|
|
256
|
+
requestId
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (status === 402) {
|
|
260
|
+
return new DuoApiError({ code: "PAYMENT_REQUIRED", message: "the payment was not accepted (HTTP 402)", status, route: path, requestId });
|
|
261
|
+
}
|
|
262
|
+
if (ok) {
|
|
263
|
+
return new DuoApiError({ code: "INVALID_RESPONSE", message: `${path} did not return a JSON object (HTTP ${status})`, status, route: path, requestId });
|
|
264
|
+
}
|
|
265
|
+
return new DuoApiError({ code: `HTTP_${status}`, message: `${path} failed with HTTP ${status}`, status, route: path, requestId });
|
|
266
|
+
}
|
|
267
|
+
function createDerivsClient(options = {}) {
|
|
268
|
+
if (options.account !== void 0 === (options.fetch !== void 0)) {
|
|
269
|
+
throw new TypeError("createDerivsClient needs exactly one of `account` (pays with your wallet) or `fetch` (a fetch that already pays)");
|
|
270
|
+
}
|
|
271
|
+
const baseUrl = normaliseBaseUrl(options.baseUrl ?? DEFAULT_DERIVS_BASE_URL);
|
|
272
|
+
const maxPriceUsd = options.maxPriceUsd ?? DEFAULT_DERIVS_MAX_PRICE_USD;
|
|
273
|
+
const userAgent = options.userAgent ?? SDK_USER_AGENT;
|
|
274
|
+
if (/[\r\n]/.test(userAgent)) throw new TypeError("userAgent must not contain line breaks");
|
|
275
|
+
const maxAtomic = usdToAtomic(maxPriceUsd);
|
|
276
|
+
const payTo = options.payTo === void 0 ? DEFAULT_PAY_TO : options.payTo;
|
|
277
|
+
const payingFetch = options.account !== void 0 ? buildPayingFetch(options.account, maxAtomic, maxPriceUsd, payTo) : options.fetch;
|
|
278
|
+
const callPaid = async (path, params = {}) => {
|
|
279
|
+
assertDerivsPath(path);
|
|
280
|
+
const query = buildDerivsQuery(params);
|
|
281
|
+
const headers = { accept: "application/json" };
|
|
282
|
+
if (canSetUserAgent() && userAgent) headers["user-agent"] = userAgent;
|
|
283
|
+
const response = await payingFetch(`${baseUrl}${path}${query ? `?${query}` : ""}`, { method: "GET", headers });
|
|
284
|
+
const body = parseJson(await response.text());
|
|
285
|
+
if (response.ok && isRecord(body) && !("error" in body)) return { data: body, receipt: receiptOf(response) };
|
|
286
|
+
throw derivsError(path, response.status, response.ok, body);
|
|
287
|
+
};
|
|
288
|
+
const call = async (path, params) => (await callPaid(path, params)).data;
|
|
289
|
+
return {
|
|
290
|
+
...createDerivsGeneratedClient(call),
|
|
291
|
+
call: (path, params) => call(path, params ?? {}),
|
|
292
|
+
callWithReceipt: (path, params) => callPaid(path, params ?? {})
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
296
|
+
0 && (module.exports = {
|
|
297
|
+
BASE_NETWORK,
|
|
298
|
+
BASE_USDC_ADDRESS,
|
|
299
|
+
DEFAULT_DERIVS_BASE_URL,
|
|
300
|
+
DEFAULT_DERIVS_MAX_PRICE_USD,
|
|
301
|
+
DERIVS_MCP_URL,
|
|
302
|
+
DERIVS_ROUTES,
|
|
303
|
+
DuoApiError,
|
|
304
|
+
PriceGuardError,
|
|
305
|
+
SDK_USER_AGENT,
|
|
306
|
+
SDK_VERSION,
|
|
307
|
+
assertDerivsPath,
|
|
308
|
+
buildDerivsQuery,
|
|
309
|
+
createClient,
|
|
310
|
+
createDerivsClient,
|
|
311
|
+
createDerivsGeneratedClient,
|
|
312
|
+
derivsNumber
|
|
313
|
+
});
|