Skip to content

Commit c01d355

Browse files
committed
fix: add direct chain registration fallback for standalone ClawNet node deployments
1 parent 92c995e commit c01d355

File tree

2 files changed

+115
-9
lines changed

2 files changed

+115
-9
lines changed

packages/node/src/services/identity-adapter-service.ts

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { ErrorCodes, TelagentError, hashDid, isDidClaw, type AgentDID } from '@telagent/protocol';
22
import { publicKeyFromDid, bytesToHex } from '@claw-network/core';
3+
import { ethers } from 'ethers';
34

45
import type { ClawNetGatewayService, IdentityInfo } from '../clawnet/gateway-service.js';
56
import type { ManagedClawNetNode } from '../clawnet/managed-node.js';
67
import type { IdentityCache } from '../storage/identity-cache.js';
8+
import { getGlobalLogger } from '../logger.js';
9+
10+
const logger = getGlobalLogger();
11+
12+
/** Minimal ABI for on-chain DID registration (no artifacts needed) */
13+
const IDENTITY_ABI = [
14+
'function getController(bytes32 didHash) view returns (address)',
15+
'function batchRegisterDID(bytes32[] didHashes, bytes[] publicKeys, uint8[] purposes, address[] controllers)',
16+
];
717

818
export interface ResolvedIdentity {
919
did: AgentDID;
@@ -99,30 +109,79 @@ export class IdentityAdapterService {
99109

100110
/**
101111
* Ensure the node's own DID is registered on-chain.
102-
* Uses the embedded ClawNet node's identity service directly (batchRegisterDID)
103-
* which is the correct internal registration path.
112+
* Strategy:
113+
* 1. If a managed ClawNet node is available, use its identity service (batchRegisterDID)
114+
* 2. Otherwise, fall back to direct ethers.js contract call using CLAW_CHAIN_* env vars
104115
*/
105116
async ensureRegistered(): Promise<void> {
106117
const self = await this.getSelf();
107118
// If controller is a real EVM address (starts with 0x), it's already on-chain
108119
if (self.controller.startsWith('0x')) {
109120
return;
110121
}
111-
if (!this.managedNode) {
112-
throw new Error('No managed ClawNet node — cannot auto-register on-chain');
113-
}
114122
// Convert the multibase public key to 0x-prefixed hex for the smart contract
115123
const rawBytes = publicKeyFromDid(self.did);
116124
const hexKey = `0x${bytesToHex(rawBytes)}`;
117-
// Register directly via the embedded node's identity service
118-
const controller = await this.managedNode.ensureRegisteredOnChain(self.did, hexKey);
119-
if (!controller) {
120-
throw new Error('Chain identity service unavailable on embedded node');
125+
126+
if (this.managedNode) {
127+
// Path A: embedded managed node (local dev)
128+
const controller = await this.managedNode.ensureRegisteredOnChain(self.did, hexKey);
129+
if (!controller) {
130+
throw new Error('Chain identity service unavailable on embedded node');
131+
}
132+
} else {
133+
// Path B: direct ethers.js call (cloud with standalone ClawNet node)
134+
await this.registerOnChainDirect(self.did, hexKey);
121135
}
122136
// Refresh self identity to pick up chain data
123137
await this.getSelf();
124138
}
125139

140+
/**
141+
* Register DID on-chain directly using ethers.js and CLAW_CHAIN_* env vars.
142+
* Used as fallback when no managed ClawNet node is available.
143+
*/
144+
private async registerOnChainDirect(did: string, publicKeyHex: string): Promise<void> {
145+
const rpcUrl = process.env.CLAW_CHAIN_RPC_URL;
146+
const identityAddr = process.env.CLAW_CHAIN_IDENTITY_CONTRACT;
147+
const signerEnv = process.env.CLAW_SIGNER_ENV || 'CLAW_PRIVATE_KEY';
148+
const privateKey = process.env[signerEnv];
149+
150+
if (!rpcUrl || !identityAddr || !privateKey) {
151+
throw new Error(
152+
'Missing chain config for direct registration. ' +
153+
'Set CLAW_CHAIN_RPC_URL, CLAW_CHAIN_IDENTITY_CONTRACT, and CLAW_SIGNER_ENV/private key.',
154+
);
155+
}
156+
157+
const provider = new ethers.JsonRpcProvider(rpcUrl);
158+
const wallet = new ethers.Wallet(privateKey, provider);
159+
const contract = new ethers.Contract(identityAddr, IDENTITY_ABI, wallet);
160+
const didHash = ethers.keccak256(ethers.toUtf8Bytes(did));
161+
162+
// Check if already registered on-chain
163+
try {
164+
const controller = await contract.getController(didHash);
165+
if (controller !== ethers.ZeroAddress) {
166+
logger.info('[telagent] DID already on-chain: %s → controller %s', did, controller);
167+
return;
168+
}
169+
} catch {
170+
// getController reverts when DID not found — proceed to register
171+
}
172+
173+
// Register via batchRegisterDID (REGISTRAR_ROLE, no ECDSA sig needed)
174+
logger.info('[telagent] Direct chain registration: %s → controller %s', did, wallet.address);
175+
const tx = await contract.batchRegisterDID(
176+
[didHash],
177+
[publicKeyHex],
178+
[0], // authentication purpose
179+
[wallet.address],
180+
);
181+
await tx.wait();
182+
logger.info('[telagent] DID registered on-chain successfully: %s', did);
183+
}
184+
126185
async resolve(rawDid: string): Promise<ResolvedIdentity> {
127186
if (!isDidClaw(rawDid)) {
128187
throw new TelagentError(ErrorCodes.VALIDATION, 'DID must use did:claw format');

scripts/deploy-node.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
HOST="$1"
5+
SSH_KEY="$HOME/.ssh/id_ed25519_clawnet"
6+
7+
echo "=== Deploying to $HOST ==="
8+
9+
ssh -i "$SSH_KEY" "root@$HOST" 'bash -s' <<'REMOTE'
10+
set -euo pipefail
11+
12+
echo "--- Step 1: Stop service, backup env ---"
13+
systemctl stop telagent-node
14+
cp /opt/telagent/.env.cloud /tmp/.env.cloud.bak
15+
echo "Done."
16+
17+
echo "--- Step 2: Fresh clone ---"
18+
rm -rf /opt/telagent
19+
git clone --depth 1 https://github.com/claw-network/telagent.git /opt/telagent
20+
cp /tmp/.env.cloud.bak /opt/telagent/.env.cloud
21+
echo "Done."
22+
23+
echo "--- Step 3: Install deps ---"
24+
cd /opt/telagent
25+
pnpm install --frozen-lockfile 2>&1 | tail -5
26+
echo "Done."
27+
28+
echo "--- Step 4: Fix start script ---"
29+
sed -i 's|tsx --env-file=../../.env src/daemon.ts|tsx src/daemon.ts|' packages/node/package.json
30+
grep '"start"' packages/node/package.json
31+
echo "Done."
32+
33+
echo "--- Step 5: Build workspace deps ---"
34+
pnpm --filter @telagent/protocol build 2>&1 | tail -3
35+
pnpm --filter @telagent/sdk build 2>&1 | tail -3
36+
echo "Done."
37+
38+
echo "--- Step 6: Start service ---"
39+
systemctl start telagent-node
40+
sleep 4
41+
systemctl is-active telagent-node
42+
43+
echo "--- Step 7: Check logs ---"
44+
journalctl -u telagent-node --no-pager -n 30 | grep -iE 'Identity|register|chain|error|listen' || true
45+
46+
echo "=== DEPLOY COMPLETE ==="
47+
REMOTE

0 commit comments

Comments
 (0)