11/**
22 * FlashStack Testnet Flash Loan Executor
3- * Runs 3 flash loans for Milestone 1 evidence
3+ * Waits for each transaction to confirm before sending the next.
44 *
55 * Usage: node scripts/run-flash-loans.mjs
66 */
77
88import { makeContractCall , broadcastTransaction , PostConditionMode , Cl } from "@stacks/transactions" ;
99import networkPkg from "@stacks/network" ;
10- const { STACKS_TESTNET } = networkPkg ;
10+ const { STACKS_TESTNET , STACKS_MAINNET } = networkPkg ;
1111import walletPkg from "@stacks/wallet-sdk" ;
1212const { generateWallet } = walletPkg ;
1313
14- const MNEMONIC = "thumb mask enroll era nasty lend universe choose treat purchase monster ancient utility enjoy hero arrive crime miss misery cage digital acoustic security emerge" ;
15- const DEPLOYER = "STX0KBBP3T4QRBTWHYA15VEKYVKNWW722EGZ2TEM" ;
16- const network = STACKS_TESTNET ;
14+ const MNEMONIC = "embrace denial purse peace alone insect ranch install milk upon switch exclude carry spot merge pizza increase fancy quarter glide country disorder fabric ability" ;
15+ const DEPLOYER = "SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ" ;
16+ const network = STACKS_MAINNET ;
17+ const API = "https://api.hiro.so" ;
1718
1819async function getPrivateKey ( ) {
1920 const wallet = await generateWallet ( { secretKey : MNEMONIC , password : "" } ) ;
20- const account = wallet . accounts [ 0 ] ;
21- return account . stxPrivateKey ;
21+ return wallet . accounts [ 0 ] . stxPrivateKey ;
2222}
2323
24- async function callContract ( privateKey , contractName , functionName , args ) {
24+ async function getNonce ( ) {
25+ const res = await fetch ( `${ API } /v2/accounts/${ DEPLOYER } ?proof=0` ) ;
26+ const data = await res . json ( ) ;
27+ return data . nonce ;
28+ }
29+
30+ async function waitForConfirm ( txid , label ) {
31+ process . stdout . write ( ` Waiting for ${ label } to confirm` ) ;
32+ for ( let i = 0 ; i < 60 ; i ++ ) {
33+ await new Promise ( r => setTimeout ( r , 10000 ) ) ; // wait 10s per attempt
34+ const res = await fetch ( `${ API } /extended/v1/tx/0x${ txid } ` ) ;
35+ const data = await res . json ( ) ;
36+ if ( data . tx_status === "success" ) {
37+ console . log ( " confirmed." ) ;
38+ return true ;
39+ }
40+ if ( data . tx_status === "abort_by_response" || data . tx_status === "abort_by_post_condition" ) {
41+ const result = data . tx_result ?. repr || "" ;
42+ console . log ( ` FAILED: ${ result } ` ) ;
43+ throw new Error ( `Transaction ${ label } failed: ${ result } ` ) ;
44+ }
45+ process . stdout . write ( "." ) ;
46+ }
47+ throw new Error ( `Timeout waiting for ${ label } ` ) ;
48+ }
49+
50+ async function callContract ( privateKey , nonce , contractName , functionName , args ) {
2551 const tx = await makeContractCall ( {
2652 contractAddress : DEPLOYER ,
2753 contractName,
@@ -31,80 +57,90 @@ async function callContract(privateKey, contractName, functionName, args) {
3157 network,
3258 postConditionMode : PostConditionMode . Allow ,
3359 anchorMode : 1 ,
60+ nonce,
3461 } ) ;
3562 const result = await broadcastTransaction ( { transaction : tx , network } ) ;
36- return result ;
37- }
38-
39- function sleep ( ms ) {
40- return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
63+ if ( result . error ) throw new Error ( `Broadcast failed: ${ result . error } — ${ result . reason } ` ) ;
64+ return result . txid ;
4165}
4266
4367async function main ( ) {
4468 console . log ( "FlashStack Testnet Flash Loan Executor" ) ;
4569 console . log ( "======================================" ) ;
70+ console . log ( `Deployer: ${ DEPLOYER } \n` ) ;
4671
4772 const privateKey = await getPrivateKey ( ) ;
48- console . log ( `Deployer: ${ DEPLOYER } \n` ) ;
73+ let nonce = await getNonce ( ) ;
74+ console . log ( `Starting nonce: ${ nonce } \n` ) ;
4975
5076 // Step 1: Authorize flashstack-core as flash minter
5177 console . log ( "Step 1: Authorizing flashstack-core as flash minter..." ) ;
52- const r1 = await callContract ( privateKey , "sbtc-token" , "set-flash-minter" , [
78+ const tx1 = await callContract ( privateKey , nonce ++ , "sbtc-token" , "set-flash-minter" , [
5379 Cl . principal ( `${ DEPLOYER } .flashstack-core` ) ,
5480 ] ) ;
55- console . log ( ` txid: ${ r1 . txid ?? JSON . stringify ( r1 ) } ` ) ;
56- await sleep ( 2000 ) ;
81+ console . log ( ` txid: ${ tx1 } ` ) ;
82+ await waitForConfirm ( tx1 , "set-flash-minter" ) ;
5783
5884 // Step 2: Whitelist test-receiver
5985 console . log ( "Step 2: Whitelisting test-receiver..." ) ;
60- const r2 = await callContract ( privateKey , "flashstack-core" , "add-approved-receiver" , [
86+ const tx2 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "add-approved-receiver" , [
6187 Cl . principal ( `${ DEPLOYER } .test-receiver` ) ,
6288 ] ) ;
63- console . log ( ` txid: ${ r2 . txid ?? JSON . stringify ( r2 ) } ` ) ;
64- await sleep ( 2000 ) ;
89+ console . log ( ` txid: ${ tx2 } ` ) ;
90+ await waitForConfirm ( tx2 , "add-approved-receiver" ) ;
6591
6692 // Step 3: Set test collateral (30,000 STX)
6793 console . log ( "Step 3: Setting test collateral (30,000 STX for deployer)..." ) ;
68- const r3 = await callContract ( privateKey , "flashstack-core" , "set-test-stx-locked" , [
94+ const tx3 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "set-test-stx-locked" , [
6995 Cl . principal ( DEPLOYER ) ,
7096 Cl . uint ( 30000000000 ) ,
7197 ] ) ;
72- console . log ( ` txid: ${ r3 . txid ?? JSON . stringify ( r3 ) } ` ) ;
73- await sleep ( 2000 ) ;
98+ console . log ( ` txid: ${ tx3 } ` ) ;
99+ await waitForConfirm ( tx3 , "set-test-stx-locked" ) ;
74100
75- // Flash Loan 1: 0.01 sBTC
76- console . log ( "\nFlash Loan #1: 0.01 sBTC (1,000,000 sats) ..." ) ;
77- const fl1 = await callContract ( privateKey , "flashstack-core" , "flash-mint" , [
101+ // Flash Loan 1: 0.01 sBTC — test-receiver
102+ console . log ( "\nFlash Loan #1: 0.01 sBTC via test-receiver ..." ) ;
103+ const fl1 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "flash-mint" , [
78104 Cl . uint ( 1000000 ) ,
79105 Cl . principal ( `${ DEPLOYER } .test-receiver` ) ,
80106 ] ) ;
81- console . log ( ` txid: ${ fl1 . txid ?? JSON . stringify ( fl1 ) } ` ) ;
82- await sleep ( 2000 ) ;
107+ console . log ( ` txid: ${ fl1 } ` ) ;
108+ await waitForConfirm ( fl1 , "flash-mint #1" ) ;
83109
84- // Flash Loan 2: 0.05 sBTC
85- console . log ( "Flash Loan #2: 0.05 sBTC (5,000,000 sats) ..." ) ;
86- const fl2 = await callContract ( privateKey , "flashstack-core" , "flash-mint" , [
110+ // Flash Loan 2: 0.05 sBTC — test-receiver
111+ console . log ( "Flash Loan #2: 0.05 sBTC via test-receiver ..." ) ;
112+ const fl2 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "flash-mint" , [
87113 Cl . uint ( 5000000 ) ,
88114 Cl . principal ( `${ DEPLOYER } .test-receiver` ) ,
89115 ] ) ;
90- console . log ( ` txid: ${ fl2 . txid ?? JSON . stringify ( fl2 ) } ` ) ;
91- await sleep ( 2000 ) ;
116+ console . log ( ` txid: ${ fl2 } ` ) ;
117+ await waitForConfirm ( fl2 , "flash-mint #2" ) ;
118+
119+ // Flash Loan 3: 0.10 sBTC — dex-aggregator-receiver (ARBITRAGE execution)
120+ console . log ( "Flash Loan #3: 0.10 sBTC via dex-aggregator-receiver (arbitrage)..." ) ;
121+
122+ // Whitelist the dex-aggregator-receiver for the arbitrage execution
123+ console . log ( " Whitelisting dex-aggregator-receiver..." ) ;
124+ const wl2 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "add-approved-receiver" , [
125+ Cl . principal ( `${ DEPLOYER } .dex-aggregator-receiver` ) ,
126+ ] ) ;
127+ console . log ( ` txid: ${ wl2 } ` ) ;
128+ await waitForConfirm ( wl2 , "whitelist dex-aggregator-receiver" ) ;
92129
93- // Flash Loan 3: 0.10 sBTC
94- console . log ( "Flash Loan #3: 0.10 sBTC (10,000,000 sats)..." ) ;
95- const fl3 = await callContract ( privateKey , "flashstack-core" , "flash-mint" , [
130+ const fl3 = await callContract ( privateKey , nonce ++ , "flashstack-core" , "flash-mint" , [
96131 Cl . uint ( 10000000 ) ,
97- Cl . principal ( `${ DEPLOYER } .test -receiver` ) ,
132+ Cl . principal ( `${ DEPLOYER } .dex-aggregator -receiver` ) ,
98133 ] ) ;
99- console . log ( ` txid: ${ fl3 . txid ?? JSON . stringify ( fl3 ) } ` ) ;
134+ console . log ( ` txid: ${ fl3 } ` ) ;
135+ await waitForConfirm ( fl3 , "flash-mint #3 (arbitrage)" ) ;
100136
101137 console . log ( "\n======================================" ) ;
102- console . log ( "MILESTONE 1 EVIDENCE - SAVE THESE LINKS" ) ;
138+ console . log ( "MILESTONE 2 EVIDENCE - SAVE THESE LINKS" ) ;
103139 console . log ( "======================================" ) ;
104- console . log ( `Flash Loan 1: https://explorer.hiro.so/txid/${ fl1 . txid } ?chain=testnet ` ) ;
105- console . log ( `Flash Loan 2: https://explorer.hiro.so/txid/${ fl2 . txid } ?chain=testnet ` ) ;
106- console . log ( `Flash Loan 3: https://explorer.hiro.so/txid/${ fl3 . txid } ?chain=testnet ` ) ;
140+ console . log ( `Flash Loan 1: https://explorer.hiro.so/txid/${ fl1 } ` ) ;
141+ console . log ( `Flash Loan 2: https://explorer.hiro.so/txid/${ fl2 } ` ) ;
142+ console . log ( `Flash Loan 3 (arbitrage) : https://explorer.hiro.so/txid/${ fl3 } ` ) ;
107143 console . log ( "======================================" ) ;
108144}
109145
110- main ( ) . catch ( console . error ) ;
146+ main ( ) . catch ( e => { console . error ( "\nERROR:" , e . message ) ; process . exit ( 1 ) ; } ) ;
0 commit comments