@clawlabz/clawnetwork 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.ts +408 -104
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -15,6 +15,7 @@ const DEFAULT_P2P_PORT = 9711
15
15
  const DEFAULT_NETWORK = 'mainnet'
16
16
  const DEFAULT_SYNC_MODE = 'light'
17
17
  const DEFAULT_HEALTH_CHECK_SECONDS = 30
18
+ const MIN_NODE_VERSION = '0.4.19'
18
19
  const DEFAULT_UI_PORT = 19877
19
20
  const MAX_RESTART_ATTEMPTS = 3
20
21
 
@@ -227,6 +228,16 @@ function getBinaryVersion(binaryPath: string): string | null {
227
228
  } catch { return null }
228
229
  }
229
230
 
231
+ function isVersionOlder(current: string, required: string): boolean {
232
+ const c = current.split('.').map(Number)
233
+ const r = required.split('.').map(Number)
234
+ for (let i = 0; i < 3; i++) {
235
+ if ((c[i] || 0) < (r[i] || 0)) return true
236
+ if ((c[i] || 0) > (r[i] || 0)) return false
237
+ }
238
+ return false
239
+ }
240
+
230
241
  function detectPlatformTarget(): string {
231
242
  const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : 'linux'
232
243
  const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64'
@@ -807,24 +818,28 @@ function buildUiHtml(cfg: PluginConfig): string {
807
818
  <meta charset="UTF-8">
808
819
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
809
820
  <title>ClawNetwork Node Dashboard</title>
810
- <link rel="icon" href="https://cdn.clawlabz.xyz/brand/favicon.png">
821
+ <link rel="icon" href="https://explorer.clawlabz.xyz/favicon.png">
822
+ <link rel="preconnect" href="https://fonts.googleapis.com">
823
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
811
824
  <style>
812
825
  :root {
813
- --bg: #0a0a12;
814
- --bg-panel: #12121f;
815
- --border: #1e1e3a;
816
- --accent: #00ccff;
817
- --accent-dim: rgba(0, 204, 255, 0.15);
818
- --green: #00ff88;
819
- --green-dim: rgba(0, 255, 136, 0.15);
820
- --purple: #8b5cf6;
821
- --text: #e0e0f0;
822
- --text-dim: #666688;
823
- --danger: #ff4455;
824
- --font: system-ui, -apple-system, sans-serif;
825
- --font-mono: 'SF Mono', 'Fira Code', Consolas, monospace;
826
+ --bg: #0a0705;
827
+ --bg-panel: #140e0a;
828
+ --border: #2a1c14;
829
+ --accent: #F96706;
830
+ --accent-dim: rgba(249, 103, 6, 0.15);
831
+ --accent-light: #FF8C3A;
832
+ --purple: #a855f7;
833
+ --purple-dim: rgba(168, 85, 247, 0.15);
834
+ --green: #22c55e;
835
+ --green-dim: rgba(34, 197, 94, 0.15);
836
+ --text: #fffaf5;
837
+ --text-dim: #8892a0;
838
+ --danger: #ef4444;
839
+ --font: 'Space Grotesk', system-ui, -apple-system, sans-serif;
840
+ --font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
826
841
  --radius: 10px;
827
- --shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
842
+ --shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
828
843
  }
829
844
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
830
845
  body { background: var(--bg); color: var(--text); font-family: var(--font); line-height: 1.6; min-height: 100vh; }
@@ -834,16 +849,17 @@ function buildUiHtml(cfg: PluginConfig): string {
834
849
  .header { background: var(--bg-panel); border-bottom: 1px solid var(--border); padding: 16px 0; position: sticky; top: 0; z-index: 100; }
835
850
  .header .container { display: flex; align-items: center; justify-content: space-between; }
836
851
  .logo { font-size: 22px; font-weight: 800; letter-spacing: -0.5px; }
837
- .logo-claw { color: var(--accent); }
838
- .logo-net { color: var(--green); }
852
+ .logo-claw { color: #ffffff; }
853
+ .logo-net { color: var(--accent); }
839
854
  .header-badge { font-size: 11px; background: var(--accent-dim); color: var(--accent); padding: 2px 8px; border-radius: 4px; }
840
855
 
841
- .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 24px 0; }
856
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
842
857
  .stat-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
843
858
  .stat-label { font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; }
844
859
  .stat-value { font-size: 28px; font-weight: 700; font-family: var(--font-mono); margin-top: 4px; }
845
860
  .stat-value.green { color: var(--green); }
846
861
  .stat-value.accent { color: var(--accent); }
862
+ .stat-value.purple { color: var(--purple); }
847
863
  .stat-value.danger { color: var(--danger); }
848
864
 
849
865
  .panel { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin: 16px 0; }
@@ -858,13 +874,23 @@ function buildUiHtml(cfg: PluginConfig): string {
858
874
  .status-dot.offline { background: var(--danger); }
859
875
  .status-dot.syncing { background: #ffaa00; animation: pulse 1.5s infinite; }
860
876
 
861
- .btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text); font-size: 13px; cursor: pointer; transition: 0.2s; }
877
+ .btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text); font-size: 13px; cursor: pointer; transition: 0.2s; font-family: var(--font); }
862
878
  .btn:hover { border-color: var(--accent); color: var(--accent); }
863
879
  .btn.danger:hover { border-color: var(--danger); color: var(--danger); }
864
880
  .btn.primary { background: var(--accent-dim); border-color: var(--accent); color: var(--accent); }
865
- .btn-group { display: flex; gap: 8px; margin: 16px 0; flex-wrap: wrap; }
881
+ .node-controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--border); }
882
+ .node-controls .spacer { flex: 1; }
866
883
 
867
- .logs-box { background: #080810; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: var(--font-mono); font-size: 12px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; color: var(--text-dim); line-height: 1.8; }
884
+ .wallet-hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
885
+ .wallet-balance { font-size: 36px; font-weight: 800; font-family: var(--font-mono); color: var(--accent); letter-spacing: -1px; line-height: 1; }
886
+ .wallet-balance-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
887
+ .wallet-addr-wrap { flex: 1; min-width: 0; }
888
+ .wallet-addr-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
889
+ .wallet-addr { font-family: var(--font-mono); font-size: 12px; background: var(--bg); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); word-break: break-all; display: flex; align-items: center; gap: 8px; }
890
+ .copy-btn { background: none; border: none; color: var(--accent); cursor: pointer; font-size: 12px; padding: 2px 8px; border-radius: 4px; border: 1px solid var(--accent); white-space: nowrap; font-family: var(--font); transition: 0.2s; }
891
+ .copy-btn:hover { background: var(--accent-dim); }
892
+
893
+ .logs-box { background: #060402; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: var(--font-mono); font-size: 12px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; color: var(--text-dim); line-height: 1.8; }
868
894
 
869
895
  .wallet-addr { font-family: var(--font-mono); font-size: 13px; background: var(--bg); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); word-break: break-all; display: flex; align-items: center; gap: 8px; }
870
896
  .copy-btn { background: none; border: none; color: var(--accent); cursor: pointer; font-size: 14px; padding: 2px 6px; }
@@ -873,7 +899,7 @@ function buildUiHtml(cfg: PluginConfig): string {
873
899
  .toast { position: fixed; bottom: 24px; right: 24px; background: var(--bg-panel); border: 1px solid var(--accent); color: var(--accent); padding: 12px 20px; border-radius: 8px; font-size: 13px; opacity: 0; transition: 0.3s; z-index: 1000; }
874
900
  .toast.show { opacity: 1; }
875
901
 
876
- .quick-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin: 16px 0; }
902
+ .quick-actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0 0; }
877
903
  .quick-action { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--text); }
878
904
  .quick-action:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
879
905
  .quick-action .qa-icon { font-size: 18px; width: 28px; text-align: center; }
