Last active
December 10, 2025 11:00
-
-
Save h4x3rotab/f0c9f279e65f9c3bb6ccf2e77880dc52 to your computer and use it in GitHub Desktop.
TON root contract dashboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "dependencies": { | |
| "@ton/core": "^0.62.0", | |
| "@ton/ton": "^16.1.0" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /** | |
| * Mini TON dashboard: fetches the COCOON root contract state from mainnet and | |
| * prints tokenomics plus the currently registered proxies (on-chain registry). | |
| * | |
| * Requirements: | |
| * npm install @ton/ton @ton/core | |
| * | |
| * Usage: | |
| * ROOT_CONTRACT_ADDRESS=EQ... TONCENTER_API_KEY=... node scripts/ton-dashboard.mjs | |
| * node scripts/ton-dashboard.mjs --root EQ... --endpoint https://toncenter.com/api/v2/jsonRPC | |
| */ | |
| import { TonClient } from "@ton/ton"; | |
| import { Address, Cell, Dictionary } from "@ton/core"; | |
| const DEFAULT_ROOT = | |
| process.env.ROOT_CONTRACT_ADDRESS || | |
| "EQBcXvP9DUA4k5tqUapcilt4kZnBzF0Ts7OW0Yp5FI0aN7g0"; | |
| const DEFAULT_ENDPOINT = | |
| process.env.TONCENTER_ENDPOINT || "https://toncenter.com/api/v2/jsonRPC"; | |
| function parseArgs() { | |
| const args = new Map(); | |
| for (let i = 2; i < process.argv.length; i++) { | |
| const arg = process.argv[i]; | |
| if (!arg.startsWith("--")) { | |
| args.set("_", arg); | |
| continue; | |
| } | |
| const [k, v] = arg.replace(/^--/, "").split("="); | |
| args.set(k, v ?? process.argv[++i]); | |
| } | |
| return args; | |
| } | |
| const args = parseArgs(); | |
| const rootAddress = args.get("_") || args.get("root") || DEFAULT_ROOT; | |
| const endpoint = args.get("endpoint") || DEFAULT_ENDPOINT; | |
| const apiKey = args.get("api-key") || process.env.TONCENTER_API_KEY || ""; | |
| function nanoToTon(n) { | |
| return Number(n) / 1e9; | |
| } | |
| function parseParams(cell) { | |
| const cs = cell.beginParse(); | |
| const paramsStructVersion = cs.loadUint(8); | |
| const paramsVersion = cs.loadUint(32); | |
| const uniqueId = cs.loadUint(32); | |
| const isTest = cs.loadBit(); | |
| const pricePerToken = cs.loadCoins(); | |
| const workerFeePerToken = cs.loadCoins(); | |
| const promptMult = paramsStructVersion >= 3 ? cs.loadUint(32) : 10000; | |
| const cachedMult = paramsStructVersion >= 2 ? cs.loadUint(32) : 10000; | |
| const completionMult = paramsStructVersion >= 3 ? cs.loadUint(32) : 10000; | |
| const reasoningMult = paramsStructVersion >= 2 ? cs.loadUint(32) : 10000; | |
| const proxyDelayBeforeClose = cs.loadUint(32); | |
| const clientDelayBeforeClose = cs.loadUint(32); | |
| const minProxyStake = paramsStructVersion >= 1 ? cs.loadCoins() : 0n; | |
| const minClientStake = paramsStructVersion >= 1 ? cs.loadCoins() : 0n; | |
| return { | |
| paramsStructVersion, | |
| paramsVersion, | |
| uniqueId, | |
| isTest, | |
| pricePerToken, | |
| workerFeePerToken, | |
| promptMult, | |
| cachedMult, | |
| completionMult, | |
| reasoningMult, | |
| proxyDelayBeforeClose, | |
| clientDelayBeforeClose, | |
| minProxyStake, | |
| minClientStake, | |
| }; | |
| } | |
| function extractHashes(cell) { | |
| if (!cell) return []; | |
| const tryCells = [cell, ...cell.refs]; | |
| for (const candidate of tryCells) { | |
| try { | |
| const dict = candidate.beginParse().loadDictDirect( | |
| Dictionary.Keys.BigUint(256), | |
| Dictionary.Values.Cell() | |
| ); | |
| return [...dict.keys()].map((k) => k.toString(16).padStart(64, "0")); | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| return []; | |
| } | |
| function parseProxiesDict(cell) { | |
| if (!cell) return []; | |
| // First, try dict form: { seqno(uint32) -> cell(addresses) } | |
| try { | |
| const dict = cell.beginParse().loadDictDirect( | |
| Dictionary.Keys.Uint(32), | |
| Dictionary.Values.Cell() | |
| ); | |
| const proxies = []; | |
| for (const [seq, vcell] of dict) { | |
| const cs = vcell.beginParse(); | |
| const typeBit = cs.loadBit(); // 0=ipv4, 1=ipv6 | |
| const len = cs.loadUint(7); | |
| const addrBuf = cs.loadBuffer(len); | |
| const addrStr = addrBuf.toString("utf8"); | |
| const [forWorkers, forClients] = addrStr.includes(" ") | |
| ? addrStr.split(" ", 2) | |
| : [addrStr, addrStr]; | |
| proxies.push({ | |
| seqno: Number(seq), | |
| type: typeBit ? "ipv6" : "ipv4", | |
| forWorkers, | |
| forClients, | |
| }); | |
| } | |
| if (proxies.length) { | |
| return proxies.sort((a, b) => a.seqno - b.seqno); | |
| } | |
| } catch (e) { | |
| // fall through to legacy | |
| } | |
| // Legacy format: single entry with two addresses separated by space, prefixed by a count. | |
| try { | |
| const br = cell.beginParse(); | |
| const typeBit = br.loadBit(); // 0=ipv4, 1=ipv6 | |
| const len = br.loadUint(7); | |
| const available = Math.floor(br.remainingBits / 8); | |
| const toRead = Math.min(len, available); | |
| const addrBuf = br.loadBuffer(toRead); | |
| const text = addrBuf.toString("utf8"); | |
| const m = text.match( | |
| /(\d{1,3}(?:\.\d{1,3}){3}:\d{2,5})(?:\s+(\d{1,3}(?:\.\d{1,3}){3}:\d{2,5}))?/ | |
| ); | |
| const forWorkers = m ? m[1] : text.trim(); | |
| const forClients = m && m[2] ? m[2] : forWorkers; | |
| return [ | |
| { | |
| seqno: 0, | |
| type: typeBit ? "ipv6" : "ipv4", | |
| forWorkers, | |
| forClients, | |
| }, | |
| ]; | |
| } catch (e) { | |
| return []; | |
| } | |
| } | |
| async function main() { | |
| const client = new TonClient({ endpoint, apiKey }); | |
| const root = Address.parse(rootAddress); | |
| const state = await client.getContractState(root); | |
| if (!state.data) { | |
| throw new Error("Contract has no data (not active?)"); | |
| } | |
| const rootCell = Cell.fromBoc(Buffer.from(state.data, "base64"))[0]; | |
| const slice = rootCell.beginParse(); | |
| const owner = slice.loadAddress(); | |
| const dataCell = slice.loadRef(); | |
| const version = slice.loadUint(32); | |
| const paramsCell = slice.loadRef(); | |
| const parsed = parseParams(paramsCell); | |
| const dataSlice = dataCell.beginParse(); | |
| const loadMaybeRefDict = () => { | |
| const exists = dataSlice.loadBit(); | |
| return exists ? dataSlice.loadRef() : null; | |
| }; | |
| const proxyHashesCell = loadMaybeRefDict(); | |
| const proxiesDictCell = loadMaybeRefDict(); | |
| const workerHashesCell = loadMaybeRefDict(); | |
| const modelHashesCell = loadMaybeRefDict(); | |
| const proxyHashes = extractHashes(proxyHashesCell); | |
| const proxies = parseProxiesDict(proxiesDictCell); | |
| const workerHashes = extractHashes(workerHashesCell); | |
| const modelHashes = extractHashes(modelHashesCell); | |
| console.log("Root contract:", root.toString({ bounceable: false })); | |
| console.log("Owner:", owner.toString({ bounceable: false })); | |
| console.log("Version:", version); | |
| console.log(""); | |
| console.log("Tokenomics (nanoTON):"); | |
| console.log(" price_per_token :", parsed.pricePerToken.toString()); | |
| console.log(" worker_fee_per_token:", parsed.workerFeePerToken.toString()); | |
| console.log( | |
| ` multipliers : prompt=${parsed.promptMult} cached=${parsed.cachedMult} completion=${parsed.completionMult} reasoning=${parsed.reasoningMult}` | |
| ); | |
| console.log( | |
| ` min stakes (TON) : proxy=${nanoToTon(parsed.minProxyStake)} client=${nanoToTon(parsed.minClientStake)}` | |
| ); | |
| console.log( | |
| ` close delays (sec) : proxy=${parsed.proxyDelayBeforeClose} client=${parsed.clientDelayBeforeClose}` | |
| ); | |
| console.log(""); | |
| console.log("Registered proxies:"); | |
| if (proxies.length === 0) { | |
| console.log(" (none)"); | |
| } else { | |
| proxies.forEach((p) => | |
| console.log( | |
| ` #${p.seqno.toString().padStart(3, " ")} workers=${p.forWorkers} clients=${p.forClients} (${p.type})` | |
| ) | |
| ); | |
| } | |
| console.log(""); | |
| console.log("Proxy hashes:", proxyHashes.length); | |
| proxyHashes.forEach((h) => console.log(" ", h)); | |
| console.log("Worker hashes:", workerHashes.length); | |
| workerHashes.forEach((h) => console.log(" ", h)); | |
| console.log("Model hashes:", modelHashes.length); | |
| modelHashes.forEach((h) => console.log(" ", h)); | |
| } | |
| main().catch((err) => { | |
| console.error("Failed to fetch dashboard:", err.message || err); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment