@1claw/openclaw-plugin 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1claw/openclaw-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "OpenClaw plugin for 1claw — HSM-backed secret management, transaction signing, and Shroud LLM proxy integration for AI agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/client.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  SimulationResponse,
10
10
  BundleSimulationResponse,
11
11
  TransactionResponse,
12
+ SignTransactionResponse,
12
13
  AgentProfile,
13
14
  ApiErrorBody,
14
15
  } from "./types.js";
@@ -369,6 +370,49 @@ export class OneClawClient {
369
370
  );
370
371
  }
371
372
 
373
+ async signTransaction(
374
+ agentId: string,
375
+ tx: {
376
+ to: string;
377
+ value: string;
378
+ chain: string;
379
+ data?: string;
380
+ signing_key_path?: string;
381
+ nonce?: number;
382
+ gas_price?: string;
383
+ gas_limit?: number;
384
+ max_fee_per_gas?: string;
385
+ max_priority_fee_per_gas?: string;
386
+ simulate_first?: boolean;
387
+ },
388
+ ): Promise<SignTransactionResponse> {
389
+ return this.request<SignTransactionResponse>(
390
+ `${this.baseUrl}/v1/agents/${agentId}/transactions/sign`,
391
+ { method: "POST", body: JSON.stringify(tx) },
392
+ );
393
+ }
394
+
395
+ async listTransactions(
396
+ agentId: string,
397
+ opts?: { include_signed_tx?: boolean },
398
+ ): Promise<{ transactions: TransactionResponse[] }> {
399
+ const qs = opts?.include_signed_tx ? "?include_signed_tx=true" : "";
400
+ return this.request<{ transactions: TransactionResponse[] }>(
401
+ `${this.baseUrl}/v1/agents/${agentId}/transactions${qs}`,
402
+ );
403
+ }
404
+
405
+ async getTransaction(
406
+ agentId: string,
407
+ txId: string,
408
+ opts?: { include_signed_tx?: boolean },
409
+ ): Promise<TransactionResponse> {
410
+ const qs = opts?.include_signed_tx ? "?include_signed_tx=true" : "";
411
+ return this.request<TransactionResponse>(
412
+ `${this.baseUrl}/v1/agents/${agentId}/transactions/${txId}${qs}`,
413
+ );
414
+ }
415
+
372
416
  // ── Agent profile ────────────────────────────────────
373
417
 
374
418
  async getAgentProfile(): Promise<AgentProfile> {
@@ -0,0 +1,72 @@
1
+ import type { ResolvedConfig } from "../config.js";
2
+
3
+ interface EnrollResponse {
4
+ agent_id: string;
5
+ name: string;
6
+ message?: string;
7
+ }
8
+
9
+ export function enrollCommand(config: ResolvedConfig) {
10
+ return {
11
+ name: "oneclaw-enroll",
12
+ description: "Self-enroll a new 1claw agent. Usage: /oneclaw-enroll email@example.com [agent-name]",
13
+ acceptsArgs: true,
14
+ handler: async (ctx: { args?: string; commandBody: string }) => {
15
+ const args = (ctx.args ?? ctx.commandBody ?? "").trim();
16
+ const parts = args.split(/\s+/);
17
+ const email = parts[0];
18
+ const name = parts.slice(1).join(" ") || "openclaw-agent";
19
+
20
+ if (!email || !email.includes("@")) {
21
+ return {
22
+ text: "Usage: /oneclaw-enroll your-email@example.com [agent-name]\n\nThis registers a new agent with 1claw. The API key will be emailed to you.",
23
+ };
24
+ }
25
+
26
+ try {
27
+ const res = await fetch(`${config.baseUrl}/v1/agents/enroll`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({
31
+ name,
32
+ human_email: email,
33
+ description: "OpenClaw agent",
34
+ }),
35
+ });
36
+
37
+ if (!res.ok) {
38
+ let detail = `HTTP ${res.status}`;
39
+ try {
40
+ const body = (await res.json()) as { detail?: string };
41
+ if (body.detail) detail = body.detail;
42
+ } catch {
43
+ /* use default */
44
+ }
45
+ return { text: `Enrollment failed: ${detail}` };
46
+ }
47
+
48
+ const data = (await res.json()) as EnrollResponse;
49
+ const lines = [
50
+ "Agent enrolled successfully!",
51
+ "",
52
+ `Agent ID: ${data.agent_id}`,
53
+ `Name: ${data.name}`,
54
+ "",
55
+ `Check ${email} for your API key. Then:`,
56
+ "",
57
+ "1. Create a vault and policy in the 1claw dashboard (https://1claw.xyz)",
58
+ "2. Set these environment variables:",
59
+ ` export ONECLAW_AGENT_ID="${data.agent_id}"`,
60
+ ' export ONECLAW_AGENT_API_KEY="ocv_..."',
61
+ ' export ONECLAW_VAULT_ID="<vault-id>"',
62
+ "",
63
+ "3. Restart OpenClaw to pick up the new credentials.",
64
+ ];
65
+ return { text: lines.join("\n") };
66
+ } catch (err) {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ return { text: `Enrollment error: ${msg}` };
69
+ }
70
+ },
71
+ };
72
+ }
@@ -4,6 +4,15 @@ import type { ResolvedConfig } from "../config.js";
4
4
  import { statusCommand } from "./status.js";
5
5
  import { listCommand } from "./list.js";
6
6
  import { rotateCommand } from "./rotate.js";
7
+ import { enrollCommand } from "./enroll.js";
8
+
9
+ export function registerUnauthenticatedCommands(
10
+ api: PluginApi,
11
+ config: ResolvedConfig,
12
+ ): void {
13
+ api.registerCommand(enrollCommand(config));
14
+ api.logger.info("[1claw] Registered unauthenticated commands (/oneclaw-enroll)");
15
+ }
7
16
 
8
17
  export function registerAllCommands(
9
18
  api: PluginApi,
package/src/config.ts CHANGED
@@ -50,6 +50,7 @@ export function resolveConfig(pluginConfig?: RawPluginConfig): ResolvedConfig {
50
50
  apiKey:
51
51
  raw.apiKey ??
52
52
  process.env.ONECLAW_AGENT_API_KEY ??
53
+ process.env.ONECLAW_API_KEY ??
53
54
  undefined,
54
55
  agentId:
55
56
  raw.agentId ??
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ import { resolveConfig } from "./config.js";
3
3
  import { registerAllTools } from "./tools/index.js";
4
4
  import { registerAllHooks } from "./hooks/index.js";
5
5
  import { registerAllServices } from "./services/index.js";
6
- import { registerAllCommands } from "./commands/index.js";
6
+ import { registerAllCommands, registerUnauthenticatedCommands } from "./commands/index.js";
7
7
  import type { PluginApi } from "./types.js";
8
8
 
9
9
  interface FullConfig {
@@ -21,9 +21,12 @@ function register(api: PluginApi): void {
21
21
  const rawConfig = extractPluginConfig(api);
22
22
  const config = resolveConfig(rawConfig as Parameters<typeof resolveConfig>[0]);
23
23
 
24
+ // Always register commands that work without auth (e.g. enroll)
25
+ registerUnauthenticatedCommands(api, config);
26
+
24
27
  if (!config.apiKey) {
25
28
  api.logger.warn(
26
- "[1claw] No API key configured. Set plugins.entries.1claw.config.apiKey or ONECLAW_AGENT_API_KEY env var.",
29
+ "[1claw] No API key configured. Set ONECLAW_AGENT_API_KEY (or ONECLAW_API_KEY) env var, or run /oneclaw-enroll to get started.",
27
30
  );
28
31
  return;
29
32
  }
@@ -16,6 +16,7 @@ import { grantAccessTool } from "./grant-access.js";
16
16
  import { shareSecretTool } from "./share-secret.js";
17
17
  import { simulateTransactionTool } from "./simulate-transaction.js";
18
18
  import { submitTransactionTool } from "./submit-transaction.js";
19
+ import { signTransactionTool } from "./sign-transaction.js";
19
20
 
20
21
  type ToolFactory = (client: OneClawClient) => PluginTool;
21
22
 
@@ -33,6 +34,7 @@ const ALL_TOOL_FACTORIES: ToolFactory[] = [
33
34
  shareSecretTool,
34
35
  simulateTransactionTool,
35
36
  submitTransactionTool,
37
+ signTransactionTool,
36
38
  ];
37
39
 
38
40
  function wrapWithSecurity(
@@ -0,0 +1,70 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OneClawClient } from "../client.js";
3
+ import { OneClawApiError } from "../client.js";
4
+ import type { PluginTool, ToolResult } from "../types.js";
5
+
6
+ export function signTransactionTool(client: OneClawClient): PluginTool {
7
+ return {
8
+ name: "oneclaw_sign_transaction",
9
+ description:
10
+ "Sign an EVM transaction without broadcasting it. Returns the raw signed_tx hex and tx_hash so the caller can submit to any RPC. All agent guardrails (allowlists, value caps, daily limits) are enforced.",
11
+ parameters: Type.Object({
12
+ to: Type.String({ description: "Destination address (0x-prefixed)" }),
13
+ value: Type.String({ description: "Value in ETH (e.g. '0.01')" }),
14
+ chain: Type.String({ description: "Chain name or numeric chain ID" }),
15
+ data: Type.Optional(Type.String({ description: "Hex-encoded calldata" })),
16
+ signing_key_path: Type.Optional(Type.String({ description: "Vault path to signing key" })),
17
+ nonce: Type.Optional(Type.Integer({ description: "Transaction nonce (auto-resolved if omitted)" })),
18
+ gas_price: Type.Optional(Type.String({ description: "Gas price in wei (legacy mode)" })),
19
+ gas_limit: Type.Optional(Type.Integer({ description: "Gas limit (default: 21000)" })),
20
+ max_fee_per_gas: Type.Optional(Type.String({ description: "EIP-1559 max fee per gas in wei" })),
21
+ max_priority_fee_per_gas: Type.Optional(Type.String({ description: "EIP-1559 max priority fee in wei" })),
22
+ simulate_first: Type.Optional(Type.Boolean({ description: "Run simulation before signing (default: true)" })),
23
+ }),
24
+ optional: true,
25
+ execute: async (_id: unknown, params: Record<string, unknown>): Promise<ToolResult> => {
26
+ const agentId = client.agentId;
27
+ if (!agentId) {
28
+ return { content: [{ type: "text", text: "Error: sign_transaction requires agent authentication." }] };
29
+ }
30
+
31
+ try {
32
+ const result = await client.signTransaction(agentId, {
33
+ to: params.to as string,
34
+ value: params.value as string,
35
+ chain: params.chain as string,
36
+ data: params.data as string | undefined,
37
+ signing_key_path: params.signing_key_path as string | undefined,
38
+ nonce: params.nonce as number | undefined,
39
+ gas_price: params.gas_price as string | undefined,
40
+ gas_limit: params.gas_limit as number | undefined,
41
+ max_fee_per_gas: params.max_fee_per_gas as string | undefined,
42
+ max_priority_fee_per_gas: params.max_priority_fee_per_gas as string | undefined,
43
+ simulate_first: (params.simulate_first as boolean) ?? true,
44
+ });
45
+
46
+ const lines: string[] = [
47
+ `Transaction SIGNED (not broadcast)`,
48
+ `Tx hash: ${result.tx_hash}`,
49
+ `From: ${result.from}`,
50
+ `To: ${result.to}`,
51
+ `Chain: ${result.chain} (${result.chain_id})`,
52
+ `Nonce: ${result.nonce}`,
53
+ `Value: ${result.value_wei} wei`,
54
+ `Signed tx: ${result.signed_tx}`,
55
+ ];
56
+
57
+ if (result.simulation_id) lines.push(`Simulation: ${result.simulation_id} (${result.simulation_status})`);
58
+
59
+ return { content: [{ type: "text", text: lines.join("\n") }] };
60
+ } catch (err) {
61
+ if (err instanceof OneClawApiError) {
62
+ if (err.status === 400 || err.status === 403 || err.status === 422) {
63
+ return { content: [{ type: "text", text: `Error: ${err.detail}` }] };
64
+ }
65
+ }
66
+ throw err;
67
+ }
68
+ },
69
+ };
70
+ }
package/src/types.ts CHANGED
@@ -109,6 +109,22 @@ export interface TransactionResponse {
109
109
  simulation_status?: string;
110
110
  }
111
111
 
112
+ export interface SignTransactionResponse {
113
+ signed_tx: string;
114
+ tx_hash: string;
115
+ from: string;
116
+ to: string;
117
+ chain: string;
118
+ chain_id: number;
119
+ nonce: number;
120
+ value_wei: string;
121
+ status: "sign_only";
122
+ simulation_id?: string;
123
+ simulation_status?: string;
124
+ max_fee_per_gas?: string;
125
+ max_priority_fee_per_gas?: string;
126
+ }
127
+
112
128
  export interface AgentProfile {
113
129
  id: string;
114
130
  name: string;