chia-explorer 0.1.0 → 0.1.3
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/README.md +8 -1
- package/dist/coingecko/cache.d.ts +6 -0
- package/dist/coingecko/cache.js +27 -0
- package/dist/coingecko/cache.js.map +1 -0
- package/dist/coingecko/client.d.ts +1 -0
- package/dist/coingecko/client.js +45 -0
- package/dist/coingecko/client.js.map +1 -0
- package/dist/coingecko/index.d.ts +2 -0
- package/dist/coingecko/index.js +12 -0
- package/dist/coingecko/index.js.map +1 -0
- package/dist/coinset/pagination.d.ts +5 -0
- package/dist/coinset/pagination.js +19 -10
- package/dist/coinset/pagination.js.map +1 -1
- package/dist/schemas/price.d.ts +3 -0
- package/dist/schemas/price.js +16 -0
- package/dist/schemas/price.js.map +1 -0
- package/dist/server.js +9 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/price/convert-xch-to-fiat.d.ts +2 -0
- package/dist/tools/price/convert-xch-to-fiat.js +42 -0
- package/dist/tools/price/convert-xch-to-fiat.js.map +1 -0
- package/dist/tools/price/get-xch-price.d.ts +2 -0
- package/dist/tools/price/get-xch-price.js +26 -0
- package/dist/tools/price/get-xch-price.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Backed by the public [coinset.org](https://www.coinset.org) full-node API. Suppo
|
|
|
11
11
|
- What is the balance of `xch1...` (or `txch1...`, or a raw puzzle hash)?
|
|
12
12
|
- Look up a coin by name. Calculate a coin name from its fields.
|
|
13
13
|
- Convert between addresses and puzzle hashes in either direction.
|
|
14
|
+
- What is XCH worth in USD (or EUR, GBP, BTC, ...) right now? What's this balance worth?
|
|
14
15
|
|
|
15
16
|
## Install
|
|
16
17
|
|
|
@@ -61,8 +62,14 @@ Add to your `claude_desktop_config.json`:
|
|
|
61
62
|
| `calculate_coin_name` | sha256(parent_coin_info \|\| puzzle_hash \|\| amount) — no RPC |
|
|
62
63
|
| `address_to_puzzle_hash` | bech32m decode — no RPC |
|
|
63
64
|
| `puzzle_hash_to_address` | bech32m encode — no RPC |
|
|
65
|
+
| `get_xch_price` | Current XCH spot price in one or more currencies (CoinGecko) |
|
|
66
|
+
| `convert_xch_to_fiat` | Convert a mojo amount to fiat using the current XCH price |
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
Blockchain tools take an optional `network: "mainnet" | "testnet11"` (default `mainnet`). The price tools don't take a network arg.
|
|
69
|
+
|
|
70
|
+
## Optional config
|
|
71
|
+
|
|
72
|
+
- `COINGECKO_API_KEY` — if set, sent as `x-cg-demo-api-key` to lift the free-tier rate limit. The price tools work without it.
|
|
66
73
|
|
|
67
74
|
## Prompts
|
|
68
75
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const TTL_MS = 60_000;
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
export function getCachedPrices(currencies) {
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
const cached = {};
|
|
6
|
+
const missing = [];
|
|
7
|
+
for (const currency of currencies) {
|
|
8
|
+
const entry = cache.get(currency);
|
|
9
|
+
if (entry && entry.expiresAt > now) {
|
|
10
|
+
cached[currency] = entry.price;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
missing.push(currency);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { cached, missing };
|
|
17
|
+
}
|
|
18
|
+
export function setCachedPrices(prices) {
|
|
19
|
+
const expiresAt = Date.now() + TTL_MS;
|
|
20
|
+
for (const [currency, price] of Object.entries(prices)) {
|
|
21
|
+
cache.set(currency, { price, expiresAt });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function resetCache() {
|
|
25
|
+
cache.clear();
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/coingecko/cache.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,MAAM,CAAC;AAOtB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAiB,CAAC;AAEvC,MAAM,UAAU,eAAe,CAAC,UAAoB;IAIlD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;YACnC,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAA8B;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACtC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fetchXchPrices(currencies: string[]): Promise<Record<string, number>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const COINGECKO_URL = 'https://api.coingecko.com/api/v3/simple/price';
|
|
2
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
3
|
+
export async function fetchXchPrices(currencies) {
|
|
4
|
+
if (currencies.length === 0) {
|
|
5
|
+
throw new Error('at least one currency is required');
|
|
6
|
+
}
|
|
7
|
+
const params = new URLSearchParams({
|
|
8
|
+
ids: 'chia',
|
|
9
|
+
vs_currencies: currencies.join(','),
|
|
10
|
+
});
|
|
11
|
+
const headers = { accept: 'application/json' };
|
|
12
|
+
const apiKey = process.env.COINGECKO_API_KEY;
|
|
13
|
+
if (apiKey)
|
|
14
|
+
headers['x-cg-demo-api-key'] = apiKey;
|
|
15
|
+
const res = await fetch(`${COINGECKO_URL}?${params.toString()}`, {
|
|
16
|
+
headers,
|
|
17
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const body = await res.text().catch(() => '');
|
|
21
|
+
const detail = body ? `: ${body.slice(0, 200)}` : '';
|
|
22
|
+
throw new Error(`coingecko ${res.status} ${res.statusText}${detail}`);
|
|
23
|
+
}
|
|
24
|
+
const data = (await res.json());
|
|
25
|
+
if (!data || typeof data !== 'object' || !('chia' in data)) {
|
|
26
|
+
throw new Error('coingecko response missing `chia` field');
|
|
27
|
+
}
|
|
28
|
+
const chia = data.chia;
|
|
29
|
+
if (!chia || typeof chia !== 'object') {
|
|
30
|
+
throw new Error('coingecko response `chia` field is not an object');
|
|
31
|
+
}
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [currency, value] of Object.entries(chia)) {
|
|
34
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
35
|
+
throw new Error(`coingecko returned non-numeric price for ${currency}`);
|
|
36
|
+
}
|
|
37
|
+
out[currency.toLowerCase()] = value;
|
|
38
|
+
}
|
|
39
|
+
const missing = currencies.filter((c) => !(c in out));
|
|
40
|
+
if (missing.length > 0) {
|
|
41
|
+
throw new Error(`coingecko returned no price for: ${missing.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/coingecko/client.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG,+CAA+C,CAAC;AACtE,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAAoB;IACvD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,GAAG,EAAE,MAAM;QACX,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;KACpC,CAAC,CAAC;IAEH,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACvE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC7C,IAAI,MAAM;QAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,aAAa,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE;QAC/D,OAAO;QACP,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC;KAChD,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,GAAG,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAY,CAAC;IAC3C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,4CAA4C,QAAQ,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;IACtC,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fetchXchPrices } from './client.js';
|
|
2
|
+
import { getCachedPrices, setCachedPrices } from './cache.js';
|
|
3
|
+
export async function getXchPrices(currencies) {
|
|
4
|
+
const { cached, missing } = getCachedPrices(currencies);
|
|
5
|
+
if (missing.length === 0)
|
|
6
|
+
return cached;
|
|
7
|
+
const fetched = await fetchXchPrices(missing);
|
|
8
|
+
setCachedPrices(fetched);
|
|
9
|
+
return { ...cached, ...fetched };
|
|
10
|
+
}
|
|
11
|
+
export { resetCache } from './cache.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/coingecko/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAoB;IACrD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAC9C,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC;AACnC,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -16,6 +16,11 @@ export interface CoinRecordLike {
|
|
|
16
16
|
* looks capped. Coinset returns up to a fixed number of records per call; when
|
|
17
17
|
* we see exactly that many, we re-query the range above the highest confirmed
|
|
18
18
|
* height we've seen so far.
|
|
19
|
+
*
|
|
20
|
+
* Only include start_height / end_height in the payload when the caller asked
|
|
21
|
+
* for them. coinset.org treats end_height literally as a block height, so the
|
|
22
|
+
* full-node convention of passing 0xffffffff to mean "no upper bound" returns
|
|
23
|
+
* zero records.
|
|
19
24
|
*/
|
|
20
25
|
export declare function fetchCoinRecordsByPuzzleHash(agent: RPCAgent, puzzleHashHex: string, options?: {
|
|
21
26
|
includeSpent?: boolean;
|
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
import { get_coin_records_by_puzzle_hash } from 'chia-agent/api/rpc/full_node/index.js';
|
|
2
|
-
const MAX_HEIGHT = 0xffffffff;
|
|
3
2
|
const PAGE_SIZE_LIMIT = 5_000;
|
|
4
3
|
/**
|
|
5
4
|
* Fetch coin records by puzzle hash, paginating by height window if the response
|
|
6
5
|
* looks capped. Coinset returns up to a fixed number of records per call; when
|
|
7
6
|
* we see exactly that many, we re-query the range above the highest confirmed
|
|
8
7
|
* height we've seen so far.
|
|
8
|
+
*
|
|
9
|
+
* Only include start_height / end_height in the payload when the caller asked
|
|
10
|
+
* for them. coinset.org treats end_height literally as a block height, so the
|
|
11
|
+
* full-node convention of passing 0xffffffff to mean "no upper bound" returns
|
|
12
|
+
* zero records.
|
|
9
13
|
*/
|
|
10
14
|
export async function fetchCoinRecordsByPuzzleHash(agent, puzzleHashHex, options = {}) {
|
|
11
15
|
const includeSpent = options.includeSpent ?? false;
|
|
12
|
-
let start = options.startHeight
|
|
13
|
-
const end = options.endHeight
|
|
16
|
+
let start = options.startHeight;
|
|
17
|
+
const end = options.endHeight;
|
|
14
18
|
const all = [];
|
|
15
19
|
const seen = new Set();
|
|
16
|
-
while (
|
|
17
|
-
|
|
20
|
+
while (true) {
|
|
21
|
+
if (start !== undefined && end !== undefined && start > end)
|
|
22
|
+
break;
|
|
23
|
+
const payload = {
|
|
18
24
|
puzzle_hash: puzzleHashHex,
|
|
19
|
-
start_height: start,
|
|
20
|
-
end_height: end,
|
|
21
25
|
include_spent_coins: includeSpent,
|
|
22
|
-
}
|
|
26
|
+
};
|
|
27
|
+
if (start !== undefined)
|
|
28
|
+
payload.start_height = start;
|
|
29
|
+
if (end !== undefined)
|
|
30
|
+
payload.end_height = end;
|
|
31
|
+
const res = await get_coin_records_by_puzzle_hash(agent, payload);
|
|
23
32
|
const records = (res.coin_records ?? []);
|
|
24
|
-
let maxHeight = start;
|
|
33
|
+
let maxHeight = start ?? 0;
|
|
25
34
|
for (const r of records) {
|
|
26
35
|
const key = `${r.coin.parent_coin_info}|${r.coin.puzzle_hash}|${r.coin.amount.toString()}`;
|
|
27
36
|
if (seen.has(key))
|
|
@@ -33,7 +42,7 @@ export async function fetchCoinRecordsByPuzzleHash(agent, puzzleHashHex, options
|
|
|
33
42
|
}
|
|
34
43
|
if (records.length < PAGE_SIZE_LIMIT)
|
|
35
44
|
break;
|
|
36
|
-
if (maxHeight <= start)
|
|
45
|
+
if (start !== undefined && maxHeight <= start)
|
|
37
46
|
break;
|
|
38
47
|
start = maxHeight + 1;
|
|
39
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/coinset/pagination.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/coinset/pagination.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,uCAAuC,CAAC;AAiBxF,MAAM,eAAe,GAAG,KAAK,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAe,EACf,aAAqB,EACrB,UAAgF,EAAE;IAElF,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;IACnD,IAAI,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC;IAChC,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC;IAC9B,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,KAAK,GAAG,GAAG;YAAE,MAAM;QACnE,MAAM,OAAO,GAA+B;YAC1C,WAAW,EAAE,aAAa;YAC1B,mBAAmB,EAAE,YAAY;SAClC,CAAC;QACF,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,CAAC,YAAY,GAAG,KAAK,CAAC;QACtD,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,CAAC,UAAU,GAAG,GAAG,CAAC;QAChD,MAAM,GAAG,GAAG,MAAM,+BAA+B,CAAC,KAAK,EAAE,OAA4B,CAAC,CAAC;QACvF,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAqB,CAAC;QAC7D,IAAI,SAAS,GAAG,KAAK,IAAI,CAAC,CAAC;QAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC3F,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACZ,IAAI,CAAC,CAAC,qBAAqB,GAAG,SAAS;gBAAE,SAAS,GAAG,CAAC,CAAC,qBAAqB,CAAC;QAC/E,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe;YAAE,MAAM;QAC5C,IAAI,KAAK,KAAK,SAAS,IAAI,SAAS,IAAI,KAAK;YAAE,MAAM;QACrD,KAAK,GAAG,SAAS,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const currenciesSchema = z
|
|
3
|
+
.array(z
|
|
4
|
+
.string()
|
|
5
|
+
.min(2)
|
|
6
|
+
.max(10)
|
|
7
|
+
.regex(/^[a-zA-Z]+$/))
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(20)
|
|
10
|
+
.default(['usd'])
|
|
11
|
+
.describe("Fiat or crypto currency codes accepted by CoinGecko (e.g. 'usd', 'eur', 'gbp', 'jpy', 'btc', 'eth'). Case-insensitive. Defaults to ['usd'].");
|
|
12
|
+
export const mojoAmountSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^\d+$/)
|
|
15
|
+
.describe('Non-negative integer mojo amount as a decimal string. 1 XCH = 1,000,000,000,000 mojo. Convert XCH amounts to mojo before passing.');
|
|
16
|
+
//# sourceMappingURL=price.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/schemas/price.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,KAAK,CACJ,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,EAAE,CAAC;KACP,KAAK,CAAC,aAAa,CAAC,CACxB;KACA,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,EAAE,CAAC;KACP,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;KAChB,QAAQ,CACP,6IAA6I,CAC9I,CAAC;AAEJ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,EAAE;KACR,KAAK,CAAC,OAAO,CAAC;KACd,QAAQ,CACP,mIAAmI,CACpI,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -12,15 +12,19 @@ import { register as registerGetCoinByName } from './tools/coins/get-coin-by-nam
|
|
|
12
12
|
import { register as registerCalculateCoinName } from './tools/coins/calculate-coin-name.js';
|
|
13
13
|
import { register as registerAddressToPuzzleHash } from './tools/addresses/address-to-puzzle-hash.js';
|
|
14
14
|
import { register as registerPuzzleHashToAddress } from './tools/addresses/puzzle-hash-to-address.js';
|
|
15
|
+
import { register as registerGetXchPrice } from './tools/price/get-xch-price.js';
|
|
16
|
+
import { register as registerConvertXchToFiat } from './tools/price/convert-xch-to-fiat.js';
|
|
15
17
|
import { register as registerNetworkStatusPrompt } from './prompts/network-status.js';
|
|
16
18
|
import { register as registerAddressSummaryPrompt } from './prompts/address-summary.js';
|
|
17
19
|
import { register as registerBlockSummaryPrompt } from './prompts/block-summary.js';
|
|
18
20
|
export function createServer() {
|
|
19
21
|
const server = new McpServer({ name: 'chia-explorer', version: VERSION }, {
|
|
20
|
-
instructions: 'chia-explorer answers questions about the Chia blockchain via the public coinset.org API
|
|
22
|
+
instructions: 'chia-explorer answers questions about the Chia blockchain via the public coinset.org API, ' +
|
|
23
|
+
'plus XCH spot price and fiat conversion via the public CoinGecko API. ' +
|
|
21
24
|
'Read-only: no signing, no key material, no push_tx. ' +
|
|
22
|
-
"
|
|
23
|
-
'When an address is provided, the network is auto-detected from the prefix (xch / txch).'
|
|
25
|
+
"Blockchain tools accept an optional `network: 'mainnet' | 'testnet11'` argument; mainnet is the default. " +
|
|
26
|
+
'When an address is provided, the network is auto-detected from the prefix (xch / txch). ' +
|
|
27
|
+
'Price tools take no network argument.',
|
|
24
28
|
});
|
|
25
29
|
registerGetBlockchainState(server);
|
|
26
30
|
registerGetNetspace(server);
|
|
@@ -34,6 +38,8 @@ export function createServer() {
|
|
|
34
38
|
registerCalculateCoinName(server);
|
|
35
39
|
registerAddressToPuzzleHash(server);
|
|
36
40
|
registerPuzzleHashToAddress(server);
|
|
41
|
+
registerGetXchPrice(server);
|
|
42
|
+
registerConvertXchToFiat(server);
|
|
37
43
|
registerNetworkStatusPrompt(server);
|
|
38
44
|
registerAddressSummaryPrompt(server);
|
|
39
45
|
registerBlockSummaryPrompt(server);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,QAAQ,IAAI,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AACpG,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAE,QAAQ,IAAI,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACjG,OAAO,EAAE,QAAQ,IAAI,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,QAAQ,IAAI,8BAA8B,EAAE,MAAM,gDAAgD,CAAC;AAE5G,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,QAAQ,IAAI,kCAAkC,EAAE,MAAM,kDAAkD,CAAC;AAClH,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,QAAQ,IAAI,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAE7F,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AACtG,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAEtG,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,QAAQ,IAAI,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,QAAQ,IAAI,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAEpF,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,EAC3C;QACE,YAAY,EACV,4FAA4F;YAC5F,sDAAsD;YACtD,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,QAAQ,IAAI,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AACpG,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAE,QAAQ,IAAI,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACjG,OAAO,EAAE,QAAQ,IAAI,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,QAAQ,IAAI,8BAA8B,EAAE,MAAM,gDAAgD,CAAC;AAE5G,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,QAAQ,IAAI,kCAAkC,EAAE,MAAM,kDAAkD,CAAC;AAClH,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,EAAE,QAAQ,IAAI,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAE7F,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AACtG,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAEtG,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAAE,QAAQ,IAAI,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAE5F,OAAO,EAAE,QAAQ,IAAI,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAE,QAAQ,IAAI,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,QAAQ,IAAI,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAEpF,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,EAC3C;QACE,YAAY,EACV,4FAA4F;YAC5F,wEAAwE;YACxE,sDAAsD;YACtD,2GAA2G;YAC3G,0FAA0F;YAC1F,uCAAuC;KAC1C,CACF,CAAC;IAEF,0BAA0B,CAAC,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACjC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,8BAA8B,CAAC,MAAM,CAAC,CAAC;IAEvC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,kCAAkC,CAAC,MAAM,CAAC,CAAC;IAC3C,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAElC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACpC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAEpC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAEjC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACpC,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACrC,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAEnC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getXchPrices } from '../../coingecko/index.js';
|
|
2
|
+
import { mojoToXch, toBigInt } from '../../chia/amounts.js';
|
|
3
|
+
import { currenciesSchema, mojoAmountSchema } from '../../schemas/price.js';
|
|
4
|
+
import { errorText, jsonText } from '../shared/response.js';
|
|
5
|
+
function normalize(currencies) {
|
|
6
|
+
return Array.from(new Set(currencies.map((c) => c.toLowerCase())));
|
|
7
|
+
}
|
|
8
|
+
export function register(server) {
|
|
9
|
+
server.tool('convert_xch_to_fiat', 'Convert a mojo amount to its current fiat (or other CoinGecko-supported currency) value. 1 XCH = 1,000,000,000,000 mojo — convert XCH to mojo before calling. Prices come from CoinGecko (cached 60s). Returned values use JS floating-point; precise to far more decimals than any realistic balance.', {
|
|
10
|
+
mojo: mojoAmountSchema,
|
|
11
|
+
currencies: currenciesSchema,
|
|
12
|
+
}, async ({ mojo, currencies }) => {
|
|
13
|
+
try {
|
|
14
|
+
const mojoBig = toBigInt(mojo);
|
|
15
|
+
if (mojoBig < 0n)
|
|
16
|
+
throw new Error('mojo must be non-negative');
|
|
17
|
+
const xchString = mojoToXch(mojoBig);
|
|
18
|
+
const xchNumber = Number(xchString);
|
|
19
|
+
const normalized = normalize(currencies);
|
|
20
|
+
const prices = await getXchPrices(normalized);
|
|
21
|
+
const values = {};
|
|
22
|
+
for (const currency of normalized) {
|
|
23
|
+
const price = prices[currency];
|
|
24
|
+
if (price === undefined)
|
|
25
|
+
throw new Error(`missing price for ${currency}`);
|
|
26
|
+
values[currency] = xchNumber * price;
|
|
27
|
+
}
|
|
28
|
+
return jsonText({
|
|
29
|
+
source: 'coingecko',
|
|
30
|
+
mojo: mojoBig.toString(),
|
|
31
|
+
amount_xch: xchString,
|
|
32
|
+
prices_per_xch: prices,
|
|
33
|
+
values,
|
|
34
|
+
fetched_at: new Date().toISOString(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
return errorText(err);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=convert-xch-to-fiat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert-xch-to-fiat.js","sourceRoot":"","sources":["../../../src/tools/price/convert-xch-to-fiat.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5D,SAAS,SAAS,CAAC,UAAoB;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,wSAAwS,EACxS;QACE,IAAI,EAAE,gBAAgB;QACtB,UAAU,EAAE,gBAAgB;KAC7B,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,OAAO,GAAG,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC/D,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;YACrC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;YAEpC,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;YAE9C,MAAM,MAAM,GAA2B,EAAE,CAAC;YAC1C,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC/B,IAAI,KAAK,KAAK,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;gBAC1E,MAAM,CAAC,QAAQ,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC;YACvC,CAAC;YAED,OAAO,QAAQ,CAAC;gBACd,MAAM,EAAE,WAAW;gBACnB,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE;gBACxB,UAAU,EAAE,SAAS;gBACrB,cAAc,EAAE,MAAM;gBACtB,MAAM;gBACN,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACrC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getXchPrices } from '../../coingecko/index.js';
|
|
2
|
+
import { currenciesSchema } from '../../schemas/price.js';
|
|
3
|
+
import { errorText, jsonText } from '../shared/response.js';
|
|
4
|
+
function normalize(currencies) {
|
|
5
|
+
return Array.from(new Set(currencies.map((c) => c.toLowerCase())));
|
|
6
|
+
}
|
|
7
|
+
export function register(server) {
|
|
8
|
+
server.tool('get_xch_price', "Current XCH spot price via CoinGecko. Accepts any currency CoinGecko supports (e.g. 'usd', 'eur', 'gbp', 'jpy', 'btc', 'eth'); defaults to ['usd']. Free-tier API is rate-limited; results are cached for 60 seconds.", {
|
|
9
|
+
currencies: currenciesSchema,
|
|
10
|
+
}, async ({ currencies }) => {
|
|
11
|
+
try {
|
|
12
|
+
const normalized = normalize(currencies);
|
|
13
|
+
const prices = await getXchPrices(normalized);
|
|
14
|
+
return jsonText({
|
|
15
|
+
source: 'coingecko',
|
|
16
|
+
asset: 'chia',
|
|
17
|
+
prices,
|
|
18
|
+
fetched_at: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return errorText(err);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=get-xch-price.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-xch-price.js","sourceRoot":"","sources":["../../../src/tools/price/get-xch-price.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5D,SAAS,SAAS,CAAC,UAAoB;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,eAAe,EACf,uNAAuN,EACvN;QACE,UAAU,EAAE,gBAAgB;KAC7B,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;YAC9C,OAAO,QAAQ,CAAC;gBACd,MAAM,EAAE,WAAW;gBACnB,KAAK,EAAE,MAAM;gBACb,MAAM;gBACN,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACrC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED