33// Pipeline: Goal → [Analyst: snapshot] → [Risk Gate: evaluate] → [Executor: sign+broadcast]
44
55import { EventEmitter } from 'events'
6- import Anthropic from '@anthropic-ai/sdk'
76import { getDB } from './db/client.js'
87import { tasks , strategies , memory , knownProtocols } from './db/schema.js'
98import { eq , desc } from 'drizzle-orm'
@@ -18,10 +17,38 @@ import type {
1817 TaskStep ,
1918} from '@nocodeclarity/tools'
2019import { captureChainSnapshot } from '@nocodeclarity/tools/read'
21- import { signAndBroadcast , waitForConfirmation } from '@nocodeclarity/tools/write'
20+ import { signAndBroadcast , waitForConfirmation , getPublicKeyFromPrivate } from '@nocodeclarity/tools/write'
2221import { analyzeSnapshot , evaluateRisk , describeExecution } from '@nocodeclarity/agents'
2322
24- const anthropic = new Anthropic ( )
23+ // ── Anthropic API (direct fetch, no SDK) ─────────────────────────────────────
24+
25+ async function claudeChat ( system : string , userMessage : string , maxTokens = 500 ) : Promise < string > {
26+ const apiKey = process . env [ 'ANTHROPIC_API_KEY' ]
27+ if ( ! apiKey ) throw new Error ( 'ANTHROPIC_API_KEY is required' )
28+
29+ const response = await fetch ( 'https://api.anthropic.com/v1/messages' , {
30+ method : 'POST' ,
31+ headers : {
32+ 'Content-Type' : 'application/json' ,
33+ 'x-api-key' : apiKey ,
34+ 'anthropic-version' : '2023-06-01' ,
35+ } ,
36+ body : JSON . stringify ( {
37+ model : 'claude-haiku-4-5-20251001' ,
38+ max_tokens : maxTokens ,
39+ system,
40+ messages : [ { role : 'user' , content : userMessage } ] ,
41+ } ) ,
42+ } )
43+
44+ if ( ! response . ok ) {
45+ const err : any = await response . json ( ) . catch ( ( ) => ( { } ) )
46+ throw new Error ( `Anthropic API error ${ response . status } : ${ err ?. error ?. message ?? 'unknown' } ` )
47+ }
48+
49+ const data : any = await response . json ( )
50+ return data ?. content ?. [ 0 ] ?. text ?. trim ( ) ?? ''
51+ }
2552
2653// ── Constants ────────────────────────────────────────────────────────────────
2754
@@ -164,20 +191,13 @@ export class StacksSwarm extends EventEmitter {
164191 await import ( '@nocodeclarity/tools/write' )
165192
166193 // Step 1: classify goal
167- const classifyResponse = await anthropic . messages . create ( {
168- model : 'claude-haiku-4-5-20251001' ,
169- max_tokens : 50 ,
170- system : `Classify the user's goal into exactly one template.
171- Return ONLY the template name, nothing else.
172- Templates: deposit_yield, stack_pox, swap, transfer, unknown` ,
173- messages : [ { role : 'user' , content : goal } ]
174- } )
194+ const templateText = await claudeChat (
195+ `Classify the user's goal into exactly one template.\nReturn ONLY the template name, nothing else.\nTemplates: deposit_yield, stack_pox, swap, transfer, unknown` ,
196+ goal ,
197+ 50
198+ )
175199
176- const template = (
177- classifyResponse . content [ 0 ] ?. type === 'text'
178- ? classifyResponse . content [ 0 ] . text . trim ( )
179- : 'unknown'
180- ) as 'deposit_yield' | 'stack_pox' | 'swap' | 'transfer' | 'unknown'
200+ const template = ( templateText || 'unknown' ) as 'deposit_yield' | 'stack_pox' | 'swap' | 'transfer' | 'unknown'
181201
182202 if ( template === 'unknown' ) {
183203 throw new Error (
@@ -188,25 +208,11 @@ Templates: deposit_yield, stack_pox, swap, transfer, unknown`,
188208 }
189209
190210 // Step 2: extract parameters using LLM
191- const extractResponse = await anthropic . messages . create ( {
192- model : 'claude-haiku-4-5-20251001' ,
193- max_tokens : 500 ,
194- system : `Extract transaction parameters from the user's goal.
195- Return ONLY valid JSON. No preamble. No markdown.
196- Available wallet: ${ JSON . stringify ( snapshot . wallet . stxBalance ) }
197- Token balances: ${ JSON . stringify ( snapshot . wallet . tokenBalances ) }
198- Template: ${ template }
199-
200- For deposit_yield: { "token": "stx" | "sbtc", "amount": number_in_base_units }
201- For stack_pox: { "amount": number_in_microSTX, "cycles": number_1_to_12 }
202- For swap: { "fromToken": "stx" | "sbtc", "toToken": "stx" | "sbtc", "amount": number_in_base_units }
203- For transfer: { "token": "stx" | "sbtc", "amount": number_in_base_units, "recipient": "SP..." }` ,
204- messages : [ { role : 'user' , content : goal } ]
205- } )
206-
207- const paramsText = extractResponse . content [ 0 ] ?. type === 'text'
208- ? extractResponse . content [ 0 ] . text . trim ( )
209- : '{}'
211+ const paramsText = await claudeChat (
212+ `Extract transaction parameters from the user's goal.\nReturn ONLY valid JSON. No preamble. No markdown.\nAvailable wallet: ${ JSON . stringify ( snapshot . wallet . stxBalance ) } \nToken balances: ${ JSON . stringify ( snapshot . wallet . tokenBalances ) } \nTemplate: ${ template } \n\nFor deposit_yield: { "token": "stx" | "sbtc", "amount": number_in_base_units }\nFor stack_pox: { "amount": number_in_microSTX, "cycles": number_1_to_12 }\nFor swap: { "fromToken": "stx" | "sbtc", "toToken": "stx" | "sbtc", "amount": number_in_base_units }\nFor transfer: { "token": "stx" | "sbtc", "amount": number_in_base_units, "recipient": "SP..." }` ,
213+ goal ,
214+ 500
215+ )
210216
211217 let params : any
212218 try {
0 commit comments