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 | import { existsSync, mkdirSync } from "node:fs"; |
| #2 | import { resolve } from "node:path"; |
| #3 | import { spawnSync } from "node:child_process"; |
| #4 | |
| #5 | export interface TwammAutomationStatus { |
| #6 | root: string; |
| #7 | exists: boolean; |
| #8 | anchorToml: boolean; |
| #9 | cargoToml: boolean; |
| #10 | appPackage: boolean; |
| #11 | crankScript: boolean; |
| #12 | programIdl: boolean; |
| #13 | defaultRpcUrl: string; |
| #14 | tokenAMint: string; |
| #15 | tokenBMint: string; |
| #16 | liveEnabled: boolean; |
| #17 | operatorConfirmed: boolean; |
| #18 | warnings: string[]; |
| #19 | } |
| #20 | |
| #21 | export interface TwammAutomationPlan { |
| #22 | mode: "build" | "test" | "crank"; |
| #23 | command: string; |
| #24 | cwd: string; |
| #25 | args: string[]; |
| #26 | env: Record<string, string>; |
| #27 | warnings: string[]; |
| #28 | } |
| #29 | |
| #30 | type BuildOptions = { |
| #31 | skipAppInstall?: boolean; |
| #32 | }; |
| #33 | |
| #34 | type TestOptions = { |
| #35 | anchor?: boolean; |
| #36 | cargo?: boolean; |
| #37 | }; |
| #38 | |
| #39 | type CrankOptions = { |
| #40 | rpcUrl?: string; |
| #41 | tokenAMint?: string; |
| #42 | tokenBMint?: string; |
| #43 | walletPath?: string; |
| #44 | once?: boolean; |
| #45 | yes?: boolean; |
| #46 | }; |
| #47 | |
| #48 | const DEFAULT_SOL_MINT = "So11111111111111111111111111111111111111112"; |
| #49 | const DEFAULT_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; |
| #50 | |
| #51 | function repoCandidates(): string[] { |
| #52 | const explicit = process.env.CLAWD_TWAMM_ROOT; |
| #53 | const cwd = process.cwd(); |
| #54 | return [ |
| #55 | ...(explicit ? [explicit] : []), |
| #56 | resolve(cwd, "Perps", "twamm-master"), |
| #57 | resolve(cwd, "..", "Perps", "twamm-master"), |
| #58 | resolve(cwd, "..", "..", "Perps", "twamm-master"), |
| #59 | ]; |
| #60 | } |
| #61 | |
| #62 | export function resolveTwammRoot(): string { |
| #63 | for (const candidate of repoCandidates()) { |
| #64 | if (existsSync(resolve(candidate, "Anchor.toml")) && existsSync(resolve(candidate, "app", "src", "crank.ts"))) { |
| #65 | return candidate; |
| #66 | } |
| #67 | } |
| #68 | return process.env.CLAWD_TWAMM_ROOT || resolve(process.cwd(), "Perps", "twamm-master"); |
| #69 | } |
| #70 | |
| #71 | function defaultRpcUrl(): string { |
| #72 | return process.env.CLAWD_TWAMM_RPC_URL || process.env.SOLANA_RPC_URL || "local"; |
| #73 | } |
| #74 | |
| #75 | function defaultTokenAMint(): string { |
| #76 | return process.env.CLAWD_TWAMM_TOKEN_A_MINT || DEFAULT_SOL_MINT; |
| #77 | } |
| #78 | |
| #79 | function defaultTokenBMint(): string { |
| #80 | return process.env.CLAWD_TWAMM_TOKEN_B_MINT || DEFAULT_USDC_MINT; |
| #81 | } |
| #82 | |
| #83 | function baseWarnings(): string[] { |
| #84 | const warnings = [ |
| #85 | "Reference TWAMM implementation is unaudited for this perps automation layer.", |
| #86 | "The crank runner signs permissionless crank transactions while running.", |
| #87 | "Use localnet/devnet first and require explicit operator confirmation for mainnet.", |
| #88 | ]; |
| #89 | if (!process.env.CLAWD_TWAMM_TOKEN_A_MINT || !process.env.CLAWD_TWAMM_TOKEN_B_MINT) { |
| #90 | warnings.push("CLAWD_TWAMM_TOKEN_A_MINT/B_MINT not set; defaulting to SOL/USDC mints."); |
| #91 | } |
| #92 | return warnings; |
| #93 | } |
| #94 | |
| #95 | export function getTwammAutomationStatus(): TwammAutomationStatus { |
| #96 | const root = resolveTwammRoot(); |
| #97 | return { |
| #98 | root, |
| #99 | exists: existsSync(root), |
| #100 | anchorToml: existsSync(resolve(root, "Anchor.toml")), |
| #101 | cargoToml: existsSync(resolve(root, "Cargo.toml")), |
| #102 | appPackage: existsSync(resolve(root, "app", "package.json")), |
| #103 | crankScript: existsSync(resolve(root, "app", "src", "crank.ts")), |
| #104 | programIdl: existsSync(resolve(root, "target", "idl", "twamm.json")), |
| #105 | defaultRpcUrl: defaultRpcUrl(), |
| #106 | tokenAMint: defaultTokenAMint(), |
| #107 | tokenBMint: defaultTokenBMint(), |
| #108 | liveEnabled: process.env.CLAWD_TWAMM_LIVE === "true", |
| #109 | operatorConfirmed: process.env.OPERATOR_CONFIRMED === "true", |
| #110 | warnings: baseWarnings(), |
| #111 | }; |
| #112 | } |
| #113 | |
| #114 | export function buildTwammBuildPlan(options: BuildOptions = {}): TwammAutomationPlan { |
| #115 | const status = getTwammAutomationStatus(); |
| #116 | const warnings = [...status.warnings]; |
| #117 | if (!options.skipAppInstall && !existsSync(resolve(status.root, "node_modules"))) { |
| #118 | warnings.push("Run npm install in the TWAMM root before anchor build, or use the build command to do it automatically."); |
| #119 | } |
| #120 | return { |
| #121 | mode: "build", |
| #122 | command: "anchor", |
| #123 | cwd: status.root, |
| #124 | args: ["build"], |
| #125 | env: { RUST_BACKTRACE: process.env.RUST_BACKTRACE || "1" }, |
| #126 | warnings, |
| #127 | }; |
| #128 | } |
| #129 | |
| #130 | export function buildTwammTestPlan(options: TestOptions = {}): TwammAutomationPlan { |
| #131 | const status = getTwammAutomationStatus(); |
| #132 | const runAnchor = options.anchor ?? true; |
| #133 | const runCargo = options.cargo ?? false; |
| #134 | const args = runAnchor ? ["test", "--", "--features", "test"] : ["test", "--", "--nocapture"]; |
| #135 | return { |
| #136 | mode: "test", |
| #137 | command: runAnchor && !runCargo ? "anchor" : "cargo", |
| #138 | cwd: status.root, |
| #139 | args, |
| #140 | env: { RUST_BACKTRACE: process.env.RUST_BACKTRACE || "1" }, |
| #141 | warnings: status.warnings, |
| #142 | }; |
| #143 | } |
| #144 | |
| #145 | export function buildTwammCrankPlan(options: CrankOptions = {}): TwammAutomationPlan { |
| #146 | const status = getTwammAutomationStatus(); |
| #147 | const warnings = [...status.warnings]; |
| #148 | if (!status.liveEnabled || !status.operatorConfirmed || !options.yes) { |
| #149 | warnings.push("Crank command is blocked until CLAWD_TWAMM_LIVE=true, OPERATOR_CONFIRMED=true, and --yes are all present."); |
| #150 | } |
| #151 | |
| #152 | return { |
| #153 | mode: "crank", |
| #154 | command: "npx", |
| #155 | cwd: status.root, |
| #156 | args: [ |
| #157 | "ts-node", |
| #158 | "-P", |
| #159 | "tsconfig.json", |
| #160 | "app/src/crank.ts", |
| #161 | options.rpcUrl || status.defaultRpcUrl, |
| #162 | options.tokenAMint || status.tokenAMint, |
| #163 | options.tokenBMint || status.tokenBMint, |
| #164 | ], |
| #165 | env: { |
| #166 | RUST_BACKTRACE: process.env.RUST_BACKTRACE || "1", |
| #167 | ...(options.walletPath ? { ANCHOR_WALLET: options.walletPath } : {}), |
| #168 | ...(options.once ? { CLAWD_TWAMM_CRANK_ONCE: "true" } : {}), |
| #169 | }, |
| #170 | warnings, |
| #171 | }; |
| #172 | } |
| #173 | |
| #174 | export function buildTwammAutomation(options: BuildOptions = {}) { |
| #175 | const status = getTwammAutomationStatus(); |
| #176 | if (!status.exists || !status.anchorToml) { |
| #177 | return { ok: false, status, error: "TWAMM workspace not found. Set CLAWD_TWAMM_ROOT." }; |
| #178 | } |
| #179 | |
| #180 | mkdirSync(resolve(status.root, "target"), { recursive: true }); |
| #181 | if (!options.skipAppInstall && !existsSync(resolve(status.root, "node_modules"))) { |
| #182 | const install = spawnSync("npm", ["install"], { |
| #183 | cwd: status.root, |
| #184 | env: process.env, |
| #185 | stdio: "pipe", |
| #186 | encoding: "utf8", |
| #187 | }); |
| #188 | if (install.status !== 0) { |
| #189 | return { |
| #190 | ok: false, |
| #191 | status: getTwammAutomationStatus(), |
| #192 | step: "npm install", |
| #193 | stdout: install.stdout, |
| #194 | stderr: install.stderr, |
| #195 | error: install.error?.message, |
| #196 | }; |
| #197 | } |
| #198 | } |
| #199 | |
| #200 | const result = spawnSync("anchor", ["build"], { |
| #201 | cwd: status.root, |
| #202 | env: process.env, |
| #203 | stdio: "pipe", |
| #204 | encoding: "utf8", |
| #205 | }); |
| #206 | |
| #207 | return { |
| #208 | ok: result.status === 0, |
| #209 | status: getTwammAutomationStatus(), |
| #210 | stdout: result.stdout, |
| #211 | stderr: result.stderr, |
| #212 | error: result.error?.message, |
| #213 | }; |
| #214 | } |
| #215 | |
| #216 | export function runTwammCrank(options: CrankOptions = {}): never { |
| #217 | const status = getTwammAutomationStatus(); |
| #218 | if (!status.exists || !status.crankScript) { |
| #219 | console.error("error: TWAMM workspace not found. Set CLAWD_TWAMM_ROOT."); |
| #220 | process.exit(2); |
| #221 | } |
| #222 | if (!status.liveEnabled || !status.operatorConfirmed || !options.yes) { |
| #223 | console.error("error: TWAMM crank is gated. Set CLAWD_TWAMM_LIVE=true, OPERATOR_CONFIRMED=true, and pass --yes."); |
| #224 | process.exit(3); |
| #225 | } |
| #226 | |
| #227 | const plan = buildTwammCrankPlan(options); |
| #228 | const result = spawnSync(plan.command, plan.args, { |
| #229 | cwd: plan.cwd, |
| #230 | env: { ...process.env, ...plan.env }, |
| #231 | stdio: "inherit", |
| #232 | }); |
| #233 | if (result.error) { |
| #234 | console.error(`error: failed to run TWAMM crank: ${result.error.message}`); |
| #235 | process.exit(127); |
| #236 | } |
| #237 | process.exit(typeof result.status === "number" ? result.status : 1); |
| #238 | } |
| #239 |