repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * Automaton SIWE Provisioning |
| #3 | * |
| #4 | * Uses the automaton's wallet to authenticate via Sign-In With Ethereum (SIWE) |
| #5 | * and create an API key for Conway API access. |
| #6 | * Adapted from conway-mcp/src/cli/provision.ts |
| #7 | */ |
| #8 | |
| #9 | import fs from "fs"; |
| #10 | import path from "path"; |
| #11 | import { SiweMessage } from "siwe"; |
| #12 | import { getWallet, getAutomatonDir } from "./wallet.js"; |
| #13 | import type { ProvisionResult } from "../types.js"; |
| #14 | |
| #15 | const DEFAULT_API_URL = "https://api.conway.tech"; |
| #16 | |
| #17 | /** |
| #18 | * Load API key from ~/.automaton/config.json if it exists. |
| #19 | */ |
| #20 | export function loadApiKeyFromConfig(): string | null { |
| #21 | const configPath = path.join(getAutomatonDir(), "config.json"); |
| #22 | if (!fs.existsSync(configPath)) return null; |
| #23 | try { |
| #24 | const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); |
| #25 | return config.apiKey || null; |
| #26 | } catch { |
| #27 | return null; |
| #28 | } |
| #29 | } |
| #30 | |
| #31 | /** |
| #32 | * Save API key and wallet address to ~/.automaton/config.json |
| #33 | */ |
| #34 | function saveConfig(apiKey: string, walletAddress: string): void { |
| #35 | const dir = getAutomatonDir(); |
| #36 | if (!fs.existsSync(dir)) { |
| #37 | fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); |
| #38 | } |
| #39 | const configPath = path.join(dir, "config.json"); |
| #40 | const config = { |
| #41 | apiKey, |
| #42 | walletAddress, |
| #43 | provisionedAt: new Date().toISOString(), |
| #44 | }; |
| #45 | fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { |
| #46 | mode: 0o600, |
| #47 | }); |
| #48 | } |
| #49 | |
| #50 | /** |
| #51 | * Run the full SIWE provisioning flow: |
| #52 | * 1. Load wallet |
| #53 | * 2. Get nonce from Conway API |
| #54 | * 3. Sign SIWE message |
| #55 | * 4. Verify signature -> get JWT |
| #56 | * 5. Create API key |
| #57 | * 6. Save to config.json |
| #58 | */ |
| #59 | export async function provision( |
| #60 | apiUrl?: string, |
| #61 | ): Promise<ProvisionResult> { |
| #62 | const url = apiUrl || process.env.CONWAY_API_URL || DEFAULT_API_URL; |
| #63 | |
| #64 | // 1. Load wallet |
| #65 | const { account } = await getWallet(); |
| #66 | const address = account.address; |
| #67 | |
| #68 | // 2. Get nonce |
| #69 | const nonceResp = await fetch(`${url}/v1/auth/nonce`, { |
| #70 | method: "POST", |
| #71 | }); |
| #72 | if (!nonceResp.ok) { |
| #73 | throw new Error( |
| #74 | `Failed to get nonce: ${nonceResp.status} ${await nonceResp.text()}`, |
| #75 | ); |
| #76 | } |
| #77 | const { nonce } = (await nonceResp.json()) as { nonce: string }; |
| #78 | |
| #79 | // 3. Construct and sign SIWE message |
| #80 | const siweMessage = new SiweMessage({ |
| #81 | domain: "conway.tech", |
| #82 | address, |
| #83 | statement: |
| #84 | "Sign in to Conway as an Automaton to provision an API key.", |
| #85 | uri: `${url}/v1/auth/verify`, |
| #86 | version: "1", |
| #87 | chainId: 8453, // Base |
| #88 | nonce, |
| #89 | issuedAt: new Date().toISOString(), |
| #90 | }); |
| #91 | |
| #92 | const messageString = siweMessage.prepareMessage(); |
| #93 | const signature = await account.signMessage({ message: messageString }); |
| #94 | |
| #95 | // 4. Verify signature -> get JWT |
| #96 | const verifyResp = await fetch(`${url}/v1/auth/verify`, { |
| #97 | method: "POST", |
| #98 | headers: { "Content-Type": "application/json" }, |
| #99 | body: JSON.stringify({ message: messageString, signature }), |
| #100 | }); |
| #101 | |
| #102 | if (!verifyResp.ok) { |
| #103 | throw new Error( |
| #104 | `SIWE verification failed: ${verifyResp.status} ${await verifyResp.text()}`, |
| #105 | ); |
| #106 | } |
| #107 | |
| #108 | const { access_token } = (await verifyResp.json()) as { |
| #109 | access_token: string; |
| #110 | }; |
| #111 | |
| #112 | // 5. Create API key |
| #113 | const keyResp = await fetch(`${url}/v1/auth/api-keys`, { |
| #114 | method: "POST", |
| #115 | headers: { |
| #116 | "Content-Type": "application/json", |
| #117 | Authorization: `Bearer ${access_token}`, |
| #118 | }, |
| #119 | body: JSON.stringify({ name: "conway-automaton" }), |
| #120 | }); |
| #121 | |
| #122 | if (!keyResp.ok) { |
| #123 | throw new Error( |
| #124 | `Failed to create API key: ${keyResp.status} ${await keyResp.text()}`, |
| #125 | ); |
| #126 | } |
| #127 | |
| #128 | const { key, key_prefix } = (await keyResp.json()) as { |
| #129 | key: string; |
| #130 | key_prefix: string; |
| #131 | }; |
| #132 | |
| #133 | // 6. Save to config |
| #134 | saveConfig(key, address); |
| #135 | |
| #136 | return { apiKey: key, walletAddress: address, keyPrefix: key_prefix }; |
| #137 | } |
| #138 | |
| #139 | /** |
| #140 | * Register the automaton's creator as its parent with Conway. |
| #141 | * This allows the creator to see automaton logs and inference calls. |
| #142 | */ |
| #143 | export async function registerParent( |
| #144 | creatorAddress: string, |
| #145 | apiUrl?: string, |
| #146 | ): Promise<void> { |
| #147 | const url = apiUrl || process.env.CONWAY_API_URL || DEFAULT_API_URL; |
| #148 | const apiKey = loadApiKeyFromConfig(); |
| #149 | if (!apiKey) { |
| #150 | throw new Error("Must provision API key before registering parent"); |
| #151 | } |
| #152 | |
| #153 | const resp = await fetch(`${url}/v1/automaton/register-parent`, { |
| #154 | method: "POST", |
| #155 | headers: { |
| #156 | "Content-Type": "application/json", |
| #157 | Authorization: apiKey, |
| #158 | }, |
| #159 | body: JSON.stringify({ creatorAddress }), |
| #160 | }); |
| #161 | |
| #162 | // Endpoint may not exist yet -- fail gracefully |
| #163 | if (!resp.ok && resp.status !== 404) { |
| #164 | throw new Error( |
| #165 | `Failed to register parent: ${resp.status} ${await resp.text()}`, |
| #166 | ); |
| #167 | } |
| #168 | } |
| #169 |