@@ -266,6 +266,130 @@ describe("stackflow agent", () => {
266266 store . close ( ) ;
267267 } ) ;
268268
269+ it ( "opens a pipe via signer adapter with expected contract call" , async ( ) => {
270+ const dbFile = tempDbFile ( "agent-open" ) ;
271+ const store = new AgentStateStore ( { dbFile } ) ;
272+
273+ const calls : Array < { contractId : string ; functionName : string ; functionArgs : unknown [ ] ; network ?: string } > = [ ] ;
274+
275+ const agent = new StackflowAgentService ( {
276+ stateStore : store ,
277+ signer : {
278+ async submitDispute ( ) {
279+ return { txid : "0x1" } ;
280+ } ,
281+ async callContract ( input ) {
282+ calls . push ( input as { contractId : string ; functionName : string ; functionArgs : unknown [ ] ; network ?: string } ) ;
283+ return { ok : true , txid : "0xopen" } ;
284+ } ,
285+ } ,
286+ network : "devnet" ,
287+ } ) ;
288+
289+ const result = await agent . openPipe ( {
290+ contractId : "ST1STACKFLOW.stackflow-0-6-0" ,
291+ token : null ,
292+ amount : "1000" ,
293+ counterpartyPrincipal : "ST1COUNTERPARTY" ,
294+ nonce : "0" ,
295+ } ) ;
296+
297+ expect ( result . ok ) . toBe ( true ) ;
298+ expect ( calls ) . toHaveLength ( 1 ) ;
299+ expect ( calls [ 0 ] ) . toEqual ( {
300+ contractId : "ST1STACKFLOW.stackflow-0-6-0" ,
301+ functionName : "fund-pipe" ,
302+ functionArgs : [ null , "1000" , "ST1COUNTERPARTY" , "0" ] ,
303+ network : "devnet" ,
304+ } ) ;
305+
306+ store . close ( ) ;
307+ } ) ;
308+
309+ it ( "builds outgoing transfer from tracked state and accepts signed incoming update" , async ( ) => {
310+ const dbFile = tempDbFile ( "agent-send-receive" ) ;
311+ const store = new AgentStateStore ( { dbFile } ) ;
312+
313+ const contractId = "ST1TESTABC.contract" ;
314+ const pipeKey = {
315+ "principal-1" : "ST1LOCAL" ,
316+ "principal-2" : "ST1OTHER" ,
317+ token : null ,
318+ } ;
319+ const pipeId = buildPipeId ( { contractId, pipeKey } ) ;
320+
321+ store . upsertTrackedPipe ( {
322+ pipeId,
323+ contractId,
324+ pipeKey,
325+ localPrincipal : "ST1LOCAL" ,
326+ counterpartyPrincipal : "ST1OTHER" ,
327+ token : null ,
328+ } ) ;
329+
330+ store . upsertSignatureState ( {
331+ contractId,
332+ pipeKey,
333+ forPrincipal : "ST1LOCAL" ,
334+ withPrincipal : "ST1OTHER" ,
335+ token : null ,
336+ myBalance : "100" ,
337+ theirBalance : "0" ,
338+ nonce : "0" ,
339+ action : "1" ,
340+ actor : "ST1LOCAL" ,
341+ mySignature : "0x" + "11" . repeat ( 65 ) ,
342+ theirSignature : "0x" + "22" . repeat ( 65 ) ,
343+ secret : null ,
344+ validAfter : null ,
345+ beneficialOnly : false ,
346+ } ) ;
347+
348+ const agent = new StackflowAgentService ( {
349+ stateStore : store ,
350+ signer : {
351+ async sip018Sign ( ) {
352+ return "0x" + "44" . repeat ( 65 ) ;
353+ } ,
354+ async submitDispute ( ) {
355+ return { txid : "0x1" } ;
356+ } ,
357+ async callContract ( ) {
358+ return { ok : true } ;
359+ } ,
360+ } ,
361+ } ) ;
362+
363+ const outgoing = agent . buildOutgoingTransfer ( {
364+ pipeId,
365+ amount : "25" ,
366+ actor : "ST1LOCAL" ,
367+ } ) ;
368+
369+ expect ( outgoing . myBalance ) . toBe ( "75" ) ;
370+ expect ( outgoing . theirBalance ) . toBe ( "25" ) ;
371+ expect ( outgoing . nonce ) . toBe ( "1" ) ;
372+
373+ const accepted = await agent . acceptIncomingTransfer ( {
374+ pipeId,
375+ payload : {
376+ ...outgoing ,
377+ actor : "ST1OTHER" ,
378+ theirSignature : "0x" + "33" . repeat ( 65 ) ,
379+ } ,
380+ } ) ;
381+
382+ expect ( accepted . accepted ) . toBe ( true ) ;
383+ expect ( accepted . mySignature ) . toMatch ( / ^ 0 x [ 0 - 9 a - f ] + $ / ) ;
384+
385+ const latest = store . getLatestSignatureState ( pipeId , "ST1LOCAL" ) ;
386+ expect ( latest ?. nonce ) . toBe ( "1" ) ;
387+ expect ( latest ?. myBalance ) . toBe ( "75" ) ;
388+ expect ( latest ?. theirBalance ) . toBe ( "25" ) ;
389+
390+ store . close ( ) ;
391+ } ) ;
392+
269393 it ( "defaults watcher interval to one hour" , ( ) => {
270394 const dbFile = tempDbFile ( "agent-interval" ) ;
271395 const store = new AgentStateStore ( { dbFile } ) ;
0 commit comments