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 | export type TradingMode = "observe" | "paper" | "live"; |
| #2 | |
| #3 | export interface PerpsRiskLimits { |
| #4 | allowedSymbols: string[]; |
| #5 | maxNotionalUsd: number; |
| #6 | maxLeverage: number; |
| #7 | maxSpreadBps: number; |
| #8 | requireWallet: boolean; |
| #9 | } |
| #10 | |
| #11 | export interface PerpsRuntimeConfig { |
| #12 | rpcUrl: string; |
| #13 | apiUrl: string; |
| #14 | heliusApiKey?: string; |
| #15 | wallet?: string; |
| #16 | traderPdaIndex: number; |
| #17 | traderSubaccountIndex: number; |
| #18 | liveTrading: boolean; |
| #19 | operatorConfirmed: boolean; |
| #20 | simOnly: boolean; |
| #21 | telegramBotToken?: string; |
| #22 | telegramAllowedChats: string[]; |
| #23 | risk: PerpsRiskLimits; |
| #24 | } |
| #25 | |
| #26 | export interface PreflightRequest { |
| #27 | symbol: string; |
| #28 | notionalUsd: number; |
| #29 | leverage?: number; |
| #30 | expectedSpreadBps?: number; |
| #31 | execution: "observe" | "paper" | "vulcan-live" | "rise-live"; |
| #32 | } |
| #33 | |
| #34 | export interface PreflightReport { |
| #35 | ok: boolean; |
| #36 | mode: TradingMode; |
| #37 | blocking: string[]; |
| #38 | warnings: string[]; |
| #39 | } |
| #40 | |
| #41 | function parseCsv(value: string | undefined): string[] { |
| #42 | if (!value) { |
| #43 | return []; |
| #44 | } |
| #45 | return value |
| #46 | .split(",") |
| #47 | .map((item) => item.trim()) |
| #48 | .filter(Boolean); |
| #49 | } |
| #50 | |
| #51 | function normalizeSymbols(symbols: string[]): string[] { |
| #52 | return symbols.map((symbol) => symbol.toUpperCase()); |
| #53 | } |
| #54 | |
| #55 | export function loadPerpsRuntimeConfig(env: NodeJS.ProcessEnv = process.env): PerpsRuntimeConfig { |
| #56 | return { |
| #57 | rpcUrl: env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com", |
| #58 | apiUrl: env.CLAWD_PERPS_API_URL ?? "https://perp-api.phoenix.trade", |
| #59 | heliusApiKey: env.HELIUS_API_KEY || undefined, |
| #60 | wallet: env.CLAWD_PERPS_WALLET || undefined, |
| #61 | traderPdaIndex: Number(env.CLAWD_PERPS_TRADER_PDA_INDEX ?? 0), |
| #62 | traderSubaccountIndex: Number(env.CLAWD_PERPS_TRADER_SUBACCOUNT_INDEX ?? 0), |
| #63 | liveTrading: env.LIVE_TRADING === "true", |
| #64 | operatorConfirmed: env.OPERATOR_CONFIRMED === "true", |
| #65 | simOnly: env.PERPS_SIM_ONLY !== "false", |
| #66 | telegramBotToken: env.TELEGRAM_BOT_TOKEN || undefined, |
| #67 | telegramAllowedChats: parseCsv(env.TELEGRAM_ALLOWED_CHATS), |
| #68 | risk: { |
| #69 | allowedSymbols: normalizeSymbols(parseCsv(env.PERPS_ALLOWED_SYMBOLS ?? "SOL,ETH,BTC")), |
| #70 | maxNotionalUsd: Number(env.PERPS_MAX_NOTIONAL_USD ?? 250), |
| #71 | maxLeverage: Number(env.PERPS_MAX_LEVERAGE ?? 3), |
| #72 | maxSpreadBps: Number(env.PERPS_MAX_SPREAD_BPS ?? 40), |
| #73 | requireWallet: env.PERPS_REQUIRE_WALLET !== "false", |
| #74 | }, |
| #75 | }; |
| #76 | } |
| #77 | |
| #78 | export function resolveTradingMode(config: PerpsRuntimeConfig): TradingMode { |
| #79 | if (config.liveTrading && config.operatorConfirmed && !config.simOnly) { |
| #80 | return "live"; |
| #81 | } |
| #82 | if (!config.simOnly) { |
| #83 | return "paper"; |
| #84 | } |
| #85 | return "observe"; |
| #86 | } |
| #87 | |
| #88 | export function buildPreflightReport( |
| #89 | config: PerpsRuntimeConfig, |
| #90 | request: PreflightRequest, |
| #91 | ): PreflightReport { |
| #92 | const blocking: string[] = []; |
| #93 | const warnings: string[] = []; |
| #94 | const mode = resolveTradingMode(config); |
| #95 | const symbol = request.symbol.trim().toUpperCase(); |
| #96 | |
| #97 | if (!config.rpcUrl) { |
| #98 | blocking.push("Missing SOLANA_RPC_URL."); |
| #99 | } |
| #100 | if (config.risk.requireWallet && !config.wallet) { |
| #101 | blocking.push("Missing CLAWD_PERPS_WALLET."); |
| #102 | } |
| #103 | if (!config.risk.allowedSymbols.includes(symbol)) { |
| #104 | blocking.push(`Symbol ${symbol} is outside PERPS_ALLOWED_SYMBOLS.`); |
| #105 | } |
| #106 | if (request.notionalUsd <= 0) { |
| #107 | blocking.push("Notional must be positive."); |
| #108 | } |
| #109 | if (request.notionalUsd > config.risk.maxNotionalUsd) { |
| #110 | blocking.push( |
| #111 | `Notional ${request.notionalUsd} exceeds PERPS_MAX_NOTIONAL_USD=${config.risk.maxNotionalUsd}.`, |
| #112 | ); |
| #113 | } |
| #114 | if (request.leverage !== undefined && request.leverage > config.risk.maxLeverage) { |
| #115 | blocking.push( |
| #116 | `Leverage ${request.leverage} exceeds PERPS_MAX_LEVERAGE=${config.risk.maxLeverage}.`, |
| #117 | ); |
| #118 | } |
| #119 | if ( |
| #120 | request.expectedSpreadBps !== undefined && |
| #121 | request.expectedSpreadBps > config.risk.maxSpreadBps |
| #122 | ) { |
| #123 | blocking.push( |
| #124 | `Spread ${request.expectedSpreadBps}bps exceeds PERPS_MAX_SPREAD_BPS=${config.risk.maxSpreadBps}.`, |
| #125 | ); |
| #126 | } |
| #127 | if (request.execution === "rise-live" || request.execution === "vulcan-live") { |
| #128 | if (mode !== "live") { |
| #129 | blocking.push( |
| #130 | "Live execution disabled. Require LIVE_TRADING=true, OPERATOR_CONFIRMED=true, and PERPS_SIM_ONLY=false.", |
| #131 | ); |
| #132 | } |
| #133 | warnings.push("Live execution path must still simulate and sign outside this adapter."); |
| #134 | } |
| #135 | if (mode !== "live") { |
| #136 | warnings.push(`Runtime mode is ${mode}; execution should remain observe/paper.`); |
| #137 | } |
| #138 | |
| #139 | return { |
| #140 | ok: blocking.length === 0, |
| #141 | mode, |
| #142 | blocking, |
| #143 | warnings, |
| #144 | }; |
| #145 | } |
| #146 | |
| #147 | export function assertLiveTradingAllowed( |
| #148 | config: PerpsRuntimeConfig, |
| #149 | request: Omit<PreflightRequest, "execution">, |
| #150 | ): PreflightReport { |
| #151 | const report = buildPreflightReport(config, { |
| #152 | ...request, |
| #153 | execution: "rise-live", |
| #154 | }); |
| #155 | if (!report.ok) { |
| #156 | throw new Error(report.blocking.join(" ")); |
| #157 | } |
| #158 | return report; |
| #159 | } |
| #160 |