@ergoblockchain/sage-widget 0.1.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.
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var DEFAULT_API_BASE = "https://www.ergoblockchain.org";
5
+ var DEFAULT_LIMIT = 5;
6
+ var DEFAULT_REFRESH_MS = 6e4;
7
+
8
+ // src/api.ts
9
+ async function fetchSageActivity(opts = {}) {
10
+ const base = opts.apiBase ?? DEFAULT_API_BASE;
11
+ const limit = Math.min(Math.max(opts.limit ?? DEFAULT_LIMIT, 1), 25);
12
+ const url = `${base}/api/sage/activity?limit=${limit}`;
13
+ const res = await fetch(url, { signal: opts.signal });
14
+ if (!res.ok) {
15
+ throw new Error(`sage activity ${res.status}`);
16
+ }
17
+ return await res.json();
18
+ }
19
+ function nanoToErg(nano) {
20
+ if (!nano || nano <= 0) return "0";
21
+ const erg = nano / 1e9;
22
+ return erg.toFixed(9).replace(/\.?0+$/, "");
23
+ }
24
+ function relativeTime(ms, now = Date.now()) {
25
+ const diff = Math.max(0, now - ms);
26
+ const sec = Math.floor(diff / 1e3);
27
+ if (sec < 60) return `${sec}s ago`;
28
+ const min = Math.floor(sec / 60);
29
+ if (min < 60) return `${min}m ago`;
30
+ const hr = Math.floor(min / 60);
31
+ if (hr < 24) return `${hr}h ago`;
32
+ const day = Math.floor(hr / 24);
33
+ return `${day}d ago`;
34
+ }
35
+ function receiptUrl(txId, apiBase = DEFAULT_API_BASE) {
36
+ return `${apiBase}/r/sage/${txId}`;
37
+ }
38
+ function explorerUrl(txId, network = "testnet") {
39
+ return network === "testnet" ? `https://testnet.ergoplatform.com/transactions/${txId}` : `https://explorer.ergoplatform.com/transactions/${txId}`;
40
+ }
41
+
42
+ // src/vanilla/mountSageFeed.ts
43
+ function mountSageFeed(target, opts = {}) {
44
+ const apiBase = opts.apiBase;
45
+ const limit = opts.limit ?? DEFAULT_LIMIT;
46
+ const refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS;
47
+ let cancelled = false;
48
+ let abort = new AbortController();
49
+ let response = null;
50
+ let pollId;
51
+ let tickId;
52
+ const root = document.createElement("div");
53
+ root.setAttribute("data-sage-widget", "true");
54
+ applyStyles(root, rootStyle);
55
+ target.appendChild(root);
56
+ function render() {
57
+ root.innerHTML = "";
58
+ const header = document.createElement("div");
59
+ applyStyles(header, headerStyle);
60
+ const dot = document.createElement("span");
61
+ applyStyles(dot, dotStyle);
62
+ const label = document.createElement("span");
63
+ applyStyles(label, labelStyle);
64
+ label.textContent = `Live \xB7 Ergo ${response?.network ?? "testnet"}`;
65
+ const link = document.createElement("a");
66
+ link.href = "https://www.ergoblockchain.org/agent-economy#sage-activity";
67
+ link.target = "_blank";
68
+ link.rel = "noopener noreferrer";
69
+ link.textContent = "Sage on chain";
70
+ applyStyles(link, linkStyle);
71
+ header.append(dot, label, link);
72
+ root.appendChild(header);
73
+ const list = document.createElement("div");
74
+ const events = response?.events ?? [];
75
+ if (!response) {
76
+ list.textContent = "Loading\u2026";
77
+ applyStyles(list, emptyStyle);
78
+ } else if (events.length === 0) {
79
+ list.textContent = "No activity yet \u2014 be the first to ask Sage a paid query.";
80
+ applyStyles(list, emptyStyle);
81
+ } else {
82
+ events.forEach((evt) => list.appendChild(renderRow(evt, response.network, apiBase)));
83
+ }
84
+ root.appendChild(list);
85
+ }
86
+ function renderRow(evt, network, base) {
87
+ const isSettle = evt.type === "settlement";
88
+ const a = document.createElement("a");
89
+ a.href = isSettle ? receiptUrl(evt.txId, base) : explorerUrl(evt.txId, network);
90
+ a.target = "_blank";
91
+ a.rel = "noopener noreferrer";
92
+ applyStyles(a, rowStyle);
93
+ const chip = document.createElement("span");
94
+ chip.textContent = isSettle ? "Settled" : evt.type === "issuance" ? "Issued" : "Transfer";
95
+ applyStyles(chip, isSettle ? chipSettleStyle : chipIssueStyle);
96
+ const meta = document.createElement("span");
97
+ applyStyles(meta, metaStyle);
98
+ const tx = document.createElement("span");
99
+ tx.textContent = evt.txId;
100
+ applyStyles(tx, txStyle);
101
+ const when = document.createElement("span");
102
+ when.textContent = `block ${evt.blockHeight.toLocaleString()} \xB7 ${relativeTime(evt.timestamp)}`;
103
+ applyStyles(when, whenStyle);
104
+ meta.append(tx, when);
105
+ const amt = document.createElement("span");
106
+ const value = isSettle ? evt.paymentNanoErg ?? evt.inflowNanoErg : evt.inflowNanoErg;
107
+ applyStyles(amt, amountStyle);
108
+ const ergSpan = document.createElement("span");
109
+ ergSpan.textContent = ` ERG`;
110
+ applyStyles(ergSpan, amountUnitStyle);
111
+ amt.append(nanoToErg(value), ergSpan);
112
+ a.append(chip, meta, amt);
113
+ return a;
114
+ }
115
+ async function load() {
116
+ if (cancelled) return;
117
+ abort.abort();
118
+ abort = new AbortController();
119
+ try {
120
+ const data = await fetchSageActivity({
121
+ apiBase,
122
+ limit,
123
+ signal: abort.signal
124
+ });
125
+ if (cancelled) return;
126
+ response = data;
127
+ render();
128
+ opts.onUpdate?.(data);
129
+ } catch (err) {
130
+ if (cancelled || err.name === "AbortError") return;
131
+ (opts.onError ?? console.warn)(err);
132
+ }
133
+ }
134
+ render();
135
+ load();
136
+ if (refreshMs > 0) pollId = setInterval(load, refreshMs);
137
+ tickId = setInterval(render, 15e3);
138
+ return {
139
+ refresh: load,
140
+ destroy() {
141
+ cancelled = true;
142
+ abort.abort();
143
+ if (pollId) clearInterval(pollId);
144
+ if (tickId) clearInterval(tickId);
145
+ root.remove();
146
+ },
147
+ current: () => response
148
+ };
149
+ }
150
+ function applyStyles(el, styles) {
151
+ Object.assign(el.style, styles);
152
+ }
153
+ var rootStyle = {
154
+ background: "#000",
155
+ color: "#e5e7eb",
156
+ fontFamily: "'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace",
157
+ padding: "14px 16px",
158
+ borderRadius: "14px",
159
+ border: "1px solid rgba(255,255,255,.08)",
160
+ boxSizing: "border-box",
161
+ width: "100%",
162
+ fontSize: "14px",
163
+ lineHeight: "1.4"
164
+ };
165
+ var headerStyle = {
166
+ display: "flex",
167
+ alignItems: "center",
168
+ gap: "10px",
169
+ marginBottom: "10px"
170
+ };
171
+ var dotStyle = {
172
+ width: "8px",
173
+ height: "8px",
174
+ borderRadius: "50%",
175
+ background: "#fb923c",
176
+ flexShrink: "0"
177
+ };
178
+ var labelStyle = {
179
+ fontSize: "10px",
180
+ letterSpacing: "0.25em",
181
+ textTransform: "uppercase",
182
+ color: "#fb923c"
183
+ };
184
+ var linkStyle = {
185
+ marginLeft: "auto",
186
+ fontSize: "13px",
187
+ color: "#fed7aa",
188
+ textDecoration: "none"
189
+ };
190
+ var rowStyle = {
191
+ display: "flex",
192
+ alignItems: "center",
193
+ gap: "10px",
194
+ padding: "8px 0",
195
+ borderTop: "1px solid rgba(255,255,255,.06)",
196
+ textDecoration: "none",
197
+ color: "inherit"
198
+ };
199
+ var chipSettleStyle = {
200
+ fontSize: "9px",
201
+ letterSpacing: "0.2em",
202
+ textTransform: "uppercase",
203
+ padding: "2px 6px",
204
+ borderRadius: "4px",
205
+ flexShrink: "0",
206
+ border: "1px solid rgba(251,146,60,.4)",
207
+ color: "#fdba74",
208
+ background: "rgba(251,146,60,.1)"
209
+ };
210
+ var chipIssueStyle = {
211
+ fontSize: "9px",
212
+ letterSpacing: "0.2em",
213
+ textTransform: "uppercase",
214
+ padding: "2px 6px",
215
+ borderRadius: "4px",
216
+ flexShrink: "0",
217
+ border: "1px solid rgba(245,158,11,.3)",
218
+ color: "rgba(252,211,77,.8)",
219
+ background: "rgba(245,158,11,.05)"
220
+ };
221
+ var metaStyle = {
222
+ flex: "1",
223
+ minWidth: "0",
224
+ display: "flex",
225
+ flexDirection: "column"
226
+ };
227
+ var txStyle = {
228
+ color: "#d1d5db",
229
+ fontSize: "11px",
230
+ overflow: "hidden",
231
+ textOverflow: "ellipsis",
232
+ whiteSpace: "nowrap"
233
+ };
234
+ var whenStyle = {
235
+ color: "#6b7280",
236
+ fontSize: "10px",
237
+ marginTop: "1px"
238
+ };
239
+ var amountStyle = {
240
+ color: "#fed7aa",
241
+ fontSize: "12px",
242
+ flexShrink: "0",
243
+ textAlign: "right"
244
+ };
245
+ var amountUnitStyle = {
246
+ color: "#6b7280",
247
+ fontSize: "9px",
248
+ textTransform: "uppercase",
249
+ marginLeft: "2px"
250
+ };
251
+ var emptyStyle = {
252
+ padding: "18px 0",
253
+ textAlign: "center",
254
+ color: "#6b7280",
255
+ fontSize: "11px"
256
+ };
257
+
258
+ exports.DEFAULT_API_BASE = DEFAULT_API_BASE;
259
+ exports.DEFAULT_LIMIT = DEFAULT_LIMIT;
260
+ exports.DEFAULT_REFRESH_MS = DEFAULT_REFRESH_MS;
261
+ exports.mountSageFeed = mountSageFeed;
262
+ //# sourceMappingURL=vanilla.cjs.map
263
+ //# sourceMappingURL=vanilla.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/api.ts","../src/vanilla/mountSageFeed.ts"],"names":[],"mappings":"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","file":"vanilla.cjs","sourcesContent":["/**\n * Public types — mirror of /api/sage/activity response shape.\n *\n * The source of truth is the API at https://www.ergoblockchain.org/api/sage/activity;\n * this file re-states the schema so consumers don't have to depend on the\n * server-side fetcher.\n */\n\nexport type SageActivityType = \"settlement\" | \"issuance\" | \"transfer\"\n\nexport interface SageActivityEvent {\n /** 64-char hex transaction id. */\n txId: string\n /** Block height at inclusion. */\n blockHeight: number\n /** Block timestamp (ms epoch). */\n timestamp: number\n /** Heuristic classification of the tx. */\n type: SageActivityType\n /** nanoERG flowing into the seller wallet from this tx (sum of outputs). */\n inflowNanoErg: number\n /**\n * For settlements: value of the redeemed Note (= what the buyer paid).\n * For other event types: undefined.\n *\n * Use this — not `inflowNanoErg` — when displaying \"amount paid for a\n * settled query\". `inflowNanoErg` includes change boxes in test setups\n * where the buyer and seller share an address.\n */\n paymentNanoErg?: number\n /** First input box that carries Note-shape registers, if any. */\n noteBoxId?: string\n}\n\nexport interface SageActivityResponse {\n ok: boolean\n network: \"testnet\" | \"mainnet\"\n /** Sage seller wallet address. */\n receiver: string\n /** Total number of txs ever touching the wallet, per the explorer. */\n total: number\n events: SageActivityEvent[]\n error?: string\n}\n\n/**\n * Configuration accepted by every entry point (React component +\n * vanilla mount fn). Defaults below are sensible for the canonical\n * ergoblockchain.org deployment.\n */\nexport interface SageWidgetOptions {\n /**\n * Base URL of the Sage host. Override if you run your own Sage\n * deployment behind a custom domain. Default: ergoblockchain.org.\n */\n apiBase?: string\n /**\n * Number of events to display (max 25). Default: 5.\n */\n limit?: number\n /**\n * Polling interval in ms. Default: 60000 (60s). Set to 0 to disable\n * polling — the widget will fetch once on mount and never refresh.\n */\n refreshMs?: number\n /**\n * Optional callback fired every time a fresh response arrives. Useful\n * for analytics or for triggering host-side animations on new\n * settlements.\n */\n onUpdate?: (response: SageActivityResponse) => void\n /**\n * Optional callback fired on fetch errors. Default: console.warn.\n */\n onError?: (error: unknown) => void\n}\n\nexport const DEFAULT_API_BASE = \"https://www.ergoblockchain.org\"\nexport const DEFAULT_LIMIT = 5\nexport const DEFAULT_REFRESH_MS = 60_000\n","/**\n * Thin client over /api/sage/activity.\n *\n * Pure fetch + shape — no rendering, no DOM. React + vanilla mounts\n * both call into here so the API contract lives in one place.\n */\n\nimport {\n DEFAULT_API_BASE,\n DEFAULT_LIMIT,\n type SageActivityResponse,\n} from \"./types\"\n\nexport interface FetchActivityOptions {\n apiBase?: string\n limit?: number\n signal?: AbortSignal\n}\n\nexport async function fetchSageActivity(\n opts: FetchActivityOptions = {},\n): Promise<SageActivityResponse> {\n const base = opts.apiBase ?? DEFAULT_API_BASE\n const limit = Math.min(Math.max(opts.limit ?? DEFAULT_LIMIT, 1), 25)\n const url = `${base}/api/sage/activity?limit=${limit}`\n const res = await fetch(url, { signal: opts.signal })\n if (!res.ok) {\n throw new Error(`sage activity ${res.status}`)\n }\n return (await res.json()) as SageActivityResponse\n}\n\n/** nanoERG → \"0.001\" (trims trailing zeros, max 9 decimals). */\nexport function nanoToErg(nano: number | undefined): string {\n if (!nano || nano <= 0) return \"0\"\n const erg = nano / 1e9\n return erg.toFixed(9).replace(/\\.?0+$/, \"\")\n}\n\n/** Cheap relative-time formatter, ASCII-only, no Intl deps. */\nexport function relativeTime(ms: number, now: number = Date.now()): string {\n const diff = Math.max(0, now - ms)\n const sec = Math.floor(diff / 1000)\n if (sec < 60) return `${sec}s ago`\n const min = Math.floor(sec / 60)\n if (min < 60) return `${min}m ago`\n const hr = Math.floor(min / 60)\n if (hr < 24) return `${hr}h ago`\n const day = Math.floor(hr / 24)\n return `${day}d ago`\n}\n\n/** Receipt URL for a settled tx, given the host base. */\nexport function receiptUrl(txId: string, apiBase: string = DEFAULT_API_BASE): string {\n return `${apiBase}/r/sage/${txId}`\n}\n\n/** Explorer URL for a tx on the given network. */\nexport function explorerUrl(\n txId: string,\n network: \"testnet\" | \"mainnet\" = \"testnet\",\n): string {\n return network === \"testnet\"\n ? `https://testnet.ergoplatform.com/transactions/${txId}`\n : `https://explorer.ergoplatform.com/transactions/${txId}`\n}\n","/**\n * mountSageFeed — framework-agnostic DOM mount for the Sage activity\n * feed. Imports cleanly into any non-React app (Svelte, Vue, plain HTML\n * with bundler, etc.). For React apps prefer the typed component at\n * `@ergoblockchain/sage-widget/react`.\n *\n * Usage:\n *\n * import { mountSageFeed } from \"@ergoblockchain/sage-widget/vanilla\"\n * const handle = mountSageFeed(document.getElementById(\"sage\")!, {\n * limit: 5,\n * refreshMs: 60000,\n * })\n * // later: handle.destroy()\n *\n * No iframe, no shadow DOM — the markup is rendered into the target\n * element with inline styles. If you need style isolation, embed the\n * canonical iframe variant from `https://www.ergoblockchain.org/agents.js`.\n */\n\nimport {\n explorerUrl,\n fetchSageActivity,\n nanoToErg,\n receiptUrl,\n relativeTime,\n} from \"../api\"\nimport {\n DEFAULT_LIMIT,\n DEFAULT_REFRESH_MS,\n type SageActivityEvent,\n type SageActivityResponse,\n type SageWidgetOptions,\n} from \"../types\"\n\nexport interface MountSageFeedHandle {\n /** Trigger a fetch outside the polling interval (e.g. on user action). */\n refresh: () => Promise<void>\n /** Stop polling and remove all DOM the widget rendered. */\n destroy: () => void\n /** Most recent successful response, or null if none yet. */\n current: () => SageActivityResponse | null\n}\n\nexport function mountSageFeed(\n target: Element,\n opts: SageWidgetOptions = {},\n): MountSageFeedHandle {\n const apiBase = opts.apiBase\n const limit = opts.limit ?? DEFAULT_LIMIT\n const refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS\n\n let cancelled = false\n let abort = new AbortController()\n let response: SageActivityResponse | null = null\n let pollId: ReturnType<typeof setInterval> | undefined\n let tickId: ReturnType<typeof setInterval> | undefined\n\n const root = document.createElement(\"div\")\n root.setAttribute(\"data-sage-widget\", \"true\")\n applyStyles(root, rootStyle)\n target.appendChild(root)\n\n function render() {\n root.innerHTML = \"\"\n const header = document.createElement(\"div\")\n applyStyles(header, headerStyle)\n const dot = document.createElement(\"span\")\n applyStyles(dot, dotStyle)\n const label = document.createElement(\"span\")\n applyStyles(label, labelStyle)\n label.textContent = `Live · Ergo ${response?.network ?? \"testnet\"}`\n const link = document.createElement(\"a\")\n link.href = \"https://www.ergoblockchain.org/agent-economy#sage-activity\"\n link.target = \"_blank\"\n link.rel = \"noopener noreferrer\"\n link.textContent = \"Sage on chain\"\n applyStyles(link, linkStyle)\n header.append(dot, label, link)\n root.appendChild(header)\n\n const list = document.createElement(\"div\")\n const events = response?.events ?? []\n if (!response) {\n list.textContent = \"Loading…\"\n applyStyles(list, emptyStyle)\n } else if (events.length === 0) {\n list.textContent = \"No activity yet — be the first to ask Sage a paid query.\"\n applyStyles(list, emptyStyle)\n } else {\n events.forEach((evt) => list.appendChild(renderRow(evt, response!.network, apiBase)))\n }\n root.appendChild(list)\n }\n\n function renderRow(\n evt: SageActivityEvent,\n network: \"testnet\" | \"mainnet\",\n base: string | undefined,\n ): HTMLAnchorElement {\n const isSettle = evt.type === \"settlement\"\n const a = document.createElement(\"a\")\n a.href = isSettle ? receiptUrl(evt.txId, base) : explorerUrl(evt.txId, network)\n a.target = \"_blank\"\n a.rel = \"noopener noreferrer\"\n applyStyles(a, rowStyle)\n\n const chip = document.createElement(\"span\")\n chip.textContent = isSettle\n ? \"Settled\"\n : evt.type === \"issuance\"\n ? \"Issued\"\n : \"Transfer\"\n applyStyles(chip, isSettle ? chipSettleStyle : chipIssueStyle)\n\n const meta = document.createElement(\"span\")\n applyStyles(meta, metaStyle)\n const tx = document.createElement(\"span\")\n tx.textContent = evt.txId\n applyStyles(tx, txStyle)\n const when = document.createElement(\"span\")\n when.textContent = `block ${evt.blockHeight.toLocaleString()} · ${relativeTime(evt.timestamp)}`\n applyStyles(when, whenStyle)\n meta.append(tx, when)\n\n const amt = document.createElement(\"span\")\n const value = isSettle ? evt.paymentNanoErg ?? evt.inflowNanoErg : evt.inflowNanoErg\n applyStyles(amt, amountStyle)\n const ergSpan = document.createElement(\"span\")\n ergSpan.textContent = ` ERG`\n applyStyles(ergSpan, amountUnitStyle)\n amt.append(nanoToErg(value), ergSpan)\n\n a.append(chip, meta, amt)\n return a\n }\n\n async function load() {\n if (cancelled) return\n abort.abort()\n abort = new AbortController()\n try {\n const data = await fetchSageActivity({\n apiBase,\n limit,\n signal: abort.signal,\n })\n if (cancelled) return\n response = data\n render()\n opts.onUpdate?.(data)\n } catch (err) {\n if (cancelled || (err as { name?: string }).name === \"AbortError\") return\n ;(opts.onError ?? console.warn)(err)\n }\n }\n\n render()\n load()\n if (refreshMs > 0) pollId = setInterval(load, refreshMs)\n tickId = setInterval(render, 15_000)\n\n return {\n refresh: load,\n destroy() {\n cancelled = true\n abort.abort()\n if (pollId) clearInterval(pollId)\n if (tickId) clearInterval(tickId)\n root.remove()\n },\n current: () => response,\n }\n}\n\nfunction applyStyles(el: HTMLElement, styles: Partial<CSSStyleDeclaration>) {\n Object.assign(el.style, styles)\n}\n\n// ── Default inline styles, mirror of the React component ──\n\nconst rootStyle: Partial<CSSStyleDeclaration> = {\n background: \"#000\",\n color: \"#e5e7eb\",\n fontFamily:\n \"'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace\",\n padding: \"14px 16px\",\n borderRadius: \"14px\",\n border: \"1px solid rgba(255,255,255,.08)\",\n boxSizing: \"border-box\",\n width: \"100%\",\n fontSize: \"14px\",\n lineHeight: \"1.4\",\n}\n\nconst headerStyle: Partial<CSSStyleDeclaration> = {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"10px\",\n marginBottom: \"10px\",\n}\n\nconst dotStyle: Partial<CSSStyleDeclaration> = {\n width: \"8px\",\n height: \"8px\",\n borderRadius: \"50%\",\n background: \"#fb923c\",\n flexShrink: \"0\",\n}\n\nconst labelStyle: Partial<CSSStyleDeclaration> = {\n fontSize: \"10px\",\n letterSpacing: \"0.25em\",\n textTransform: \"uppercase\",\n color: \"#fb923c\",\n}\n\nconst linkStyle: Partial<CSSStyleDeclaration> = {\n marginLeft: \"auto\",\n fontSize: \"13px\",\n color: \"#fed7aa\",\n textDecoration: \"none\",\n}\n\nconst rowStyle: Partial<CSSStyleDeclaration> = {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"10px\",\n padding: \"8px 0\",\n borderTop: \"1px solid rgba(255,255,255,.06)\",\n textDecoration: \"none\",\n color: \"inherit\",\n}\n\nconst chipSettleStyle: Partial<CSSStyleDeclaration> = {\n fontSize: \"9px\",\n letterSpacing: \"0.2em\",\n textTransform: \"uppercase\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n flexShrink: \"0\",\n border: \"1px solid rgba(251,146,60,.4)\",\n color: \"#fdba74\",\n background: \"rgba(251,146,60,.1)\",\n}\n\nconst chipIssueStyle: Partial<CSSStyleDeclaration> = {\n fontSize: \"9px\",\n letterSpacing: \"0.2em\",\n textTransform: \"uppercase\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n flexShrink: \"0\",\n border: \"1px solid rgba(245,158,11,.3)\",\n color: \"rgba(252,211,77,.8)\",\n background: \"rgba(245,158,11,.05)\",\n}\n\nconst metaStyle: Partial<CSSStyleDeclaration> = {\n flex: \"1\",\n minWidth: \"0\",\n display: \"flex\",\n flexDirection: \"column\",\n}\n\nconst txStyle: Partial<CSSStyleDeclaration> = {\n color: \"#d1d5db\",\n fontSize: \"11px\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n}\n\nconst whenStyle: Partial<CSSStyleDeclaration> = {\n color: \"#6b7280\",\n fontSize: \"10px\",\n marginTop: \"1px\",\n}\n\nconst amountStyle: Partial<CSSStyleDeclaration> = {\n color: \"#fed7aa\",\n fontSize: \"12px\",\n flexShrink: \"0\",\n textAlign: \"right\",\n}\n\nconst amountUnitStyle: Partial<CSSStyleDeclaration> = {\n color: \"#6b7280\",\n fontSize: \"9px\",\n textTransform: \"uppercase\",\n marginLeft: \"2px\",\n}\n\nconst emptyStyle: Partial<CSSStyleDeclaration> = {\n padding: \"18px 0\",\n textAlign: \"center\",\n color: \"#6b7280\",\n fontSize: \"11px\",\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import { c as SageActivityResponse, e as SageWidgetOptions } from './types-uzMz6_mP.cjs';
2
+ export { D as DEFAULT_API_BASE, a as DEFAULT_LIMIT, b as DEFAULT_REFRESH_MS, S as SageActivityEvent, d as SageActivityType } from './types-uzMz6_mP.cjs';
3
+
4
+ /**
5
+ * mountSageFeed — framework-agnostic DOM mount for the Sage activity
6
+ * feed. Imports cleanly into any non-React app (Svelte, Vue, plain HTML
7
+ * with bundler, etc.). For React apps prefer the typed component at
8
+ * `@ergoblockchain/sage-widget/react`.
9
+ *
10
+ * Usage:
11
+ *
12
+ * import { mountSageFeed } from "@ergoblockchain/sage-widget/vanilla"
13
+ * const handle = mountSageFeed(document.getElementById("sage")!, {
14
+ * limit: 5,
15
+ * refreshMs: 60000,
16
+ * })
17
+ * // later: handle.destroy()
18
+ *
19
+ * No iframe, no shadow DOM — the markup is rendered into the target
20
+ * element with inline styles. If you need style isolation, embed the
21
+ * canonical iframe variant from `https://www.ergoblockchain.org/agents.js`.
22
+ */
23
+
24
+ interface MountSageFeedHandle {
25
+ /** Trigger a fetch outside the polling interval (e.g. on user action). */
26
+ refresh: () => Promise<void>;
27
+ /** Stop polling and remove all DOM the widget rendered. */
28
+ destroy: () => void;
29
+ /** Most recent successful response, or null if none yet. */
30
+ current: () => SageActivityResponse | null;
31
+ }
32
+ declare function mountSageFeed(target: Element, opts?: SageWidgetOptions): MountSageFeedHandle;
33
+
34
+ export { type MountSageFeedHandle, SageActivityResponse, SageWidgetOptions, mountSageFeed };
@@ -0,0 +1,34 @@
1
+ import { c as SageActivityResponse, e as SageWidgetOptions } from './types-uzMz6_mP.js';
2
+ export { D as DEFAULT_API_BASE, a as DEFAULT_LIMIT, b as DEFAULT_REFRESH_MS, S as SageActivityEvent, d as SageActivityType } from './types-uzMz6_mP.js';
3
+
4
+ /**
5
+ * mountSageFeed — framework-agnostic DOM mount for the Sage activity
6
+ * feed. Imports cleanly into any non-React app (Svelte, Vue, plain HTML
7
+ * with bundler, etc.). For React apps prefer the typed component at
8
+ * `@ergoblockchain/sage-widget/react`.
9
+ *
10
+ * Usage:
11
+ *
12
+ * import { mountSageFeed } from "@ergoblockchain/sage-widget/vanilla"
13
+ * const handle = mountSageFeed(document.getElementById("sage")!, {
14
+ * limit: 5,
15
+ * refreshMs: 60000,
16
+ * })
17
+ * // later: handle.destroy()
18
+ *
19
+ * No iframe, no shadow DOM — the markup is rendered into the target
20
+ * element with inline styles. If you need style isolation, embed the
21
+ * canonical iframe variant from `https://www.ergoblockchain.org/agents.js`.
22
+ */
23
+
24
+ interface MountSageFeedHandle {
25
+ /** Trigger a fetch outside the polling interval (e.g. on user action). */
26
+ refresh: () => Promise<void>;
27
+ /** Stop polling and remove all DOM the widget rendered. */
28
+ destroy: () => void;
29
+ /** Most recent successful response, or null if none yet. */
30
+ current: () => SageActivityResponse | null;
31
+ }
32
+ declare function mountSageFeed(target: Element, opts?: SageWidgetOptions): MountSageFeedHandle;
33
+
34
+ export { type MountSageFeedHandle, SageActivityResponse, SageWidgetOptions, mountSageFeed };
@@ -0,0 +1,258 @@
1
+ // src/types.ts
2
+ var DEFAULT_API_BASE = "https://www.ergoblockchain.org";
3
+ var DEFAULT_LIMIT = 5;
4
+ var DEFAULT_REFRESH_MS = 6e4;
5
+
6
+ // src/api.ts
7
+ async function fetchSageActivity(opts = {}) {
8
+ const base = opts.apiBase ?? DEFAULT_API_BASE;
9
+ const limit = Math.min(Math.max(opts.limit ?? DEFAULT_LIMIT, 1), 25);
10
+ const url = `${base}/api/sage/activity?limit=${limit}`;
11
+ const res = await fetch(url, { signal: opts.signal });
12
+ if (!res.ok) {
13
+ throw new Error(`sage activity ${res.status}`);
14
+ }
15
+ return await res.json();
16
+ }
17
+ function nanoToErg(nano) {
18
+ if (!nano || nano <= 0) return "0";
19
+ const erg = nano / 1e9;
20
+ return erg.toFixed(9).replace(/\.?0+$/, "");
21
+ }
22
+ function relativeTime(ms, now = Date.now()) {
23
+ const diff = Math.max(0, now - ms);
24
+ const sec = Math.floor(diff / 1e3);
25
+ if (sec < 60) return `${sec}s ago`;
26
+ const min = Math.floor(sec / 60);
27
+ if (min < 60) return `${min}m ago`;
28
+ const hr = Math.floor(min / 60);
29
+ if (hr < 24) return `${hr}h ago`;
30
+ const day = Math.floor(hr / 24);
31
+ return `${day}d ago`;
32
+ }
33
+ function receiptUrl(txId, apiBase = DEFAULT_API_BASE) {
34
+ return `${apiBase}/r/sage/${txId}`;
35
+ }
36
+ function explorerUrl(txId, network = "testnet") {
37
+ return network === "testnet" ? `https://testnet.ergoplatform.com/transactions/${txId}` : `https://explorer.ergoplatform.com/transactions/${txId}`;
38
+ }
39
+
40
+ // src/vanilla/mountSageFeed.ts
41
+ function mountSageFeed(target, opts = {}) {
42
+ const apiBase = opts.apiBase;
43
+ const limit = opts.limit ?? DEFAULT_LIMIT;
44
+ const refreshMs = opts.refreshMs ?? DEFAULT_REFRESH_MS;
45
+ let cancelled = false;
46
+ let abort = new AbortController();
47
+ let response = null;
48
+ let pollId;
49
+ let tickId;
50
+ const root = document.createElement("div");
51
+ root.setAttribute("data-sage-widget", "true");
52
+ applyStyles(root, rootStyle);
53
+ target.appendChild(root);
54
+ function render() {
55
+ root.innerHTML = "";
56
+ const header = document.createElement("div");
57
+ applyStyles(header, headerStyle);
58
+ const dot = document.createElement("span");
59
+ applyStyles(dot, dotStyle);
60
+ const label = document.createElement("span");
61
+ applyStyles(label, labelStyle);
62
+ label.textContent = `Live \xB7 Ergo ${response?.network ?? "testnet"}`;
63
+ const link = document.createElement("a");
64
+ link.href = "https://www.ergoblockchain.org/agent-economy#sage-activity";
65
+ link.target = "_blank";
66
+ link.rel = "noopener noreferrer";
67
+ link.textContent = "Sage on chain";
68
+ applyStyles(link, linkStyle);
69
+ header.append(dot, label, link);
70
+ root.appendChild(header);
71
+ const list = document.createElement("div");
72
+ const events = response?.events ?? [];
73
+ if (!response) {
74
+ list.textContent = "Loading\u2026";
75
+ applyStyles(list, emptyStyle);
76
+ } else if (events.length === 0) {
77
+ list.textContent = "No activity yet \u2014 be the first to ask Sage a paid query.";
78
+ applyStyles(list, emptyStyle);
79
+ } else {
80
+ events.forEach((evt) => list.appendChild(renderRow(evt, response.network, apiBase)));
81
+ }
82
+ root.appendChild(list);
83
+ }
84
+ function renderRow(evt, network, base) {
85
+ const isSettle = evt.type === "settlement";
86
+ const a = document.createElement("a");
87
+ a.href = isSettle ? receiptUrl(evt.txId, base) : explorerUrl(evt.txId, network);
88
+ a.target = "_blank";
89
+ a.rel = "noopener noreferrer";
90
+ applyStyles(a, rowStyle);
91
+ const chip = document.createElement("span");
92
+ chip.textContent = isSettle ? "Settled" : evt.type === "issuance" ? "Issued" : "Transfer";
93
+ applyStyles(chip, isSettle ? chipSettleStyle : chipIssueStyle);
94
+ const meta = document.createElement("span");
95
+ applyStyles(meta, metaStyle);
96
+ const tx = document.createElement("span");
97
+ tx.textContent = evt.txId;
98
+ applyStyles(tx, txStyle);
99
+ const when = document.createElement("span");
100
+ when.textContent = `block ${evt.blockHeight.toLocaleString()} \xB7 ${relativeTime(evt.timestamp)}`;
101
+ applyStyles(when, whenStyle);
102
+ meta.append(tx, when);
103
+ const amt = document.createElement("span");
104
+ const value = isSettle ? evt.paymentNanoErg ?? evt.inflowNanoErg : evt.inflowNanoErg;
105
+ applyStyles(amt, amountStyle);
106
+ const ergSpan = document.createElement("span");
107
+ ergSpan.textContent = ` ERG`;
108
+ applyStyles(ergSpan, amountUnitStyle);
109
+ amt.append(nanoToErg(value), ergSpan);
110
+ a.append(chip, meta, amt);
111
+ return a;
112
+ }
113
+ async function load() {
114
+ if (cancelled) return;
115
+ abort.abort();
116
+ abort = new AbortController();
117
+ try {
118
+ const data = await fetchSageActivity({
119
+ apiBase,
120
+ limit,
121
+ signal: abort.signal
122
+ });
123
+ if (cancelled) return;
124
+ response = data;
125
+ render();
126
+ opts.onUpdate?.(data);
127
+ } catch (err) {
128
+ if (cancelled || err.name === "AbortError") return;
129
+ (opts.onError ?? console.warn)(err);
130
+ }
131
+ }
132
+ render();
133
+ load();
134
+ if (refreshMs > 0) pollId = setInterval(load, refreshMs);
135
+ tickId = setInterval(render, 15e3);
136
+ return {
137
+ refresh: load,
138
+ destroy() {
139
+ cancelled = true;
140
+ abort.abort();
141
+ if (pollId) clearInterval(pollId);
142
+ if (tickId) clearInterval(tickId);
143
+ root.remove();
144
+ },
145
+ current: () => response
146
+ };
147
+ }
148
+ function applyStyles(el, styles) {
149
+ Object.assign(el.style, styles);
150
+ }
151
+ var rootStyle = {
152
+ background: "#000",
153
+ color: "#e5e7eb",
154
+ fontFamily: "'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace",
155
+ padding: "14px 16px",
156
+ borderRadius: "14px",
157
+ border: "1px solid rgba(255,255,255,.08)",
158
+ boxSizing: "border-box",
159
+ width: "100%",
160
+ fontSize: "14px",
161
+ lineHeight: "1.4"
162
+ };
163
+ var headerStyle = {
164
+ display: "flex",
165
+ alignItems: "center",
166
+ gap: "10px",
167
+ marginBottom: "10px"
168
+ };
169
+ var dotStyle = {
170
+ width: "8px",
171
+ height: "8px",
172
+ borderRadius: "50%",
173
+ background: "#fb923c",
174
+ flexShrink: "0"
175
+ };
176
+ var labelStyle = {
177
+ fontSize: "10px",
178
+ letterSpacing: "0.25em",
179
+ textTransform: "uppercase",
180
+ color: "#fb923c"
181
+ };
182
+ var linkStyle = {
183
+ marginLeft: "auto",
184
+ fontSize: "13px",
185
+ color: "#fed7aa",
186
+ textDecoration: "none"
187
+ };
188
+ var rowStyle = {
189
+ display: "flex",
190
+ alignItems: "center",
191
+ gap: "10px",
192
+ padding: "8px 0",
193
+ borderTop: "1px solid rgba(255,255,255,.06)",
194
+ textDecoration: "none",
195
+ color: "inherit"
196
+ };
197
+ var chipSettleStyle = {
198
+ fontSize: "9px",
199
+ letterSpacing: "0.2em",
200
+ textTransform: "uppercase",
201
+ padding: "2px 6px",
202
+ borderRadius: "4px",
203
+ flexShrink: "0",
204
+ border: "1px solid rgba(251,146,60,.4)",
205
+ color: "#fdba74",
206
+ background: "rgba(251,146,60,.1)"
207
+ };
208
+ var chipIssueStyle = {
209
+ fontSize: "9px",
210
+ letterSpacing: "0.2em",
211
+ textTransform: "uppercase",
212
+ padding: "2px 6px",
213
+ borderRadius: "4px",
214
+ flexShrink: "0",
215
+ border: "1px solid rgba(245,158,11,.3)",
216
+ color: "rgba(252,211,77,.8)",
217
+ background: "rgba(245,158,11,.05)"
218
+ };
219
+ var metaStyle = {
220
+ flex: "1",
221
+ minWidth: "0",
222
+ display: "flex",
223
+ flexDirection: "column"
224
+ };
225
+ var txStyle = {
226
+ color: "#d1d5db",
227
+ fontSize: "11px",
228
+ overflow: "hidden",
229
+ textOverflow: "ellipsis",
230
+ whiteSpace: "nowrap"
231
+ };
232
+ var whenStyle = {
233
+ color: "#6b7280",
234
+ fontSize: "10px",
235
+ marginTop: "1px"
236
+ };
237
+ var amountStyle = {
238
+ color: "#fed7aa",
239
+ fontSize: "12px",
240
+ flexShrink: "0",
241
+ textAlign: "right"
242
+ };
243
+ var amountUnitStyle = {
244
+ color: "#6b7280",
245
+ fontSize: "9px",
246
+ textTransform: "uppercase",
247
+ marginLeft: "2px"
248
+ };
249
+ var emptyStyle = {
250
+ padding: "18px 0",
251
+ textAlign: "center",
252
+ color: "#6b7280",
253
+ fontSize: "11px"
254
+ };
255
+
256
+ export { DEFAULT_API_BASE, DEFAULT_LIMIT, DEFAULT_REFRESH_MS, mountSageFeed };
257
+ //# sourceMappingURL=vanilla.js.map
258
+ //# sourceMappingURL=vanilla.js.map