-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathclient.js
More file actions
141 lines (116 loc) · 4.39 KB
/
Copy pathclient.js
File metadata and controls
141 lines (116 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Zerion API HTTP client — native fetch + Basic Auth + x402 pay-per-call.
*/
import { API_BASE } from "../util/constants.js";
import { getApiKey } from "../config.js";
import { getX402Fetch } from "./x402.js";
export function basicAuthHeader(key) {
return `Basic ${Buffer.from(`${key}:`).toString("base64")}`;
}
export async function fetchAPI(pathname, params = {}, useX402 = false, isRetry = false) {
const apiKey = useX402 ? null : getApiKey();
if (!useX402 && !apiKey) {
const err = new Error(
"ZERION_API_KEY is required. Get one at https://developers.zerion.io\n" +
"Alternatively, use --x402 for pay-per-call (no API key needed)."
);
err.code = "missing_api_key";
throw err;
}
const url = new URL(`${API_BASE}${pathname}`);
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null || value === "") continue;
url.searchParams.set(key, String(value));
}
const headers = { Accept: "application/json" };
if (!useX402) {
headers.Authorization = basicAuthHeader(apiKey);
}
const fetchFn = useX402 ? await getX402Fetch() : fetch;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 30_000);
const response = await fetchFn(url, { headers, signal: controller.signal });
clearTimeout(timer);
const text = await response.text();
let payload;
try {
payload = text ? JSON.parse(text) : null;
} catch {
payload = { _rawText: text.slice(0, 500) };
}
if (!response.ok) {
// Hybrid fallback: If rate-limited or quota exceeded on standard API and we have a wallet key, fallback to x402
if ([401, 403, 429].includes(response.status) && !useX402 && !isRetry && process.env.WALLET_PRIVATE_KEY) {
process.stderr.write(`\\x1b[33m⚠ API error (${response.status}). Falling back to x402 pay-per-call for ${pathname}...\\x1b[0m\\n`);
return fetchAPI(pathname, params, true, true);
}
const err = new Error(
`Zerion API error: ${response.status} ${response.statusText}`
);
err.code = "api_error";
err.status = response.status;
err.response = payload;
throw err;
}
return payload;
}
// --- Wallet endpoints ---
export async function getPortfolio(address, options = {}) {
return fetchAPI(`/wallets/${encodeURIComponent(address)}/portfolio`, {
currency: options.currency || "usd",
}, options.useX402);
}
export async function getPositions(address, options = {}) {
const params = {
"filter[positions]": options.positionFilter || "no_filter",
currency: "usd",
sort: "value",
};
if (options.chainId) params["filter[chain_ids]"] = options.chainId;
return fetchAPI(`/wallets/${encodeURIComponent(address)}/positions/`, params, options.useX402);
}
export async function getPnl(address, options = {}) {
return fetchAPI(`/wallets/${encodeURIComponent(address)}/pnl`, {}, options.useX402);
}
export async function getTransactions(address, options = {}) {
const params = {
"page[size]": options.limit || 10,
currency: "usd",
};
if (options.chainId) params["filter[chain_ids]"] = options.chainId;
return fetchAPI(`/wallets/${encodeURIComponent(address)}/transactions/`, params, options.useX402);
}
// --- Fungibles endpoints ---
export async function searchFungibles(query, options = {}) {
const params = {
"filter[search_query]": query,
currency: "usd",
sort: "-market_data.market_cap",
"page[size]": options.limit || 10,
};
if (options.chainId) params["filter[chain_ids]"] = options.chainId;
return fetchAPI("/fungibles/", params, options.useX402);
}
export async function getFungible(fungibleId, options = {}) {
return fetchAPI(`/fungibles/${fungibleId}`, {}, options.useX402);
}
// --- Chain endpoints ---
export async function getChains(options = {}) {
return fetchAPI("/chains/", {}, options.useX402);
}
export async function getGasPrices(chainId, options = {}) {
return fetchAPI("/gas/", {
"filter[chain_id]": chainId || "ethereum",
}, options.useX402);
}
// --- Swap endpoints ---
export async function getSwapOffers(params, options = {}) {
return fetchAPI("/swap/offers/", params, options.useX402);
}
export async function getSwapFungibles(inputChainId, outputChainId, options = {}) {
return fetchAPI("/swap/fungibles/", {
"input[chain_id]": inputChainId || "ethereum",
"output[chain_id]": outputChainId || "ethereum",
direction: "both",
}, options.useX402);
}