@@ -890,77 +916,98 @@ function buildUiHtml(cfg: PluginConfig): string {
890
916
  .modal-actions { display: flex; gap: 8px; margin-top: 16px; justify-content: flex-end; }
891
917
  .modal-close { background: none; border: 1px solid var(--border); color: var(--text-dim); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
892
918
  .modal-close:hover { border-color: var(--text); color: var(--text); }
919
+ .modal-input { width: 100%; box-sizing: border-box; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; font-size: 14px; color: var(--text); font-family: var(--font-mono); outline: none; margin-top: 4px; }
920
+ .modal-input:focus { border-color: var(--accent); }
921
+ .modal-hint { font-size: 12px; color: var(--text-dim); margin-top: 6px; line-height: 1.5; }
922
+
923
+ .upgrade-banner { padding: 14px 16px; border-radius: var(--radius); margin-bottom: 16px; font-size: 13px; line-height: 1.6; display: flex; justify-content: space-between; align-items: center; gap: 12px; }
924
+ .upgrade-banner.recommended { background: rgba(255, 170, 0, 0.1); border: 1px solid rgba(255, 170, 0, 0.3); color: #ffaa00; }
925
+ .upgrade-banner.recommended .upgrade-text { flex: 1; }
926
+ .upgrade-banner.recommended .upgrade-actions { display: flex; gap: 8px; }
927
+ .upgrade-banner.required { background: rgba(255, 140, 0, 0.1); border: 1px solid rgba(255, 140, 0, 0.3); color: #ff8c3a; }
928
+ .upgrade-banner.required .upgrade-text { flex: 1; }
929
+ .upgrade-banner.required .upgrade-actions { display: flex; gap: 8px; }
930
+ .upgrade-banner.critical { background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.4); color: var(--danger); width: 100%; margin-left: calc(-20px - 1px); margin-right: calc(-20px - 1px); padding: 16px calc(20px + 1px); border-radius: 0; font-weight: 600; }
931
+ .upgrade-banner.critical .upgrade-text { flex: 1; }
932
+ .upgrade-banner.critical .upgrade-actions { display: flex; gap: 8px; }
933
+ .upgrade-btn { background: var(--accent); color: var(--bg); border: none; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; font-weight: 600; transition: 0.2s; }
934
+ .upgrade-btn:hover { opacity: 0.85; }
935
+ .upgrade-dismiss { background: none; border: 1px solid currentColor; color: currentColor; padding: 4px 10px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: 0.2s; }
936
+ .upgrade-dismiss:hover { opacity: 0.7; }
893
937
  </style>
894
938
  </head>
895
939
  <body>
896
940
  <header class="header">
897
941
  <div class="container">
898
942
  <div style="display:flex;align-items:center;gap:14px">
899
- <div class="logo"><span class="logo-claw">Claw</span><span class="logo-net">Network</span></div>
943
+ <div class="logo"><img src="https://explorer.clawlabz.xyz/favicon.png" style="width:28px;height:28px;border-radius:6px;vertical-align:middle;margin-right:8px"><span class="logo-claw">Claw</span><span class="logo-net">Network</span></div>
900
944
  <span class="header-badge">Node Dashboard</span>
901
945
  </div>
902
946
  <span id="lastUpdate" style="font-size:12px;color:var(--text-dim)"></span>
903
947
  </div>
904
948
  </header>
905
949
 
906
- <main class="container" style="padding-top:8px;padding-bottom:40px">
907
- <div class="stats-grid">
908
- <div class="stat-card">
909
- <div class="stat-label">Status</div>
910
- <div class="stat-value" id="statusValue"><span class="status-dot offline"></span>Offline</div>
911
- </div>
912
- <div class="stat-card">
913
- <div class="stat-label">Block Height</div>
914
- <div class="stat-value accent" id="heightValue">—</div>
915
- </div>
916
- <div class="stat-card">
917
- <div class="stat-label">Peers</div>
918
- <div class="stat-value" id="peersValue">—</div>
919
- </div>
920
- <div class="stat-card">
921
- <div class="stat-label">Uptime</div>
922
- <div class="stat-value" id="uptimeValue">—</div>
923
- </div>
924
- </div>
950
+ <main class="container" style="padding-top:16px;padding-bottom:40px">
925
951
 
926
- <div class="btn-group">
927
- <button class="btn primary" onclick="doAction('start')">Start Node</button>
928
- <button class="btn danger" onclick="doAction('stop')">Stop Node</button>
929
- <button class="btn" onclick="doAction('faucet')">Faucet (testnet)</button>
930
- <button class="btn" onclick="refreshLogs()">Refresh Logs</button>
931
- </div>
952
+ <div id="upgradeBanner" style="display:none" class="upgrade-banner"></div>
932
953
 
933
954
  <div class="panel">
934
- <div class="panel-title">Wallet</div>
935
- <div id="walletInfo">Loading...</div>
936
- <div class="quick-actions" id="walletActions" style="display:none">
937
- <div class="quick-action" onclick="copyAddress()">
938
- <span class="qa-icon">&#x1F4CB;</span>
939
- <div><div class="qa-label">Copy Address</div><div class="qa-hint">Share to receive CLAW</div></div>
940
- </div>
941
- <div class="quick-action" onclick="importToExtension()" id="qaImportExt" style="display:none">
942
- <span class="qa-icon">&#x1F517;</span>
943
- <div><div class="qa-label">Import to Extension</div><div class="qa-hint">One-click import to browser wallet</div></div>
955
+ <div class="panel-title">Node</div>
956
+ <div class="stats-grid" style="margin:0 0 4px">
957
+ <div class="stat-card">
958
+ <div class="stat-label">Status</div>
959
+ <div class="stat-value" id="statusValue"><span class="status-dot offline"></span>Offline</div>
944
960
  </div>
945
- <div class="quick-action warn" onclick="showExportKey()">
946
- <span class="qa-icon">&#x1F511;</span>
947
- <div><div class="qa-label">Export Private Key</div><div class="qa-hint">Manual copy for backup</div></div>
961
+ <div class="stat-card">
962
+ <div class="stat-label">Block Height</div>
963
+ <div class="stat-value accent" id="heightValue">—</div>
948
964
  </div>
949
- <div class="quick-action" onclick="openExplorer()">
950
- <span class="qa-icon">&#x1F50D;</span>
951
- <div><div class="qa-label">View on Explorer</div><div class="qa-hint">Transaction history</div></div>
965
+ <div class="stat-card">
966
+ <div class="stat-label">Peers</div>
967
+ <div class="stat-value" id="peersValue">—</div>
952
968
  </div>
953
- <div class="quick-action" onclick="transferFromDashboard()">
954
- <span class="qa-icon">&#x1F4B8;</span>
955
- <div><div class="qa-label">Transfer CLAW</div><div class="qa-hint">Send to any address</div></div>
969
+ <div class="stat-card">
970
+ <div class="stat-label">Uptime</div>
971
+ <div class="stat-value" id="uptimeValue">—</div>
956
972
  </div>
957
- <div class="quick-action" onclick="registerAgentFromDashboard()">
958
- <span class="qa-icon">&#x1F916;</span>
959
- <div><div class="qa-label">Register Agent</div><div class="qa-hint">On-chain identity</div></div>
973
+ </div>
974
+ <div class="node-controls">
975
+ <button class="btn primary" id="startBtn" onclick="doAction('start')">&#x25B6; Start Node</button>
976
+ <button class="btn danger" id="stopBtn" onclick="doAction('stop')">&#x25A0; Stop Node</button>
977
+ </div>
978
+ </div>
979
+
980
+ <div class="panel" id="walletPanel">
981
+ <div class="panel-title">Wallet</div>
982
+ <div id="walletEmpty" style="color:var(--text-dim);font-size:13px">No wallet yet — start the node to generate one</div>
983
+ <div id="walletLoaded" style="display:none">
984
+ <div class="wallet-hero">
985
+ <div>
986
+ <div class="wallet-balance-label">Balance</div>
987
+ <div class="wallet-balance" id="walletBalance">—</div>
988
+ </div>
989
+ <div class="wallet-addr-wrap">
990
+ <div class="wallet-addr-label">Address</div>
991
+ <div class="wallet-addr"><span id="walletAddrText" style="flex:1;min-width:0;word-break:break-all"></span><button class="copy-btn" onclick="copyText(cachedAddress)">Copy</button></div>
992
+ </div>
960
993
  </div>
961
- <div class="quick-action" onclick="openFaucet()">
962
- <span class="qa-icon">&#x1F6B0;</span>
963
- <div><div class="qa-label">Open Faucet</div><div class="qa-hint">Get testnet CLAW</div></div>
994
+ <div class="quick-actions" id="walletActions">
995
+ <div class="quick-action" onclick="importToExtension()" id="qaImportExt">
996
+ <span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
997
+ <div><div class="qa-label">Import to Extension</div><div class="qa-hint" id="qaImportHint">One-click import to browser wallet</div></div>
998
+ </div>
999
+ <div class="quick-action warn" onclick="showExportKey()">
1000
+ <span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/></svg></span>
1001
+ <div><div class="qa-label">Export Private Key</div><div class="qa-hint">Manual copy for backup</div></div>
1002
+ </div>
1003
+ <div class="quick-action" onclick="openExplorer()">
1004
+ <span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></span>
1005
+ <div><div class="qa-label">View on Explorer</div><div class="qa-hint">Transaction history</div></div>
1006
+ </div>
1007
+ <div class="quick-action" id="qaRegister" onclick="handleRegisterAgent()">
1008
+ <span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M15 2v2M9 2v2M15 20v2M9 20v2M2 15h2M2 9h2M20 15h2M20 9h2"/></svg></span>
1009
+ <div><div class="qa-label" id="qaRegisterLabel">Register Agent</div><div class="qa-hint" id="qaRegisterHint">On-chain identity</div></div>
1010
+ </div>
964
1011
  </div>
965
1012
  </div>
966
1013
  </div>
@@ -971,13 +1018,57 @@ function buildUiHtml(cfg: PluginConfig): string {
971
1018
  </div>
972
1019
 
973
1020
  <div class="panel">
974
- <div class="panel-title">Recent Logs</div>
1021
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
1022
+ <div class="panel-title" style="margin-bottom:0">Recent Logs</div>
1023
+ <button class="btn" style="font-size:12px;padding:5px 12px" onclick="refreshLogs()">&#x21BB; Refresh</button>
1024
+ </div>
975
1025
  <div class="logs-box" id="logsBox">Loading...</div>
976
1026
  </div>
977
1027
  </main>
978
1028
 
1029
+ <footer style="border-top:1px solid var(--border);padding:24px 0;margin-top:16px">
1030
+ <div class="container" style="display:flex;flex-wrap:wrap;gap:20px;align-items:center;justify-content:space-between">
1031
+ <div style="display:flex;align-items:center;gap:8px">
1032
+ <img src="https://explorer.clawlabz.xyz/favicon.png" style="width:18px;height:18px;border-radius:4px;opacity:0.7">
1033
+ <span style="font-size:12px;color:var(--text-dim)">© 2026 ClawLabz</span>
1034
+ </div>
1035
+ <div style="display:flex;gap:20px;flex-wrap:wrap">
1036
+ <a href="https://chain.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Chain</a>
1037
+ <a href="https://explorer.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Explorer</a>
1038
+ <a href="https://chrome.google.com/webstore/search/ClawNetwork" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Wallet Extension</a>
1039
+ </div>
1040
+ </div>
1041
+ </footer>
1042
+
979
1043
  <div class="toast" id="toast"></div>
980
1044
 
1045
+ <div class="modal-overlay" id="registerModal" onclick="if(event.target===this)closeRegisterModal()">
1046
+ <div class="modal">
1047
+ <div class="modal-title">Register Agent</div>
1048
+ <p style="font-size:13px;color:var(--text-dim);margin:0 0 12px">Register your wallet as an AI Agent on ClawNetwork. The name is your on-chain identity — it does not need to be unique globally (the wallet address is what's unique). Registration is gas-free on mainnet.</p>
1049
+ <input id="registerNameInput" class="modal-input" type="text" placeholder="my-agent-name" maxlength="32" onkeydown="if(event.key==='Enter')submitRegisterAgent()" />
1050
+ <div class="modal-hint">Allowed: letters, numbers, hyphens, underscores. Max 32 chars.</div>
1051
+ <div class="modal-actions">
1052
+ <button class="modal-close" onclick="closeRegisterModal()">Cancel</button>
1053
+ <button class="btn primary" onclick="submitRegisterAgent()">Register</button>
1054
+ </div>
1055
+ </div>
1056
+ </div>
1057
+
1058
+ <div class="modal-overlay" id="installModal" onclick="if(event.target===this)closeInstallModal()">
1059
+ <div class="modal">
1060
+ <div class="modal-title">Install ClawNetwork Wallet</div>
1061
+ <p style="font-size:13px;color:var(--text-dim);margin:0 0 16px;line-height:1.6">The ClawNetwork browser extension is not detected. Install it first, then click Import to Extension to import your node wallet.</p>
1062
+ <div style="display:flex;gap:10px;flex-direction:column">
1063
+ <a href="https://chrome.google.com/webstore/search/ClawNetwork" target="_blank" class="btn primary" style="text-decoration:none;justify-content:center;padding:10px 16px">Open Chrome Web Store</a>
1064
+ <a href="https://chain.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;text-align:center" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Learn more at chain.clawlabz.xyz →</a>
1065
+ </div>
1066
+ <div class="modal-actions" style="margin-top:16px">
1067
+ <button class="modal-close" onclick="closeInstallModal()">Close</button>
1068
+ </div>
1069
+ </div>
1070
+ </div>
1071
+
981
1072
  <div class="modal-overlay" id="exportModal" onclick="if(event.target===this)closeExportModal()">
982
1073
  <div class="modal">
983
1074
  <div class="modal-title">Export Private Key</div>
@@ -1006,6 +1097,7 @@ function buildUiHtml(cfg: PluginConfig): string {
1006
1097
  let cachedAddress = '';
1007
1098
  let cachedNetwork = '';
1008
1099
  let cachedKey = '';
1100
+ let cachedAgentName = ''; // '' = not registered, string = registered name
1009
1101
 
1010
1102
  function copyText(text) {
1011
1103
  navigator.clipboard.writeText(text).then(() => toast('Copied!')).catch(() => {});
@@ -1050,16 +1142,13 @@ function buildUiHtml(cfg: PluginConfig): string {
1050
1142
  window.open('https://chain.clawlabz.xyz/faucet', '_blank');
1051
1143
  }
1052
1144
 
1053
- // Detect ClawNetwork extension provider
1145
+ // Detect ClawNetwork extension provider (for enhanced flow when available)
1054
1146
  let hasExtension = false;
1055
1147
  function checkExtension() {
1056
1148
  if (window.clawNetwork && window.clawNetwork.isClawNetwork) {
1057
1149
  hasExtension = true;
1058
- const el = document.getElementById('qaImportExt');
1059
- if (el) el.style.display = '';
1060
1150
  }
1061
1151
  }
1062
- // Check immediately and after a short delay (extension injects at document_start)
1063
1152
  checkExtension();
1064
1153
  setTimeout(checkExtension, 1000);
1065
1154
  setTimeout(checkExtension, 3000);
@@ -1083,7 +1172,10 @@ function buildUiHtml(cfg: PluginConfig): string {
1083
1172
  } catch (e) { /* fall through to provider method */ }
1084
1173
  }
1085
1174
  // Fallback: use window.clawNetwork provider
1086
- if (!window.clawNetwork) { toast('ClawNetwork extension not detected. Install it first.'); return; }
1175
+ if (!window.clawNetwork) {
1176
+ document.getElementById('installModal').classList.add('open');
1177
+ return;
1178
+ }
1087
1179
  toast('Connecting to extension...');
1088
1180
  try {
1089
1181
  await window.clawNetwork.request({ method: 'claw_requestAccounts' });
@@ -1145,21 +1237,45 @@ function buildUiHtml(cfg: PluginConfig): string {
1145
1237
  setTimeout(fetchStatus, 3000);
1146
1238
  }
1147
1239
 
1148
- async function registerAgentFromDashboard() {
1149
- const name = prompt('Agent name (alphanumeric, max 32 chars):', 'openclaw-agent');
1150
- if (!name) return;
1240
+ function closeInstallModal() {
1241
+ document.getElementById('installModal').classList.remove('open');
1242
+ }
1243
+
1244
+ function handleRegisterAgent() {
1245
+ if (cachedAgentName) {
1246
+ toast('Already registered as "' + cachedAgentName + '"');
1247
+ return;
1248
+ }
1249
+ openRegisterModal();
1250
+ }
1251
+
1252
+ function openRegisterModal() {
1253
+ document.getElementById('registerNameInput').value = '';
1254
+ document.getElementById('registerModal').classList.add('open');
1255
+ setTimeout(() => document.getElementById('registerNameInput').focus(), 50);
1256
+ }
1257
+
1258
+ function closeRegisterModal() {
1259
+ document.getElementById('registerModal').classList.remove('open');
1260
+ }
1261
+
1262
+ async function submitRegisterAgent() {
1263
+ const raw = document.getElementById('registerNameInput').value.trim();
1264
+ const name = raw.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 32);
1265
+ if (!name) { toast('Please enter an agent name'); return; }
1266
+ closeRegisterModal();
1151
1267
  if (window.clawNetwork) {
1152
1268
  try {
1153
1269
  toast('Approve registration in extension...');
1154
1270
  await window.clawNetwork.request({ method: 'claw_requestAccounts' });
1155
1271
  await window.clawNetwork.request({ method: 'claw_registerAgent', params: [name] });
1156
- toast('Agent registered!');
1272
+ toast('Agent "' + name + '" registered!');
1157
1273
  } catch (e) { toast('Registration failed: ' + (e.message || e)); }
1158
1274
  } else {
1159
1275
  try {
1160
1276
  const res = await fetch(API + '/api/agent/register', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({name}) });
1161
1277
  const data = await res.json();
1162
- toast(data.ok ? 'Agent registered!' : 'Error: ' + data.error);
1278
+ toast(data.ok ? 'Agent "' + name + '" registered!' : 'Error: ' + data.error);
1163
1279
  } catch (e) { toast('Registration failed: ' + e.message); }
1164
1280
  }
1165
1281
  }
@@ -1170,16 +1286,20 @@ function buildUiHtml(cfg: PluginConfig): string {
1170
1286
  const data = await res.json();
1171
1287
  renderStatus(data);
1172
1288
  document.getElementById('lastUpdate').textContent = 'Updated: ' + new Date().toLocaleTimeString();
1173
- } catch (e) { console.error(e); }
1289
+ } catch (e) {
1290
+ console.error(e);
1291
+ renderStatus({ running: false, blockHeight: null, peerCount: null, walletAddress: '', network: 'mainnet', syncMode: 'light', rpcUrl: 'http://localhost:19877', pluginVersion: '0.1.1', restartCount: 0, dataDir: '', balance: '', syncing: false, uptimeFormatted: '—', pid: null });
1292
+ }
1174
1293
  }
1175
1294
 
1176
1295
  function renderStatus(s) {
1177
1296
  const statusEl = document.getElementById('statusValue');
1178
1297
  if (s.running) {
1179
- const dotClass = s.syncing ? 'syncing' : 'online';
1180
- const label = s.syncing ? 'Syncing' : 'Online';
1298
+ let dotClass = 'online', label = 'Online';
1299
+ if (s.syncing && s.peerless) { dotClass = 'syncing'; label = 'No Peers'; }
1300
+ else if (s.syncing) { dotClass = 'syncing'; label = 'Syncing'; }
1181
1301
  statusEl.innerHTML = '<span class="status-dot ' + dotClass + '"></span>' + label;
1182
- statusEl.className = 'stat-value green';
1302
+ statusEl.className = 'stat-value' + (s.syncing ? '' : ' green');
1183
1303
  } else {
1184
1304
  statusEl.innerHTML = '<span class="status-dot offline"></span>Offline';
1185
1305
  statusEl.className = 'stat-value danger';
@@ -1188,23 +1308,86 @@ function buildUiHtml(cfg: PluginConfig): string {
1188
1308
  document.getElementById('heightValue').textContent = s.blockHeight !== null ? s.blockHeight.toLocaleString() : '—';
1189
1309
  document.getElementById('peersValue').textContent = s.peerCount !== null ? s.peerCount : '—';
1190
1310
  document.getElementById('uptimeValue').textContent = s.uptimeFormatted || '—';
1311
+ document.getElementById('startBtn').disabled = s.running;
1312
+ document.getElementById('stopBtn').disabled = !s.running;
1313
+ document.getElementById('startBtn').style.opacity = s.running ? '0.4' : '1';
1314
+ document.getElementById('stopBtn').style.opacity = !s.running ? '0.4' : '1';
1315
+
1316
+ // Handle upgrade banner
1317
+ const bannerEl = document.getElementById('upgradeBanner');
1318
+ if (s.upgradeLevel && s.upgradeLevel !== 'up_to_date' && s.upgradeLevel !== 'unknown') {
1319
+ bannerEl.style.display = '';
1320
+ const recommended = s.upgradeLevel === 'recommended';
1321
+ const required = s.upgradeLevel === 'required';
1322
+ const critical = s.upgradeLevel === 'critical';
1323
+ bannerEl.className = 'upgrade-banner ' + s.upgradeLevel;
1324
+ let bannerHtml = '<div class="upgrade-text">';
1325
+ if (critical) {
1326
+ bannerHtml += '⚠ CRITICAL UPDATE REQUIRED — ' + (s.changelog || 'Security update required') + '. Node stopped for security.';
1327
+ } else if (required) {
1328
+ bannerHtml += 'Update recommended: v' + (s.latestVersion || '') + ' — ' + (s.changelog || 'Update available');
1329
+ } else if (recommended) {
1330
+ bannerHtml += 'Update available: v' + (s.latestVersion || '') + ' — ' + (s.changelog || 'New version available');
1331
+ }
1332
+ bannerHtml += '</div><div class="upgrade-actions">';
1333
+ bannerHtml += '<button class="upgrade-btn" onclick="doAction(\'upgrade\')">Update Now</button>';
1334
+ if (recommended) {
1335
+ bannerHtml += '<button class="upgrade-dismiss" onclick="document.getElementById(\'upgradeBanner\').style.display=\'none\'">Dismiss</button>';
1336
+ }
1337
+ bannerHtml += '</div>';
1338
+ bannerEl.innerHTML = bannerHtml;
1339
+ } else {
1340
+ bannerEl.style.display = 'none';
1341
+ }
1191
1342
 
1192
1343
  // Wallet
1193
1344
  cachedAddress = s.walletAddress || '';
1194
1345
  cachedNetwork = s.network || '';
1195
- const wHtml = s.walletAddress
1196
- ? '<div class="wallet-addr">' + s.walletAddress + ' <button class="copy-btn" onclick="copyText(\\''+s.walletAddress+'\\')">Copy</button></div>' +
1197
- (s.balance ? '<div style="margin-top:8px;font-size:14px;color:var(--green)">' + s.balance + '</div>' : '')
1198
- : '<div style="color:var(--text-dim)">No wallet yet — start the node to generate one</div>';
1199
- document.getElementById('walletInfo').innerHTML = wHtml;
1200
- document.getElementById('walletActions').style.display = s.walletAddress ? '' : 'none';
1346
+ if (s.walletAddress) {
1347
+ document.getElementById('walletEmpty').style.display = 'none';
1348
+ document.getElementById('walletLoaded').style.display = '';
1349
+ document.getElementById('walletAddrText').textContent = s.walletAddress;
1350
+ document.getElementById('walletBalance').textContent = s.balance || '—';
1351
+ // Agent status
1352
+ cachedAgentName = s.agentName || '';
1353
+ const regCard = document.getElementById('qaRegister');
1354
+ const regLabel = document.getElementById('qaRegisterLabel');
1355
+ const regHint = document.getElementById('qaRegisterHint');
1356
+ if (cachedAgentName) {
1357
+ regLabel.textContent = 'Agent Registered';
1358
+ regHint.innerHTML = '<span style="color:var(--green)">&#x2713; ' + cachedAgentName + '</span>';
1359
+ regCard.style.borderColor = 'var(--green)';
1360
+ regCard.style.opacity = '0.85';
1361
+ } else {
1362
+ regLabel.textContent = 'Register Agent';
1363
+ regHint.textContent = 'On-chain identity';
1364
+ regCard.style.borderColor = '';
1365
+ regCard.style.opacity = '';
1366
+ }
1367
+ // Extension detection hint
1368
+ const hasExt = !!(window.clawNetwork && window.clawNetwork.isClawNetwork);
1369
+ document.getElementById('qaImportHint').textContent = hasExt ? 'Extension detected — click to import' : 'Install wallet extension first';
1370
+ } else {
1371
+ document.getElementById('walletEmpty').style.display = '';
1372
+ document.getElementById('walletLoaded').style.display = 'none';
1373
+ }
1201
1374
 
1202
1375
  // Node info
1376
+ let versionStatusHtml = s.binaryVersion || '—';
1377
+ if (s.upgradeLevel === 'up_to_date') {
1378
+ versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:var(--green)">✓</span>';
1379
+ } else if (s.upgradeLevel === 'recommended') {
1380
+ versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:#ffaa00">→ ' + (s.latestVersion || '') + '</span>';
1381
+ } else if (s.upgradeLevel === 'required') {
1382
+ versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:#ff8c3a">⚠ Update recommended</span>';
1383
+ } else if (s.upgradeLevel === 'critical') {
1384
+ versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:var(--danger)">🔴 CRITICAL</span>';
1385
+ }
1203
1386
  const rows = [
1204
1387
  ['Network', s.network],
1205
1388
  ['Sync Mode', s.syncMode],
1206
1389
  ['RPC URL', s.rpcUrl],
1207
- ['Binary Version', s.binaryVersion || '—'],
1390
+ ['Binary Version', versionStatusHtml],
1208
1391
  ['Plugin Version', s.pluginVersion],
1209
1392
  ['PID', s.pid || '—'],
1210
1393
  ['Restart Count', s.restartCount],
@@ -1321,26 +1504,52 @@ async function handle(req, res) {
1321
1504
  try {
1322
1505
  const h = await fetchJson('http://localhost:' + RPC_PORT + '/health');
1323
1506
  let balance = '';
1507
+ let walletAddress = '';
1508
+ let agentName = '';
1509
+ let upgradeLevel = 'unknown';
1510
+ let latestVersion = '';
1511
+ let releaseUrl = '';
1512
+ let changelog = '';
1513
+ let announcement = null;
1514
+ // Fetch version info if available (Phase 1 endpoint)
1515
+ try {
1516
+ const v = await fetchJson('http://localhost:' + RPC_PORT + '/version');
1517
+ if (v && v.upgrade_level) {
1518
+ upgradeLevel = v.upgrade_level;
1519
+ latestVersion = v.latest_version || '';
1520
+ releaseUrl = v.release_url || '';
1521
+ changelog = v.changelog || '';
1522
+ announcement = v.announcement || null;
1523
+ }
1524
+ } catch {}
1324
1525
  try {
1325
1526
  const walletPath = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/wallet.json');
1326
1527
  const w = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
1327
- if (w.address) { const b = await rpcCall('claw_getBalance', [w.address]); balance = formatClaw(b); }
1528
+ walletAddress = w.address || '';
1529
+ if (w.address) {
1530
+ const b = await rpcCall('claw_getBalance', [w.address]); balance = formatClaw(b);
1531
+ try { const ag = await rpcCall('claw_getAgent', [w.address]); agentName = (ag && ag.name) ? ag.name : ''; } catch {}
1532
+ }
1328
1533
  } catch {}
1329
1534
  json(200, {
1330
- running: h.status === 'ok',
1535
+ running: h.status === 'ok' || h.status === 'degraded',
1331
1536
  blockHeight: h.height,
1332
1537
  peerCount: h.peer_count,
1333
1538
  network: h.chain_id,
1334
1539
  syncMode: 'light',
1335
1540
  rpcUrl: 'http://localhost:' + RPC_PORT,
1336
- walletAddress: (() => { try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/workspace/clawnetwork/wallet.json'), 'utf8')).address; } catch { return ''; } })(),
1541
+ walletAddress,
1337
1542
  binaryVersion: h.version,
1338
- pluginVersion: '0.1.0',
1543
+ pluginVersion: '0.1.1',
1339
1544
  uptime: h.uptime_secs,
1340
1545
  uptimeFormatted: h.uptime_secs < 60 ? h.uptime_secs + 's' : h.uptime_secs < 3600 ? Math.floor(h.uptime_secs/60) + 'm' : Math.floor(h.uptime_secs/3600) + 'h ' + Math.floor((h.uptime_secs%3600)/60) + 'm',
1341
- restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance, syncing: h.status === 'degraded',
1546
+ restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance, agentName, syncing: h.status === 'degraded', peerless: h.peer_count === 0, lastBlockAgeSecs: h.last_block_age_secs,
1547
+ upgradeLevel, latestVersion, releaseUrl, changelog, announcement,
1342
1548
  });
1343
- } catch { json(200, { running: false, blockHeight: null, peerCount: null }); }
1549
+ } catch {
1550
+ const walletAddr = (() => { try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/workspace/clawnetwork/wallet.json'), 'utf8')).address; } catch { return ''; } })();
1551
+ json(200, { running: false, blockHeight: null, peerCount: null, walletAddress: walletAddr, network: 'mainnet', syncMode: 'light', rpcUrl: 'http://localhost:' + RPC_PORT, pluginVersion: '0.1.1', restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance: '', agentName: '', syncing: false, uptimeFormatted: '—', pid: null, upgradeLevel: 'unknown', latestVersion: '', releaseUrl: '', changelog: '', announcement: null });
1552
+ }
1344
1553
  return;
1345
1554
  }
1346
1555
  if (p === '/api/logs') {
@@ -1461,8 +1670,21 @@ async function handle(req, res) {
1461
1670
  }
1462
1671
  if (a === 'start') {
1463
1672
  try {
1464
- // Check if already running
1673
+ // Check if already running — try RPC health first (covers stale PID file)
1465
1674
  const pidFile = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/node.pid');
1675
+ try {
1676
+ const h = await fetchJson('http://localhost:' + RPC_PORT + '/health');
1677
+ if (h && (h.status === 'ok' || h.status === 'degraded')) {
1678
+ try {
1679
+ const { execSync } = require('child_process');
1680
+ const pgrep = execSync("pgrep -f 'claw-node start'", { encoding: 'utf8', timeout: 3000 }).trim();
1681
+ const livePid = parseInt(pgrep.split('\\n')[0], 10);
1682
+ if (livePid > 0) fs.writeFileSync(pidFile, String(livePid));
1683
+ } catch {}
1684
+ json(200, { message: 'Node already running' }); return;
1685
+ }
1686
+ } catch {}
1687
+ // Fallback: check PID file
1466
1688
  try {
1467
1689
  const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
1468
1690
  if (pid > 0) { try { process.kill(pid, 0); json(200, { message: 'Node already running', pid }); return; } catch {} }
@@ -1529,6 +1751,64 @@ async function handle(req, res) {
1529
1751
  json(200, { message: 'Use Stop then Start to restart the node' });
1530
1752
  return;
1531
1753
  }
1754
+ if (a === 'upgrade') {
1755
+ try {
1756
+ // 1. Stop running node
1757
+ const pidFile = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/node.pid');
1758
+ try {
1759
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
1760
+ if (pid > 0) try { process.kill(pid, 'SIGTERM'); } catch {}
1761
+ } catch {}
1762
+ try { require('child_process').execFileSync('pkill', ['-f', 'claw-node start'], { timeout: 5000 }); } catch {}
1763
+
1764
+ // 2. Download latest binary
1765
+ const binDir = path.join(os.homedir(), '.openclaw/bin');
1766
+ const binName = process.platform === 'win32' ? 'claw-node.exe' : 'claw-node';
1767
+ const target = process.platform === 'darwin'
1768
+ ? (process.arch === 'arm64' ? 'macos-aarch64' : 'macos-x86_64')
1769
+ : process.platform === 'win32' ? 'windows-x86_64' : 'linux-x86_64';
1770
+ const ext = process.platform === 'win32' ? 'zip' : 'tar.gz';
1771
+
1772
+ // Fetch latest release tag
1773
+ let version = 'latest';
1774
+ try {
1775
+ const res = await fetch('https://api.github.com/repos/clawlabz/claw-network/releases/latest');
1776
+ if (res.ok) { const d = await res.json(); if (d.tag_name) version = d.tag_name; }
1777
+ } catch {}
1778
+
1779
+ const baseUrl = version === 'latest'
1780
+ ? 'https://github.com/clawlabz/claw-network/releases/latest/download'
1781
+ : 'https://github.com/clawlabz/claw-network/releases/download/' + version;
1782
+ const downloadUrl = baseUrl + '/claw-node-' + target + '.' + ext;
1783
+
1784
+ const tmpFile = path.join(os.tmpdir(), 'claw-node-upgrade-' + Date.now() + '.' + ext);
1785
+ require('child_process').execFileSync('curl', ['-sSfL', '-o', tmpFile, downloadUrl], { timeout: 120000 });
1786
+
1787
+ // Ensure bin directory exists
1788
+ if (!fs.existsSync(binDir)) { fs.mkdirSync(binDir, { recursive: true }); }
1789
+
1790
+ // Extract binary
1791
+ if (ext === 'tar.gz') {
1792
+ require('child_process').execFileSync('tar', ['xzf', tmpFile, '-C', binDir], { timeout: 30000 });
1793
+ } else {
1794
+ // Windows zip handling
1795
+ const AdmZip = require('adm-zip');
1796
+ const zip = new AdmZip(tmpFile);
1797
+ zip.extractAllTo(binDir, true);
1798
+ }
1799
+ fs.chmodSync(path.join(binDir, binName), 0o755);
1800
+ try { fs.unlinkSync(tmpFile); } catch {}
1801
+
1802
+ // 3. Get new version
1803
+ let newVersion = 'unknown';
1804
+ try {
1805
+ newVersion = require('child_process').execFileSync(path.join(binDir, binName), ['--version'], { encoding: 'utf8', timeout: 5000 }).trim();
1806
+ } catch {}
1807
+
1808
+ json(200, { message: 'Upgraded to ' + newVersion + '. Restart the node from Dashboard.', newVersion });
1809
+ } catch (e) { json(500, { error: e.message }); }
1810
+ return;
1811
+ }
1532
1812
  json(400, { error: 'Unknown action: ' + a });
1533
1813
  return;
1534
1814
  }
@@ -1796,6 +2076,18 @@ export default function register(api: OpenClawApi) {
1796
2076
  out({ error: 'claw-node not found. Run: curl -sSf https://raw.githubusercontent.com/clawlabz/claw-network/main/claw-node/scripts/install.sh | bash' })
1797
2077
  return
1798
2078
  }
2079
+ } else {
2080
+ // Auto-upgrade if binary is older than required minimum
2081
+ const currentVersion = getBinaryVersion(binary)
2082
+ if (currentVersion && isVersionOlder(currentVersion, MIN_NODE_VERSION)) {
2083
+ api.logger?.info?.(`[clawnetwork] claw-node ${currentVersion} is outdated (need >=${MIN_NODE_VERSION}), upgrading...`)
2084
+ process.stdout.write(`Upgrading claw-node ${currentVersion} → ${MIN_NODE_VERSION}+...\n`)
2085
+ try {
2086
+ binary = await downloadBinary(api)
2087
+ } catch (e: unknown) {
2088
+ api.logger?.warn?.(`[clawnetwork] auto-upgrade failed: ${(e as Error).message}, continuing with ${currentVersion}`)
2089
+ }
2090
+ }
1799
2091
  }
1800
2092
  initNode(binary, cfg.network, api)
1801
2093
  startNodeProcess(binary, cfg, api)
@@ -2035,12 +2327,16 @@ export default function register(api: OpenClawApi) {
2035
2327
  // Check if already running (e.g. from a previous detached start)
2036
2328
  const state = isNodeRunning()
2037
2329
  if (state.running) {
2038
- api.logger?.info?.(`[clawnetwork] node already running (pid=${state.pid}), skipping auto-start`)
2330
+ api.logger?.info?.(`[clawnetwork] node already running (pid=${state.pid}), skipping node start`)
2039
2331
  startHealthCheck(cfg, api)
2332
+ // Still need to ensure heartbeat loop is running (may have been lost on gateway restart)
2333
+ const wallet = ensureWallet(cfg.network, api)
2334
+ await sleep(5_000)
2335
+ await autoRegisterMiner(cfg, wallet, api)
2040
2336
  return
2041
2337
  }
2042
2338
 
2043
- // Step 1: Ensure binary
2339
+ // Step 1: Ensure binary (auto-upgrade if outdated)
2044
2340
  let binary = findBinary()
2045
2341
  if (!binary) {
2046
2342
  if (cfg.autoDownload) {
@@ -2050,6 +2346,14 @@ export default function register(api: OpenClawApi) {
2050
2346
  api.logger?.error?.('[clawnetwork] claw-node not found and autoDownload is disabled')
2051
2347
  return
2052
2348
  }
2349
+ } else if (cfg.autoDownload) {
2350
+ const cv = getBinaryVersion(binary)
2351
+ if (cv && isVersionOlder(cv, MIN_NODE_VERSION)) {
2352
+ api.logger?.info?.(`[clawnetwork] claw-node ${cv} outdated (need >=${MIN_NODE_VERSION}), upgrading...`)
2353
+ try { binary = await downloadBinary(api) } catch (e: unknown) {
2354
+ api.logger?.warn?.(`[clawnetwork] auto-upgrade failed: ${(e as Error).message}`)
2355
+ }
2356
+ }
2053
2357
  }
2054
2358
 
2055
2359
  // Step 2: Init
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawlabz/clawnetwork",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Run a ClawNetwork blockchain node inside OpenClaw. Every agent is a blockchain node.",
5
5
  "type": "module",
6
6
  "license": "MIT",