@jinn-network/client 0.1.0 → 0.1.1
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 +46 -9
- package/dist/adapters/mech/adapter.d.ts +1 -0
- package/dist/adapters/mech/adapter.js +35 -0
- package/dist/adapters/mech/adapter.js.map +1 -1
- package/dist/api/gather-status.js +1 -0
- package/dist/api/gather-status.js.map +1 -1
- package/dist/api/server.js +12 -0
- package/dist/api/server.js.map +1 -1
- package/dist/api/status-build.d.ts +1 -0
- package/dist/api/status-build.js.map +1 -1
- package/dist/api/status-rollup-build.d.ts +4 -0
- package/dist/api/status-rollup-build.js +4 -0
- package/dist/api/status-rollup-build.js.map +1 -1
- package/dist/bin/jinn-mcp.d.ts +14 -0
- package/dist/bin/jinn-mcp.js +19 -0
- package/dist/bin/jinn-mcp.js.map +1 -0
- package/dist/build-meta.json +1 -1
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.js +146 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/bootstrap.js +1 -0
- package/dist/cli/commands/bootstrap.js.map +1 -1
- package/dist/cli/commands/doctor.js +43 -11
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/fund-requirements.js +69 -1
- package/dist/cli/commands/fund-requirements.js.map +1 -1
- package/dist/cli/commands/history.js +1 -0
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/init.js +31 -7
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/keys-backup.js +142 -10
- package/dist/cli/commands/keys-backup.js.map +1 -1
- package/dist/cli/commands/logs.js +28 -13
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/plugin-install.d.ts +3 -0
- package/dist/cli/commands/plugin-install.js +799 -0
- package/dist/cli/commands/plugin-install.js.map +1 -0
- package/dist/cli/commands/quickstart.d.ts +3 -0
- package/dist/cli/commands/quickstart.js +236 -0
- package/dist/cli/commands/quickstart.js.map +1 -0
- package/dist/cli/commands/run.js +6 -0
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/stop.js +1 -0
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/commands/submit-intent.js +11 -1
- package/dist/cli/commands/submit-intent.js.map +1 -1
- package/dist/cli/commands/update.d.ts +3 -0
- package/dist/cli/commands/update.js +154 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/version.js +15 -1
- package/dist/cli/commands/version.js.map +1 -1
- package/dist/cli/deployment-digest.js +20 -4
- package/dist/cli/deployment-digest.js.map +1 -1
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/password.d.ts +15 -0
- package/dist/cli/password.js +29 -1
- package/dist/cli/password.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon/balance-topup-loop.d.ts +40 -0
- package/dist/daemon/balance-topup-loop.js +96 -0
- package/dist/daemon/balance-topup-loop.js.map +1 -0
- package/dist/daemon/daemon.d.ts +7 -0
- package/dist/daemon/daemon.js +12 -0
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/dashboard/index.html +500 -0
- package/dist/earning/bootstrap.d.ts +2 -0
- package/dist/earning/bootstrap.js +68 -14
- package/dist/earning/bootstrap.js.map +1 -1
- package/dist/earning/contracts.d.ts +17 -0
- package/dist/earning/contracts.js +7 -1
- package/dist/earning/contracts.js.map +1 -1
- package/dist/earning/faucet.d.ts +15 -0
- package/dist/earning/faucet.js +64 -0
- package/dist/earning/faucet.js.map +1 -0
- package/dist/earning/store.d.ts +5 -0
- package/dist/earning/store.js +7 -3
- package/dist/earning/store.js.map +1 -1
- package/dist/errors/unauthorized-account.d.ts +10 -0
- package/dist/errors/unauthorized-account.js +14 -0
- package/dist/errors/unauthorized-account.js.map +1 -0
- package/dist/main.js +30 -1
- package/dist/main.js.map +1 -1
- package/dist/mcp/operator-server.d.ts +34 -0
- package/dist/mcp/operator-server.js +219 -0
- package/dist/mcp/operator-server.js.map +1 -0
- package/dist/operator-errors.js +11 -0
- package/dist/operator-errors.js.map +1 -1
- package/dist/preflight/claude-auth.d.ts +57 -0
- package/dist/preflight/claude-auth.js +153 -0
- package/dist/preflight/claude-auth.js.map +1 -0
- package/dist/runner/claude.js +15 -0
- package/dist/runner/claude.js.map +1 -1
- package/dist/store/store.js +5 -0
- package/dist/store/store.js.map +1 -1
- package/dist/tx-retry.js +11 -1
- package/dist/tx-retry.js.map +1 -1
- package/package.json +12 -3
- package/skills/jinn-operator/SKILL.md +213 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>jinn operator dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body {
|
|
10
|
+
background: #0a0a0f;
|
|
11
|
+
color: #e0e0e0;
|
|
12
|
+
font-family: "JetBrains Mono", "Fira Code", "SF Mono", monospace;
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
line-height: 1.5;
|
|
15
|
+
padding: 20px;
|
|
16
|
+
}
|
|
17
|
+
h1 {
|
|
18
|
+
font-size: 18px;
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
margin-bottom: 16px;
|
|
21
|
+
color: #e0e0e0;
|
|
22
|
+
}
|
|
23
|
+
.grid {
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: 1fr;
|
|
26
|
+
gap: 12px;
|
|
27
|
+
}
|
|
28
|
+
@media (min-width: 900px) {
|
|
29
|
+
.grid { grid-template-columns: 1fr 1fr; }
|
|
30
|
+
.full-width { grid-column: 1 / -1; }
|
|
31
|
+
}
|
|
32
|
+
.card {
|
|
33
|
+
border: 1px solid #1a1a2e;
|
|
34
|
+
border-radius: 6px;
|
|
35
|
+
padding: 14px 16px;
|
|
36
|
+
background: #0f0f18;
|
|
37
|
+
}
|
|
38
|
+
.card-title {
|
|
39
|
+
font-size: 12px;
|
|
40
|
+
text-transform: uppercase;
|
|
41
|
+
letter-spacing: 0.08em;
|
|
42
|
+
color: #888;
|
|
43
|
+
margin-bottom: 10px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Health bar */
|
|
47
|
+
.health-bar {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 12px;
|
|
51
|
+
flex-wrap: wrap;
|
|
52
|
+
}
|
|
53
|
+
.health-dot {
|
|
54
|
+
width: 10px;
|
|
55
|
+
height: 10px;
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
}
|
|
59
|
+
.health-dot.ok { background: #22c55e; }
|
|
60
|
+
.health-dot.warn { background: #f59e0b; }
|
|
61
|
+
.health-dot.error { background: #ef4444; }
|
|
62
|
+
.health-meta {
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
color: #888;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Table */
|
|
68
|
+
table {
|
|
69
|
+
width: 100%;
|
|
70
|
+
border-collapse: collapse;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
}
|
|
73
|
+
th {
|
|
74
|
+
text-align: left;
|
|
75
|
+
font-weight: 500;
|
|
76
|
+
color: #888;
|
|
77
|
+
padding: 4px 8px 4px 0;
|
|
78
|
+
border-bottom: 1px solid #1a1a2e;
|
|
79
|
+
}
|
|
80
|
+
td {
|
|
81
|
+
padding: 4px 8px 4px 0;
|
|
82
|
+
border-bottom: 1px solid #111;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Step badges */
|
|
86
|
+
.badge {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
padding: 1px 6px;
|
|
89
|
+
border-radius: 3px;
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
}
|
|
92
|
+
.badge-complete { background: #22c55e22; color: #22c55e; }
|
|
93
|
+
.badge-staked, .badge-mech_deployed, .badge-service_staked { background: #3b82f622; color: #3b82f6; }
|
|
94
|
+
.badge-awaiting_stake, .badge-awaiting_funding { background: #f59e0b22; color: #f59e0b; }
|
|
95
|
+
.badge-default { background: #88888822; color: #888; }
|
|
96
|
+
|
|
97
|
+
/* KV pairs */
|
|
98
|
+
.kv { display: flex; justify-content: space-between; padding: 3px 0; }
|
|
99
|
+
.kv-key { color: #888; }
|
|
100
|
+
.kv-val { color: #e0e0e0; text-align: right; }
|
|
101
|
+
|
|
102
|
+
/* Warning bg */
|
|
103
|
+
.warn-bg { background: #f59e0b11; border-color: #f59e0b44; }
|
|
104
|
+
.ok-bg { background: #22c55e08; border-color: #22c55e33; }
|
|
105
|
+
.red-bg { background: #ef444411; border-color: #ef444444; }
|
|
106
|
+
|
|
107
|
+
/* Activity list */
|
|
108
|
+
.activity-list {
|
|
109
|
+
max-height: 200px;
|
|
110
|
+
overflow-y: auto;
|
|
111
|
+
font-size: 12px;
|
|
112
|
+
}
|
|
113
|
+
.activity-list div {
|
|
114
|
+
padding: 2px 0;
|
|
115
|
+
border-bottom: 1px solid #111;
|
|
116
|
+
}
|
|
117
|
+
.activity-list .role { color: #3b82f6; }
|
|
118
|
+
|
|
119
|
+
/* Next actions */
|
|
120
|
+
.actions-list { list-style: disc; padding-left: 18px; }
|
|
121
|
+
.actions-list li { padding: 2px 0; font-size: 13px; }
|
|
122
|
+
|
|
123
|
+
.error-msg { color: #ef4444; font-size: 12px; }
|
|
124
|
+
.truncated { font-family: inherit; }
|
|
125
|
+
</style>
|
|
126
|
+
</head>
|
|
127
|
+
<body>
|
|
128
|
+
<h1>jinn operator dashboard</h1>
|
|
129
|
+
<div class="grid">
|
|
130
|
+
|
|
131
|
+
<!-- Health bar -->
|
|
132
|
+
<div id="panel-health" class="card full-width">
|
|
133
|
+
<div class="card-title">health</div>
|
|
134
|
+
<div class="health-bar">
|
|
135
|
+
<span id="health-dot" class="health-dot warn"></span>
|
|
136
|
+
<span id="health-label">connecting...</span>
|
|
137
|
+
<span id="health-meta" class="health-meta"></span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Fleet -->
|
|
142
|
+
<div id="panel-fleet" class="card">
|
|
143
|
+
<div class="card-title">fleet</div>
|
|
144
|
+
<div id="fleet-content">--</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- Master gas -->
|
|
148
|
+
<div id="panel-gas" class="card">
|
|
149
|
+
<div class="card-title">master gas</div>
|
|
150
|
+
<div id="gas-content">--</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<!-- Activity -->
|
|
154
|
+
<div id="panel-activity" class="card">
|
|
155
|
+
<div class="card-title">activity</div>
|
|
156
|
+
<div id="activity-content">--</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Rewards -->
|
|
160
|
+
<div id="panel-rewards" class="card">
|
|
161
|
+
<div class="card-title">rewards</div>
|
|
162
|
+
<div id="rewards-content">--</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<!-- Next actions -->
|
|
166
|
+
<div id="panel-actions" class="card full-width">
|
|
167
|
+
<div class="card-title">next actions</div>
|
|
168
|
+
<div id="actions-content">--</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<script>
|
|
174
|
+
(function() {
|
|
175
|
+
var POLL_INTERVAL = 5000;
|
|
176
|
+
|
|
177
|
+
function $(id) { return document.getElementById(id); }
|
|
178
|
+
|
|
179
|
+
function truncAddr(addr) {
|
|
180
|
+
if (!addr || addr.length < 12) return addr || '--';
|
|
181
|
+
return addr.slice(0, 6) + '...' + addr.slice(-4);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function weiToEth(weiStr) {
|
|
185
|
+
if (!weiStr) return '--';
|
|
186
|
+
try {
|
|
187
|
+
var big = BigInt(weiStr);
|
|
188
|
+
var intPart = big / 10n**18n;
|
|
189
|
+
var fracPart = big % 10n**18n;
|
|
190
|
+
var fracStr = fracPart.toString().padStart(18, '0').slice(0, 4);
|
|
191
|
+
return intPart.toString() + '.' + fracStr;
|
|
192
|
+
} catch(e) { return weiStr; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function stepBadgeClass(step) {
|
|
196
|
+
if (step === 'complete') return 'badge-complete';
|
|
197
|
+
if (['staked','mech_deployed','service_staked'].indexOf(step) !== -1) return 'badge-staked';
|
|
198
|
+
if (['awaiting_stake','awaiting_funding'].indexOf(step) !== -1) return 'badge-awaiting_stake';
|
|
199
|
+
return 'badge-default';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function setHealthIndicator(state, msg) {
|
|
203
|
+
var dot = $('health-dot');
|
|
204
|
+
var label = $('health-label');
|
|
205
|
+
dot.className = 'health-dot ' + (state === 'ok' ? 'ok' : 'error');
|
|
206
|
+
label.textContent = state === 'ok' ? 'connected' : 'error: ' + (msg || 'unknown');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function render(data) {
|
|
210
|
+
// Health
|
|
211
|
+
var dot = $('health-dot');
|
|
212
|
+
var label = $('health-label');
|
|
213
|
+
var meta = $('health-meta');
|
|
214
|
+
|
|
215
|
+
var isShutdown = data.daemon && data.daemon.shutdownState;
|
|
216
|
+
var rpcOk = data.rpc && data.rpc.ok;
|
|
217
|
+
|
|
218
|
+
if (isShutdown) {
|
|
219
|
+
dot.className = 'health-dot warn';
|
|
220
|
+
label.textContent = 'shutting down (' + data.daemon.shutdownState + ')';
|
|
221
|
+
} else if (!rpcOk) {
|
|
222
|
+
dot.className = 'health-dot error';
|
|
223
|
+
label.textContent = 'rpc error' + (data.rpc && data.rpc.error ? ': ' + data.rpc.error : '');
|
|
224
|
+
} else {
|
|
225
|
+
dot.className = 'health-dot ok';
|
|
226
|
+
label.textContent = 'healthy';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var metaParts = [];
|
|
230
|
+
if (data.rpc && data.rpc.chainId) metaParts.push('chain ' + data.rpc.chainId);
|
|
231
|
+
if (data.rpc && data.rpc.blockNumber) metaParts.push('block ' + data.rpc.blockNumber);
|
|
232
|
+
if (data.daemon && data.daemon.timestamp) {
|
|
233
|
+
var d = new Date(data.daemon.timestamp);
|
|
234
|
+
metaParts.push('at ' + d.toLocaleTimeString());
|
|
235
|
+
}
|
|
236
|
+
meta.textContent = metaParts.join(' | ');
|
|
237
|
+
|
|
238
|
+
// Fleet
|
|
239
|
+
renderFleet(data.fleet);
|
|
240
|
+
|
|
241
|
+
// Master gas
|
|
242
|
+
renderGas(data.masterGas);
|
|
243
|
+
|
|
244
|
+
// Activity
|
|
245
|
+
renderActivity(data.activity);
|
|
246
|
+
|
|
247
|
+
// Rewards
|
|
248
|
+
renderRewards(data.rewards, data.earnings);
|
|
249
|
+
|
|
250
|
+
// Next actions
|
|
251
|
+
renderActions(data.nextActions);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function renderFleet(fleet) {
|
|
255
|
+
var el = $('fleet-content');
|
|
256
|
+
el.textContent = '';
|
|
257
|
+
|
|
258
|
+
if (!fleet || !fleet.loaded) {
|
|
259
|
+
el.textContent = 'No fleet loaded';
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (fleet.services.length === 0) {
|
|
264
|
+
el.textContent = 'No services';
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
var tbl = document.createElement('table');
|
|
269
|
+
var thead = document.createElement('thead');
|
|
270
|
+
var hrow = document.createElement('tr');
|
|
271
|
+
['#','step','svcId','safe','mech'].forEach(function(h) {
|
|
272
|
+
var th = document.createElement('th');
|
|
273
|
+
th.textContent = h;
|
|
274
|
+
hrow.appendChild(th);
|
|
275
|
+
});
|
|
276
|
+
thead.appendChild(hrow);
|
|
277
|
+
tbl.appendChild(thead);
|
|
278
|
+
|
|
279
|
+
var tbody = document.createElement('tbody');
|
|
280
|
+
fleet.services.forEach(function(s) {
|
|
281
|
+
var tr = document.createElement('tr');
|
|
282
|
+
|
|
283
|
+
var tdIdx = document.createElement('td');
|
|
284
|
+
tdIdx.textContent = String(s.index);
|
|
285
|
+
tr.appendChild(tdIdx);
|
|
286
|
+
|
|
287
|
+
var tdStep = document.createElement('td');
|
|
288
|
+
var badge = document.createElement('span');
|
|
289
|
+
badge.className = 'badge ' + stepBadgeClass(s.step);
|
|
290
|
+
badge.textContent = s.step;
|
|
291
|
+
tdStep.appendChild(badge);
|
|
292
|
+
tr.appendChild(tdStep);
|
|
293
|
+
|
|
294
|
+
var tdSvc = document.createElement('td');
|
|
295
|
+
tdSvc.textContent = s.serviceId != null ? String(s.serviceId) : '--';
|
|
296
|
+
tr.appendChild(tdSvc);
|
|
297
|
+
|
|
298
|
+
var tdSafe = document.createElement('td');
|
|
299
|
+
tdSafe.textContent = truncAddr(s.safeAddress);
|
|
300
|
+
tdSafe.title = s.safeAddress || '';
|
|
301
|
+
tdSafe.className = 'truncated';
|
|
302
|
+
tr.appendChild(tdSafe);
|
|
303
|
+
|
|
304
|
+
var tdMech = document.createElement('td');
|
|
305
|
+
tdMech.textContent = truncAddr(s.mechAddress);
|
|
306
|
+
tdMech.title = s.mechAddress || '';
|
|
307
|
+
tdMech.className = 'truncated';
|
|
308
|
+
tr.appendChild(tdMech);
|
|
309
|
+
|
|
310
|
+
tbody.appendChild(tr);
|
|
311
|
+
});
|
|
312
|
+
tbl.appendChild(tbody);
|
|
313
|
+
el.appendChild(tbl);
|
|
314
|
+
|
|
315
|
+
var summary = document.createElement('div');
|
|
316
|
+
summary.style.marginTop = '8px';
|
|
317
|
+
summary.style.fontSize = '12px';
|
|
318
|
+
summary.style.color = '#888';
|
|
319
|
+
summary.textContent = fleet.stakedLikeCount + ' staked, ' + fleet.completeCount + ' complete';
|
|
320
|
+
el.appendChild(summary);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function renderGas(gas) {
|
|
324
|
+
var el = $('gas-content');
|
|
325
|
+
var panel = $('panel-gas');
|
|
326
|
+
el.textContent = '';
|
|
327
|
+
panel.className = 'card';
|
|
328
|
+
|
|
329
|
+
if (!gas) { el.textContent = '--'; return; }
|
|
330
|
+
|
|
331
|
+
var runwayLow = gas.runwayDaysExcess !== undefined && Number(gas.runwayDaysExcess) < 3;
|
|
332
|
+
if (runwayLow) {
|
|
333
|
+
panel.className = 'card red-bg';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
var pairs = [
|
|
337
|
+
['address', truncAddr(gas.address)],
|
|
338
|
+
['balance', weiToEth(gas.balanceWei) + ' ETH'],
|
|
339
|
+
['daily est.', weiToEth(gas.dailyEstimateWei) + ' ETH'],
|
|
340
|
+
['runway', gas.runwayDaysExcess !== undefined ? gas.runwayDaysExcess + ' days' : '--'],
|
|
341
|
+
];
|
|
342
|
+
if (gas.error) {
|
|
343
|
+
pairs.push(['error', gas.error]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
pairs.forEach(function(p) {
|
|
347
|
+
var row = document.createElement('div');
|
|
348
|
+
row.className = 'kv';
|
|
349
|
+
var k = document.createElement('span');
|
|
350
|
+
k.className = 'kv-key';
|
|
351
|
+
k.textContent = p[0];
|
|
352
|
+
var v = document.createElement('span');
|
|
353
|
+
v.className = 'kv-val';
|
|
354
|
+
v.textContent = p[1];
|
|
355
|
+
if (p[0] === 'address') v.title = gas.address || '';
|
|
356
|
+
if (p[0] === 'error') v.className = 'kv-val error-msg';
|
|
357
|
+
row.appendChild(k);
|
|
358
|
+
row.appendChild(v);
|
|
359
|
+
el.appendChild(row);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function renderActivity(act) {
|
|
364
|
+
var el = $('activity-content');
|
|
365
|
+
el.textContent = '';
|
|
366
|
+
|
|
367
|
+
if (!act) { el.textContent = '--'; return; }
|
|
368
|
+
|
|
369
|
+
// Counts
|
|
370
|
+
var counts = act.counts || {};
|
|
371
|
+
var keys = Object.keys(counts);
|
|
372
|
+
if (keys.length > 0) {
|
|
373
|
+
keys.forEach(function(k) {
|
|
374
|
+
var row = document.createElement('div');
|
|
375
|
+
row.className = 'kv';
|
|
376
|
+
var kSpan = document.createElement('span');
|
|
377
|
+
kSpan.className = 'kv-key';
|
|
378
|
+
kSpan.textContent = k;
|
|
379
|
+
var vSpan = document.createElement('span');
|
|
380
|
+
vSpan.className = 'kv-val';
|
|
381
|
+
vSpan.textContent = String(counts[k]);
|
|
382
|
+
row.appendChild(kSpan);
|
|
383
|
+
row.appendChild(vSpan);
|
|
384
|
+
el.appendChild(row);
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
var none = document.createElement('div');
|
|
388
|
+
none.style.color = '#888';
|
|
389
|
+
none.style.fontSize = '12px';
|
|
390
|
+
none.textContent = 'no activity recorded';
|
|
391
|
+
el.appendChild(none);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Recent
|
|
395
|
+
var recent = act.recent || [];
|
|
396
|
+
if (recent.length > 0) {
|
|
397
|
+
var hdr = document.createElement('div');
|
|
398
|
+
hdr.style.marginTop = '10px';
|
|
399
|
+
hdr.style.fontSize = '11px';
|
|
400
|
+
hdr.style.color = '#666';
|
|
401
|
+
hdr.textContent = 'recent';
|
|
402
|
+
el.appendChild(hdr);
|
|
403
|
+
|
|
404
|
+
var list = document.createElement('div');
|
|
405
|
+
list.className = 'activity-list';
|
|
406
|
+
recent.forEach(function(r) {
|
|
407
|
+
var item = document.createElement('div');
|
|
408
|
+
var roleSpan = document.createElement('span');
|
|
409
|
+
roleSpan.className = 'role';
|
|
410
|
+
roleSpan.textContent = r.role;
|
|
411
|
+
item.appendChild(roleSpan);
|
|
412
|
+
var idText = document.createTextNode(' ' + truncAddr(r.requestId));
|
|
413
|
+
item.appendChild(idText);
|
|
414
|
+
list.appendChild(item);
|
|
415
|
+
});
|
|
416
|
+
el.appendChild(list);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function renderRewards(rewards, earnings) {
|
|
421
|
+
var el = $('rewards-content');
|
|
422
|
+
el.textContent = '';
|
|
423
|
+
|
|
424
|
+
if (!rewards) { el.textContent = '--'; return; }
|
|
425
|
+
|
|
426
|
+
var pairs = [
|
|
427
|
+
['pending', rewards.pendingStakingRewardsWei ? weiToEth(rewards.pendingStakingRewardsWei) + ' ETH' : (rewards.pendingRewardsError || '--')],
|
|
428
|
+
['claim interval', rewards.claimLoopIntervalMs ? (rewards.claimLoopIntervalMs / 1000) + 's' : 'disabled'],
|
|
429
|
+
['last claim', rewards.lastClaimTickAt || '--'],
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
pairs.forEach(function(p) {
|
|
433
|
+
var row = document.createElement('div');
|
|
434
|
+
row.className = 'kv';
|
|
435
|
+
var k = document.createElement('span');
|
|
436
|
+
k.className = 'kv-key';
|
|
437
|
+
k.textContent = p[0];
|
|
438
|
+
var v = document.createElement('span');
|
|
439
|
+
v.className = 'kv-val';
|
|
440
|
+
v.textContent = p[1];
|
|
441
|
+
if (p[0] === 'pending' && rewards.pendingRewardsError) v.className = 'kv-val error-msg';
|
|
442
|
+
row.appendChild(k);
|
|
443
|
+
row.appendChild(v);
|
|
444
|
+
el.appendChild(row);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (earnings && earnings.hint) {
|
|
448
|
+
var hint = document.createElement('div');
|
|
449
|
+
hint.style.marginTop = '8px';
|
|
450
|
+
hint.style.fontSize = '11px';
|
|
451
|
+
hint.style.color = '#888';
|
|
452
|
+
hint.textContent = earnings.hint;
|
|
453
|
+
el.appendChild(hint);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function renderActions(actions) {
|
|
458
|
+
var el = $('actions-content');
|
|
459
|
+
var panel = $('panel-actions');
|
|
460
|
+
el.textContent = '';
|
|
461
|
+
|
|
462
|
+
if (!actions || actions.length === 0) {
|
|
463
|
+
panel.className = 'card full-width ok-bg';
|
|
464
|
+
el.textContent = 'no urgent actions';
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
var isOnlyNoUrgent = actions.length === 1 && actions[0].toLowerCase().indexOf('no urgent') !== -1;
|
|
469
|
+
panel.className = isOnlyNoUrgent ? 'card full-width ok-bg' : 'card full-width warn-bg';
|
|
470
|
+
|
|
471
|
+
var ul = document.createElement('ul');
|
|
472
|
+
ul.className = 'actions-list';
|
|
473
|
+
actions.forEach(function(a) {
|
|
474
|
+
var li = document.createElement('li');
|
|
475
|
+
li.textContent = a;
|
|
476
|
+
ul.appendChild(li);
|
|
477
|
+
});
|
|
478
|
+
el.appendChild(ul);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function poll() {
|
|
482
|
+
try {
|
|
483
|
+
var res = await fetch('/v1/status');
|
|
484
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
485
|
+
var data = await res.json();
|
|
486
|
+
render(data);
|
|
487
|
+
setHealthIndicator('ok');
|
|
488
|
+
} catch(err) {
|
|
489
|
+
setHealthIndicator('error', err.message);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
494
|
+
poll();
|
|
495
|
+
setInterval(poll, POLL_INTERVAL);
|
|
496
|
+
});
|
|
497
|
+
})();
|
|
498
|
+
</script>
|
|
499
|
+
</body>
|
|
500
|
+
</html>
|
|
@@ -9,6 +9,7 @@ export interface FleetBootstrapperOptions {
|
|
|
9
9
|
earningDir?: string;
|
|
10
10
|
chain?: 'base' | 'base-sepolia';
|
|
11
11
|
rpcUrl?: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
12
13
|
stakingMode?: 'standard' | 'self-bond';
|
|
13
14
|
targetServices?: number;
|
|
14
15
|
testnetL2DeploymentPath?: string;
|
|
@@ -34,6 +35,7 @@ export declare class FleetBootstrapper {
|
|
|
34
35
|
private readonly targetServices;
|
|
35
36
|
private readonly debug;
|
|
36
37
|
private readonly masterEthDailyEstimateWei;
|
|
38
|
+
private readonly env;
|
|
37
39
|
constructor(options?: FleetBootstrapperOptions);
|
|
38
40
|
getStatus(): Promise<FleetState>;
|
|
39
41
|
/**
|
|
@@ -13,7 +13,9 @@ import { createDefaultServiceState } from './types.js';
|
|
|
13
13
|
import { formatBootstrapOperatorMessage, isJinnDebug, } from '../operator-errors.js';
|
|
14
14
|
import { reconcileServiceAgainstChain, } from './reconcile.js';
|
|
15
15
|
import { previousSafeBeingAbandoned, sweepOrphanedServiceFunds, } from './orphan-sweep.js';
|
|
16
|
-
import {
|
|
16
|
+
import { requestTestnetFunding } from './faucet.js';
|
|
17
|
+
import { flattenErrorMessage, viemSendTransactionWithRetry, waitForTransactionReceiptWithRetry, } from '../tx-retry.js';
|
|
18
|
+
import { isUnauthorizedAccountError } from '../errors/unauthorized-account.js';
|
|
17
19
|
import { createJinnPublicClient, createJinnWalletClient } from './viem-clients.js';
|
|
18
20
|
import { isTransientEthReadError } from '../chain-read-errors.js';
|
|
19
21
|
import { nextFleetServiceIndex } from './next-service-index.js';
|
|
@@ -32,9 +34,11 @@ export class FleetBootstrapper {
|
|
|
32
34
|
targetServices;
|
|
33
35
|
debug;
|
|
34
36
|
masterEthDailyEstimateWei;
|
|
37
|
+
env;
|
|
35
38
|
constructor(options = {}) {
|
|
36
39
|
this.store = new FleetStateStore(options.earningDir);
|
|
37
40
|
this.chain = options.chain ?? 'base';
|
|
41
|
+
this.env = options.env ?? process.env;
|
|
38
42
|
this.stakingMode = options.stakingMode ?? 'standard';
|
|
39
43
|
this.targetServices = options.targetServices ?? 1;
|
|
40
44
|
this.debug = options.debug ?? isJinnDebug();
|
|
@@ -105,6 +109,44 @@ export class FleetBootstrapper {
|
|
|
105
109
|
const requiredMasterEth = this.stakingMode === 'standard'
|
|
106
110
|
? this.config.minEoaGasEth
|
|
107
111
|
: SELF_BOND_ETH_PER_SERVICE * BigInt(this.targetServices);
|
|
112
|
+
const autoFaucetEnabled = this.env['JINN_DISABLE_TESTNET_FAUCET'] !== '1';
|
|
113
|
+
if (systemEth < requiredMasterEth) {
|
|
114
|
+
// On testnet, attempt automatic faucet funding before giving up
|
|
115
|
+
if (this.chain === 'base-sepolia' && autoFaucetEnabled) {
|
|
116
|
+
console.error('[fleet-bootstrap] Attempting automatic faucet funding via Coinbase CDP...');
|
|
117
|
+
const faucetResult = await requestTestnetFunding(masterAddress, 'base-sepolia');
|
|
118
|
+
if (faucetResult.ok) {
|
|
119
|
+
console.error(`[fleet-bootstrap] Faucet funded successfully (tx: ${faucetResult.txHash}). Rechecking balance...`);
|
|
120
|
+
// Wait for tx propagation
|
|
121
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
122
|
+
// Re-read balance and continue if sufficient
|
|
123
|
+
const refreshedBalance = await this.publicClient.getBalance({ address: masterAddress });
|
|
124
|
+
let refreshedSystemEth = refreshedBalance;
|
|
125
|
+
if (this.stakingMode === 'self-bond') {
|
|
126
|
+
for (const svc of state.services) {
|
|
127
|
+
if (svc.agent_address) {
|
|
128
|
+
refreshedSystemEth += await this.publicClient.getBalance({
|
|
129
|
+
address: getAddress(svc.agent_address),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (svc.safe_address) {
|
|
133
|
+
refreshedSystemEth += await this.publicClient.getBalance({
|
|
134
|
+
address: getAddress(svc.safe_address),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (refreshedSystemEth >= requiredMasterEth) {
|
|
140
|
+
console.error('[fleet-bootstrap] Faucet funding sufficient. Continuing bootstrap...');
|
|
141
|
+
// Update systemEth/masterBalance for the runway check below
|
|
142
|
+
systemEth = refreshedSystemEth;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.error(`[fleet-bootstrap] Faucet unavailable: ${faucetResult.reason}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
108
150
|
if (systemEth < requiredMasterEth) {
|
|
109
151
|
const shortfall = requiredMasterEth - systemEth;
|
|
110
152
|
const friendly = `Your master wallet needs more ETH (currently ${formatEther(masterBalance)} ETH, need ${formatEther(shortfall)} ETH more). Please send ETH to: ${masterAddress}`;
|
|
@@ -471,27 +513,39 @@ export class FleetBootstrapper {
|
|
|
471
513
|
}
|
|
472
514
|
const svc = state.services.find(s => s.index === index);
|
|
473
515
|
const serviceId = svc.service_id;
|
|
474
|
-
//
|
|
516
|
+
// `distributor.stake()` writes only to guard-scoped curating-agent maps,
|
|
517
|
+
// never to the top-level `mapCuratingAgents` that `reStake()` reads —
|
|
518
|
+
// so plain operators will hit `UnauthorizedAccount` here unless the
|
|
519
|
+
// distributor owner pre-whitelisted them. Catch-and-surface below rather
|
|
520
|
+
// than retry forever.
|
|
475
521
|
const masterAccount = deriveMasterSigner(mnemonic);
|
|
476
522
|
const masterWallet = createJinnWalletClient(this.config.rpcUrl, this.chain, masterAccount);
|
|
477
|
-
// Use distributor.reStake() — a purpose-built entry point for evicted services.
|
|
478
|
-
// It calls IStaking.unstake() → IStaking.stake() on the staking proxy without
|
|
479
|
-
// touching the service lifecycle (no terminate/unbond/recoverAccess). The service
|
|
480
|
-
// stays in Deployed state, the Safe owners are untouched, and the same service
|
|
481
|
-
// ID, Safe address, and mech address are preserved across the eviction.
|
|
482
|
-
// Authorization: master EOA is a curating agent (recorded when it called stake()).
|
|
483
523
|
const reStakeData = encodeFunctionData({
|
|
484
524
|
abi: STOLAS_DISTRIBUTOR_ABI,
|
|
485
525
|
functionName: 'reStake',
|
|
486
526
|
args: [this.config.stakingContract, BigInt(serviceId)],
|
|
487
527
|
});
|
|
488
528
|
console.error(`[fleet-bootstrap] Service ${index}: calling distributor.reStake() for evicted service ${serviceId}`);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
529
|
+
let reStakeHash;
|
|
530
|
+
try {
|
|
531
|
+
reStakeHash = await viemSendTransactionWithRetry(masterWallet, this.publicClient, {
|
|
532
|
+
account: masterAccount,
|
|
533
|
+
to: addr(this.config.distributorAddress),
|
|
534
|
+
data: reStakeData,
|
|
535
|
+
gas: 1500000n,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
const message = flattenErrorMessage(err);
|
|
540
|
+
if (isUnauthorizedAccountError(message)) {
|
|
541
|
+
throw new Error(`Service ${index} (service_id ${serviceId}) is evicted on the staking proxy and reStake is gated by the distributor's curating-agent whitelist. ` +
|
|
542
|
+
`Master EOA ${masterAccount.address} is not authorized. To recover: ` +
|
|
543
|
+
`(a) have the distributor owner call setCuratingAgents([${masterAccount.address}], [true]) on ${this.config.distributorAddress}, then re-run jinn bootstrap; or ` +
|
|
544
|
+
`(b) abandon this service and provision a new one (stOLAS bond stays with the old Safe until it's manually swept). ` +
|
|
545
|
+
`reStake revert: ${message}`);
|
|
546
|
+
}
|
|
547
|
+
throw err;
|
|
548
|
+
}
|
|
495
549
|
const receipt = await waitForTransactionReceiptWithRetry(this.publicClient, reStakeHash);
|
|
496
550
|
if (receipt.status !== 'success') {
|
|
497
551
|
throw new Error(`reStake failed for service ${index}: ${reStakeHash}`);
|