@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.
Files changed (101) hide show
  1. package/README.md +46 -9
  2. package/dist/adapters/mech/adapter.d.ts +1 -0
  3. package/dist/adapters/mech/adapter.js +35 -0
  4. package/dist/adapters/mech/adapter.js.map +1 -1
  5. package/dist/api/gather-status.js +1 -0
  6. package/dist/api/gather-status.js.map +1 -1
  7. package/dist/api/server.js +12 -0
  8. package/dist/api/server.js.map +1 -1
  9. package/dist/api/status-build.d.ts +1 -0
  10. package/dist/api/status-build.js.map +1 -1
  11. package/dist/api/status-rollup-build.d.ts +4 -0
  12. package/dist/api/status-rollup-build.js +4 -0
  13. package/dist/api/status-rollup-build.js.map +1 -1
  14. package/dist/bin/jinn-mcp.d.ts +14 -0
  15. package/dist/bin/jinn-mcp.js +19 -0
  16. package/dist/bin/jinn-mcp.js.map +1 -0
  17. package/dist/build-meta.json +1 -1
  18. package/dist/cli/commands/auth.d.ts +3 -0
  19. package/dist/cli/commands/auth.js +146 -0
  20. package/dist/cli/commands/auth.js.map +1 -0
  21. package/dist/cli/commands/bootstrap.js +1 -0
  22. package/dist/cli/commands/bootstrap.js.map +1 -1
  23. package/dist/cli/commands/doctor.js +43 -11
  24. package/dist/cli/commands/doctor.js.map +1 -1
  25. package/dist/cli/commands/fund-requirements.js +69 -1
  26. package/dist/cli/commands/fund-requirements.js.map +1 -1
  27. package/dist/cli/commands/history.js +1 -0
  28. package/dist/cli/commands/history.js.map +1 -1
  29. package/dist/cli/commands/init.js +31 -7
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/keys-backup.js +142 -10
  32. package/dist/cli/commands/keys-backup.js.map +1 -1
  33. package/dist/cli/commands/logs.js +28 -13
  34. package/dist/cli/commands/logs.js.map +1 -1
  35. package/dist/cli/commands/plugin-install.d.ts +3 -0
  36. package/dist/cli/commands/plugin-install.js +799 -0
  37. package/dist/cli/commands/plugin-install.js.map +1 -0
  38. package/dist/cli/commands/quickstart.d.ts +3 -0
  39. package/dist/cli/commands/quickstart.js +236 -0
  40. package/dist/cli/commands/quickstart.js.map +1 -0
  41. package/dist/cli/commands/run.js +6 -0
  42. package/dist/cli/commands/run.js.map +1 -1
  43. package/dist/cli/commands/stop.js +1 -0
  44. package/dist/cli/commands/stop.js.map +1 -1
  45. package/dist/cli/commands/submit-intent.js +11 -1
  46. package/dist/cli/commands/submit-intent.js.map +1 -1
  47. package/dist/cli/commands/update.d.ts +3 -0
  48. package/dist/cli/commands/update.js +154 -0
  49. package/dist/cli/commands/update.js.map +1 -0
  50. package/dist/cli/commands/version.js +15 -1
  51. package/dist/cli/commands/version.js.map +1 -1
  52. package/dist/cli/deployment-digest.js +20 -4
  53. package/dist/cli/deployment-digest.js.map +1 -1
  54. package/dist/cli/index.js +8 -0
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/cli/password.d.ts +15 -0
  57. package/dist/cli/password.js +29 -1
  58. package/dist/cli/password.js.map +1 -1
  59. package/dist/config.d.ts +8 -0
  60. package/dist/config.js +8 -0
  61. package/dist/config.js.map +1 -1
  62. package/dist/daemon/balance-topup-loop.d.ts +40 -0
  63. package/dist/daemon/balance-topup-loop.js +96 -0
  64. package/dist/daemon/balance-topup-loop.js.map +1 -0
  65. package/dist/daemon/daemon.d.ts +7 -0
  66. package/dist/daemon/daemon.js +12 -0
  67. package/dist/daemon/daemon.js.map +1 -1
  68. package/dist/dashboard/index.html +500 -0
  69. package/dist/earning/bootstrap.d.ts +2 -0
  70. package/dist/earning/bootstrap.js +68 -14
  71. package/dist/earning/bootstrap.js.map +1 -1
  72. package/dist/earning/contracts.d.ts +17 -0
  73. package/dist/earning/contracts.js +7 -1
  74. package/dist/earning/contracts.js.map +1 -1
  75. package/dist/earning/faucet.d.ts +15 -0
  76. package/dist/earning/faucet.js +64 -0
  77. package/dist/earning/faucet.js.map +1 -0
  78. package/dist/earning/store.d.ts +5 -0
  79. package/dist/earning/store.js +7 -3
  80. package/dist/earning/store.js.map +1 -1
  81. package/dist/errors/unauthorized-account.d.ts +10 -0
  82. package/dist/errors/unauthorized-account.js +14 -0
  83. package/dist/errors/unauthorized-account.js.map +1 -0
  84. package/dist/main.js +30 -1
  85. package/dist/main.js.map +1 -1
  86. package/dist/mcp/operator-server.d.ts +34 -0
  87. package/dist/mcp/operator-server.js +219 -0
  88. package/dist/mcp/operator-server.js.map +1 -0
  89. package/dist/operator-errors.js +11 -0
  90. package/dist/operator-errors.js.map +1 -1
  91. package/dist/preflight/claude-auth.d.ts +57 -0
  92. package/dist/preflight/claude-auth.js +153 -0
  93. package/dist/preflight/claude-auth.js.map +1 -0
  94. package/dist/runner/claude.js +15 -0
  95. package/dist/runner/claude.js.map +1 -1
  96. package/dist/store/store.js +5 -0
  97. package/dist/store/store.js.map +1 -1
  98. package/dist/tx-retry.js +11 -1
  99. package/dist/tx-retry.js.map +1 -1
  100. package/package.json +12 -3
  101. 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 { viemSendTransactionWithRetry, waitForTransactionReceiptWithRetry, } from '../tx-retry.js';
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
- // Master EOA is the curating agent for this service and pays gas
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
- const reStakeHash = await viemSendTransactionWithRetry(masterWallet, this.publicClient, {
490
- account: masterAccount,
491
- to: addr(this.config.distributorAddress),
492
- data: reStakeData,
493
- gas: 1500000n,
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}`);