strade-stx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/.activity_counter +1 -0
  2. package/.gitattributes +3 -0
  3. package/.vscode/settings.json +4 -0
  4. package/.vscode/tasks.json +19 -0
  5. package/CHANGELOG.md +1 -0
  6. package/Clarinet.toml +56 -0
  7. package/Clarinet.toml.backup +174 -0
  8. package/Clarinet.toml.old +146 -0
  9. package/DEPLOYMENT_RESULTS.md +160 -0
  10. package/README.md +344 -0
  11. package/TODO.md +34 -0
  12. package/contracts/CoreMarketPlace.clar +227 -0
  13. package/contracts/DisputeResolution_clar.clar +265 -0
  14. package/contracts/EscrowService.clar +171 -0
  15. package/contracts/UserProfile.clar +280 -0
  16. package/contracts/ft-trait.clar +24 -0
  17. package/contracts/token.clar +178 -0
  18. package/costs-reports.json +76026 -0
  19. package/deployments/default.mainnet-plan.yaml +67 -0
  20. package/deployments/default.simnet-plan.yaml +105 -0
  21. package/deployments/default.testnet-plan.yaml +67 -0
  22. package/deployments/new-contracts.testnet-plan.yaml +32 -0
  23. package/frontend/README.md +10 -0
  24. package/frontend/components.json +22 -0
  25. package/frontend/dist/assets/index-BacuuL66.css +1 -0
  26. package/frontend/dist/assets/index-jryypd5B.js +194 -0
  27. package/frontend/dist/favicon.png +0 -0
  28. package/frontend/dist/index.html +15 -0
  29. package/frontend/dist/manifest.json +15 -0
  30. package/frontend/dist/vite.svg +1 -0
  31. package/frontend/empty-mock.js +1 -0
  32. package/frontend/eslint.config.js +23 -0
  33. package/frontend/eslint.config.mjs +25 -0
  34. package/frontend/index.html +14 -0
  35. package/frontend/next.config.ts +17 -0
  36. package/frontend/package-lock.json +14740 -0
  37. package/frontend/package.json +56 -0
  38. package/frontend/postcss.config.js +5 -0
  39. package/frontend/postcss.config.mjs +5 -0
  40. package/frontend/public/favicon.png +0 -0
  41. package/frontend/public/file.svg +1 -0
  42. package/frontend/public/globe.svg +1 -0
  43. package/frontend/public/manifest.json +15 -0
  44. package/frontend/public/next.svg +1 -0
  45. package/frontend/public/vercel.svg +1 -0
  46. package/frontend/public/vite.svg +1 -0
  47. package/frontend/public/window.svg +1 -0
  48. package/frontend/src/App.css +42 -0
  49. package/frontend/src/App.tsx +177 -0
  50. package/frontend/src/app/about/page.tsx +208 -0
  51. package/frontend/src/app/favicon.ico +0 -0
  52. package/frontend/src/app/globals.css +129 -0
  53. package/frontend/src/app/help/page.tsx +167 -0
  54. package/frontend/src/app/how-it-works/page.tsx +274 -0
  55. package/frontend/src/app/layout.tsx +55 -0
  56. package/frontend/src/app/marketplace/page.tsx +324 -0
  57. package/frontend/src/app/my-listings/page.tsx +318 -0
  58. package/frontend/src/app/page.tsx +15 -0
  59. package/frontend/src/assets/react.svg +1 -0
  60. package/frontend/src/components/ConfirmDialog.tsx +54 -0
  61. package/frontend/src/components/CreateListingForm.tsx +231 -0
  62. package/frontend/src/components/ErrorBoundary.tsx +73 -0
  63. package/frontend/src/components/FilterPanel.tsx +10 -0
  64. package/frontend/src/components/Footer.tsx +100 -0
  65. package/frontend/src/components/Header.tsx +268 -0
  66. package/frontend/src/components/ImageUpload.tsx +147 -0
  67. package/frontend/src/components/LandingPage.tsx +322 -0
  68. package/frontend/src/components/ListingCard.tsx +154 -0
  69. package/frontend/src/components/LoadingSkeleton.tsx +44 -0
  70. package/frontend/src/components/MobileNav.tsx +89 -0
  71. package/frontend/src/components/NotificationBell.tsx +8 -0
  72. package/frontend/src/components/NotificationPanel.tsx +14 -0
  73. package/frontend/src/components/README.md +14 -0
  74. package/frontend/src/components/SearchBar.tsx +10 -0
  75. package/frontend/src/components/TestnetBanner.tsx +29 -0
  76. package/frontend/src/components/ThemeToggle.tsx +32 -0
  77. package/frontend/src/components/__tests__/Header.test.tsx +70 -0
  78. package/frontend/src/components/__tests__/ListingCard.test.tsx +86 -0
  79. package/frontend/src/components/providers/ThemeProvider.tsx +9 -0
  80. package/frontend/src/components/ui/alert-dialog.tsx +141 -0
  81. package/frontend/src/components/ui/avatar.tsx +53 -0
  82. package/frontend/src/components/ui/badge.tsx +46 -0
  83. package/frontend/src/components/ui/button.tsx +60 -0
  84. package/frontend/src/components/ui/card.tsx +92 -0
  85. package/frontend/src/components/ui/dialog.tsx +143 -0
  86. package/frontend/src/components/ui/dropdown-menu.tsx +257 -0
  87. package/frontend/src/components/ui/input.tsx +21 -0
  88. package/frontend/src/components/ui/label.tsx +24 -0
  89. package/frontend/src/components/ui/select.tsx +187 -0
  90. package/frontend/src/components/ui/sonner.tsx +40 -0
  91. package/frontend/src/components/ui/textarea.tsx +18 -0
  92. package/frontend/src/context/README.md +27 -0
  93. package/frontend/src/index.css +166 -0
  94. package/frontend/src/lib/notificationEvents.ts +10 -0
  95. package/frontend/src/lib/notificationStore.ts +13 -0
  96. package/frontend/src/lib/notifications.ts +13 -0
  97. package/frontend/src/lib/search.ts +28 -0
  98. package/frontend/src/lib/stacks.ts +189 -0
  99. package/frontend/src/lib/utils.ts +6 -0
  100. package/frontend/src/main.tsx +10 -0
  101. package/frontend/src/test/setup.ts +23 -0
  102. package/frontend/src/types.d.ts +9 -0
  103. package/frontend/tsconfig.app.json +28 -0
  104. package/frontend/tsconfig.json +41 -0
  105. package/frontend/tsconfig.node.json +26 -0
  106. package/frontend/vercel.json +4 -0
  107. package/frontend/vite.config.ts +6 -0
  108. package/frontend/vitest.config.ts +17 -0
  109. package/lcov.info +31338 -0
  110. package/mainnetcontracts.md +16 -0
  111. package/package.json +53 -0
  112. package/scripts/auto-activity.sh +9 -0
  113. package/scripts/cancel-pending.ts +67 -0
  114. package/scripts/check-balances.ts +23 -0
  115. package/scripts/distribute-evenly.ts +56 -0
  116. package/scripts/drain-accounts.ts +70 -0
  117. package/scripts/fund-accounts.ts +88 -0
  118. package/scripts/fund-active.ts +59 -0
  119. package/scripts/fund-unfunded.ts +88 -0
  120. package/scripts/generate-activity.ts +181 -0
  121. package/scripts/git-activity-generator.ts +154 -0
  122. package/scripts/mobile-server.ts +123 -0
  123. package/settings/Devnet.toml +155 -0
  124. package/settings/Mainnet.toml +7 -0
  125. package/settings/Testnet.toml +9 -0
  126. package/tests/CoreMarketPlace.fuzz.test.ts +435 -0
  127. package/tests/CoreMarketPlace.test.ts +564 -0
  128. package/tsconfig.json +26 -0
  129. package/vitest.config.js +49 -0
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Strade Activity Generator
4
+ * Usage:
5
+ * 1. Set PRIVATE_KEY below (64-char hex)
6
+ * 2. Set DRY_RUN=false when ready
7
+ * 3. npx tsx scripts/generate-activity.ts
8
+ */
9
+
10
+ import {
11
+ broadcastTransaction,
12
+ makeContractCall,
13
+ noneCV,
14
+ principalCV,
15
+ stringUtf8CV,
16
+ uintCV,
17
+ AnchorMode,
18
+ } from '@stacks/transactions';
19
+ import { StacksMainnet } from '@stacks/network';
20
+ import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
21
+ import { readFileSync } from 'fs';
22
+ import { resolve } from 'path';
23
+ import { execSync } from 'child_process';
24
+
25
+ const DRY_RUN = false; // set to false to broadcast
26
+
27
+ // curl-based fetch to bypass Node.js network restrictions
28
+ const curlFetch = async (url: string, opts: any = {}) => {
29
+ const method = opts.method || 'GET';
30
+ const headers = opts.headers ? Object.entries(opts.headers).map(([k,v]) => `-H '${k}: ${v}'`).join(' ') : '';
31
+ if (opts.body) {
32
+ const tmpFile = `/tmp/stx_req_${Date.now()}.bin`;
33
+ const { writeFileSync, unlinkSync } = await import('fs');
34
+ writeFileSync(tmpFile, opts.body);
35
+ const result = execSync(`curl -s --max-time 30 -X ${method} ${headers} --data-binary @${tmpFile} '${url}'`, { timeout: 35000 }).toString();
36
+ unlinkSync(tmpFile);
37
+ return { ok: true, json: async () => JSON.parse(result), text: async () => result };
38
+ }
39
+ const result = execSync(`curl -s -X ${method} ${headers} '${url}'`, { timeout: 15000 }).toString();
40
+ return { ok: true, json: async () => JSON.parse(result), text: async () => result };
41
+ };
42
+
43
+ // Read mnemonic from settings/Mainnet.toml
44
+ const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
45
+ const mnemonicMatch = toml.match(/mnemonic\s*=\s*"([^"]+)"/);
46
+ if (!mnemonicMatch) { console.error('mnemonic not found in settings/Mainnet.toml'); process.exit(1); }
47
+ const MNEMONIC = mnemonicMatch[1];
48
+
49
+ // Derive 100 accounts from the same mnemonic (indices 0-99)
50
+ const NUM_ACCOUNTS = 50;
51
+ const baseWallet = await generateWallet({ secretKey: MNEMONIC, password: '' });
52
+ let wallet = baseWallet;
53
+ for (let i = 1; i < NUM_ACCOUNTS; i++) {
54
+ wallet = generateNewAccount(wallet);
55
+ }
56
+ const accounts = wallet.accounts.slice(0, NUM_ACCOUNTS).map(a => a.stxPrivateKey);
57
+ const CONTRACT_PRINCIPAL = 'SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N';
58
+ const DELAY_MS = 3000;
59
+
60
+ const network = new StacksMainnet({ fetchFn: curlFetch as any });
61
+ const rand = () => Math.random().toString(36).slice(2, 8);
62
+
63
+ async function getNonce(address: string): Promise<number> {
64
+ for (let attempt = 1; attempt <= 3; attempt++) {
65
+ try {
66
+ const result = execSync(`curl -s --max-time 15 'https://api.hiro.so/extended/v1/address/${address}/nonces'`, { timeout: 20000 }).toString();
67
+ if (result.includes('Per-minute') || result.includes('rate limit')) throw new Error('rate limited');
68
+ const data = JSON.parse(result) as { possible_next_nonce: number };
69
+ return data.possible_next_nonce;
70
+ } catch (e: any) {
71
+ console.error(` getNonce attempt ${attempt}/3 failed: ${e.message}`);
72
+ if (attempt < 3) await new Promise(r => setTimeout(r, 10000)); // 10s backoff on rate limit
73
+ }
74
+ }
75
+ throw new Error('Failed to get nonce after 3 attempts');
76
+ }
77
+
78
+ async function sendTx(contractName: string, fn: string, args: any[], nonce: number, privateKey: string) {
79
+ const tx = await makeContractCall({
80
+ contractAddress: CONTRACT_PRINCIPAL,
81
+ contractName,
82
+ functionName: fn,
83
+ functionArgs: args,
84
+ senderKey: privateKey,
85
+ validateWithAbi: false,
86
+ network,
87
+ anchorMode: AnchorMode.Any,
88
+ nonce,
89
+ fee: 4_000, // 0.2 STX total per cycle across 50 accounts
90
+ });
91
+
92
+ console.log(`[${nonce}] ${contractName}.${fn}`);
93
+
94
+ if (DRY_RUN) {
95
+ console.log(' DRY-RUN: skipped');
96
+ return;
97
+ }
98
+
99
+ try {
100
+ const res = await broadcastTransaction(tx, network);
101
+ if ('txid' in res) {
102
+ console.log(` ✅ https://explorer.hiro.so/txid/${res.txid}`);
103
+ } else {
104
+ console.error(` ❌ ${JSON.stringify(res)}`);
105
+ }
106
+ } catch (e: any) {
107
+ console.error(` ❌ ${e.message}`);
108
+ }
109
+
110
+ await new Promise(r => setTimeout(r, DELAY_MS));
111
+ }
112
+
113
+ async function main() {
114
+ const { getAddressFromPrivateKey } = await import('@stacks/transactions');
115
+
116
+ // Build per-account state sequentially to avoid rate limiting
117
+ const allStates = [];
118
+ for (let i = 0; i < accounts.length; i++) {
119
+ const pk = accounts[i];
120
+ const { getAddressFromPrivateKey } = await import('@stacks/transactions');
121
+ const address = getAddressFromPrivateKey(pk, network.version);
122
+ const nonce = await getNonce(address);
123
+ let pending = 0, balance = 0;
124
+ try {
125
+ const mempool = JSON.parse(execSync(`curl -s --max-time 15 'https://api.hiro.so/extended/v1/address/${address}/mempool?limit=1'`, { timeout: 20000 }).toString());
126
+ pending = mempool.total ?? 0;
127
+ const balData = JSON.parse(execSync(`curl -s --max-time 15 'https://api.hiro.so/v2/accounts/${address}?proof=0'`, { timeout: 20000 }).toString());
128
+ balance = parseInt(balData.balance, 16);
129
+ } catch { pending = 0; }
130
+ console.log(`Account ${i}: ${address} (nonce: ${nonce}, pending: ${pending}, balance: ${(balance/1e6).toFixed(4)} STX)`);
131
+ allStates.push({ pk, address, nonce, pending, balance });
132
+ await new Promise(r => setTimeout(r, 2000));
133
+ }
134
+
135
+ const accountStates = allStates.filter(s => s.pending < 3 && s.balance > 1000);
136
+ if (accountStates.length === 0) { console.log('All accounts have too many pending txs. Try again later.'); return; }
137
+ console.log(`\nUsing ${accountStates.length}/${allStates.length} accounts\nDRY_RUN=${DRY_RUN}\n`);
138
+
139
+ // 1 tx per account per run — rotate through different contract calls
140
+ // Rotate through different contract calls - 1 per account per run
141
+ const calls = [
142
+ (s: any) => sendTx('UserProfile', 'update-profile', [
143
+ stringUtf8CV(`Bio ${rand()}`), stringUtf8CV(`${rand()}@test.com`)
144
+ ], s.nonce++, s.pk),
145
+
146
+ (s: any) => sendTx('CoreMarketPlace', 'create-listing', [
147
+ stringUtf8CV(`Item ${rand()}`), stringUtf8CV(`Desc ${rand()}`), uintCV(500_000), uintCV(144)
148
+ ], s.nonce++, s.pk),
149
+
150
+ (s: any) => sendTx('CoreMarketPlace', 'update-listing', [
151
+ uintCV(Math.floor(Math.random() * 100) + 1),
152
+ uintCV(750_000),
153
+ stringUtf8CV(`Updated desc ${rand()}`)
154
+ ], s.nonce++, s.pk),
155
+
156
+ (s: any) => sendTx('UserProfile', 'rate-user', [
157
+ principalCV(accountStates[(accountStates.indexOf(s) + 1) % accountStates.length].address),
158
+ uintCV(Math.floor(Math.random() * 5) + 1)
159
+ ], s.nonce++, s.pk),
160
+
161
+ (s: any) => sendTx('UserProfile', 'calculate-reputation', [
162
+ principalCV(s.address)
163
+ ], s.nonce++, s.pk),
164
+
165
+ (s: any) => sendTx('CoreMarketPlace', 'create-listing', [
166
+ stringUtf8CV(`Product ${rand()}`), stringUtf8CV(`Details ${rand()}`), uintCV(1_000_000), uintCV(288)
167
+ ], s.nonce++, s.pk),
168
+ ];
169
+
170
+ for (let i = 0; i < accountStates.length; i++) {
171
+ const s = accountStates[i];
172
+ // Re-fetch nonce right before sending to avoid BadNonce
173
+ s.nonce = await getNonce(s.address);
174
+ await calls[i % calls.length](s);
175
+ await new Promise(r => setTimeout(r, 500));
176
+ }
177
+
178
+ console.log('\nDone!');
179
+ }
180
+
181
+ main().catch(console.error);
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Activity Generator for Strade
4
+ * Creates commits for GitHub activity. Supports resuming from existing commits.
5
+ *
6
+ * Usage:
7
+ * npx tsx scripts/git-activity-generator.ts --help
8
+ * npx tsx scripts/git-activity-generator.ts --dry-run
9
+ * npx tsx scripts/git-activity-generator.ts
10
+ * npx tsx scripts/git-activity-generator.ts --total 500
11
+ *
12
+ * WARNINGS:
13
+ * - Creates temp files in ./temp-commits/ and commits them.
14
+ * - Rate limited by GitHub (consider delays).
15
+ * - Check GitHub TOS; for testing only.
16
+ * - Runs `git push`, `gh pr create`. Ensure authenticated.
17
+ * - Cleanup: rm -rf temp-commits/ && git branch -D fake-activity-pr-*
18
+ */
19
+
20
+ import { execSync } from 'child_process';
21
+ import { mkdirSync, writeFileSync, existsSync, rmSync, readdirSync } from 'fs';
22
+ import { dirname, join } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const TEMP_DIR = join(__dirname, '../temp-commits');
27
+ const NUM_PRS = 5;
28
+ const TOTAL_COMMITS = process.argv.includes('--total') ? parseInt(process.argv[process.argv.indexOf('--total') + 1] || '100') : 100;
29
+ const COMMITS_PER_PR = Math.ceil(TOTAL_COMMITS / NUM_PRS);
30
+ const PR_START = 183;
31
+ const COUNTER_OFFSET = 34824;
32
+ const DRY_RUN = process.argv.includes('--dry-run');
33
+ const HELP = process.argv.includes('--help');
34
+
35
+ if (HELP) {
36
+ console.log(`Usage: npx tsx ${join('scripts/git-activity-generator.ts')} [--dry-run] [--help] [--total <num>]\n`);
37
+ console.log('Options:\n --dry-run Simulate without git/gh commands\n --help Show this help\n --total Number of commits to make (default: 500)\n');
38
+ process.exit(0);
39
+ }
40
+
41
+ function run(cmd: string, options: { cwd?: string; dryRun?: boolean } = {}) {
42
+ const { cwd, dryRun } = options;
43
+ if (dryRun || DRY_RUN) {
44
+ console.log(`[DRY-RUN] cd ${cwd || process.cwd()} && ${cmd}`);
45
+ return;
46
+ }
47
+ try {
48
+ execSync(cmd, { cwd, stdio: 'inherit' });
49
+ } catch (e: any) {
50
+ console.error(`Error: ${e.message}`);
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ function log(msg: string) {
56
+ console.log(`\n>>> ${msg}`);
57
+ }
58
+
59
+ async function main() {
60
+ // Get existing file count to continue from where we left off
61
+ let startCommit = 1;
62
+ if (existsSync(TEMP_DIR)) {
63
+ const existingFiles = readdirSync(TEMP_DIR).filter(f => f.startsWith('counter') && f.endsWith('.txt'));
64
+ if (existingFiles.length > 0) {
65
+ const maxCounter = Math.max(...existingFiles.map(f => parseInt(f.replace('counter', '').replace('.txt', ''))));
66
+ startCommit = maxCounter - COUNTER_OFFSET + 1;
67
+ console.log(`\n>>> Continuing from existing commit ${startCommit} (found ${existingFiles.length} existing files, max counter: ${maxCounter})`);
68
+ } else {
69
+ // Cleanup previous if empty
70
+ rmSync(TEMP_DIR, { recursive: true });
71
+ mkdirSync(TEMP_DIR, { recursive: true });
72
+ }
73
+ } else {
74
+ mkdirSync(TEMP_DIR, { recursive: true });
75
+ }
76
+
77
+ // Create new temp files only for commits that don't exist yet
78
+ for (let i = startCommit; i <= TOTAL_COMMITS; i++) {
79
+ const file = join(TEMP_DIR, `counter${COUNTER_OFFSET + i}.txt`);
80
+ if (!existsSync(file)) {
81
+ writeFileSync(file, `Commit counter #${COUNTER_OFFSET + i} - ${Date.now()}\nMinor change for activity.\n`);
82
+ }
83
+ }
84
+
85
+ run('git add temp-commits/');
86
+ try { execSync('git commit -m "chore: add temp-commits dir for activity tracking"', { stdio: 'inherit' }); } catch {}
87
+
88
+ for (let pr = 1; pr <= NUM_PRS; pr++) {
89
+ log(`=== PR ${pr}/${NUM_PRS} (commits ${((pr-1)*COMMITS_PER_PR + 1)}-${pr*COMMITS_PER_PR}) ===`);
90
+
91
+ const branch = `fake-activity-pr-${PR_START + pr - 1}-${COUNTER_OFFSET}`;
92
+
93
+ // Create & switch branch
94
+ run(`git checkout -b ${branch}`, { dryRun: DRY_RUN });
95
+
96
+ // Make commits for this PR
97
+ for (let c = (pr-1)*COMMITS_PER_PR + 1; c <= Math.min(pr*COMMITS_PER_PR, TOTAL_COMMITS); c++) {
98
+ if (c > TOTAL_COMMITS) break;
99
+ const file = join(TEMP_DIR, `counter${COUNTER_OFFSET + c}.txt`);
100
+ const content = `Commit counter #${COUNTER_OFFSET + c} - ${Date.now() + c}\nUpdated at ${new Date().toISOString()}\nMinor change for activity.\n`;
101
+ writeFileSync(file, content);
102
+
103
+ run(`git add temp-commits/counter${COUNTER_OFFSET + c}.txt`, { cwd: process.cwd(), dryRun: DRY_RUN });
104
+ if ((c - 1) % 50 === 0 || c === Math.min(pr*COMMITS_PER_PR, TOTAL_COMMITS)) {
105
+ console.log(` Progress: PR${pr} commit ${c}/${Math.min(pr*COMMITS_PER_PR, TOTAL_COMMITS)} (${Math.min(TOTAL_COMMITS, pr*COMMITS_PER_PR)}/${TOTAL_COMMITS} total)`);
106
+ }
107
+ run(`git commit -m "chore: bump counter ${COUNTER_OFFSET + c} for activity tracking"`, { dryRun: DRY_RUN });
108
+ }
109
+
110
+ // Push & create PR
111
+ run(`git push origin ${branch}`, { dryRun: DRY_RUN });
112
+ run(`gh pr create --title "chore: activity batch #${pr} - ${COMMITS_PER_PR} minor updates" --body "Batch of ${COMMITS_PER_PR} commits for activity tracking. Changes in temp-commits/. #automation" --base main`, { dryRun: DRY_RUN });
113
+
114
+ log(`PR ${pr} created! Merging to main...`);
115
+
116
+ // Switch back to main
117
+ run(`git checkout main`, { dryRun: DRY_RUN });
118
+
119
+ // Merge the PR and delete the remote branch
120
+ run(`gh pr merge ${branch} --merge --delete-branch --subject "chore: merge activity batch #${pr}"`, { dryRun: DRY_RUN });
121
+
122
+ // Delete local branch if it still exists
123
+ try { execSync(`git branch -D ${branch}`, { stdio: 'inherit' }); } catch {}
124
+
125
+ // Pull latest main
126
+ run(`git pull origin main`, { dryRun: DRY_RUN });
127
+
128
+ log(`PR ${pr} merged and deleted!`);
129
+ }
130
+
131
+ // Safety: stash if dirty, checkout main
132
+ try { execSync('git stash push -m "pre-activity auto-save"', { stdio: 'inherit' }); } catch {}
133
+ try { execSync('git checkout main', { stdio: 'inherit' }); } catch {
134
+ execSync('git checkout master', { stdio: 'inherit' });
135
+ }
136
+ try { execSync('git stash pop', { stdio: 'inherit' }); } catch {}
137
+
138
+ // Clean up temp-commits
139
+ if (existsSync(TEMP_DIR)) {
140
+ rmSync(TEMP_DIR, { recursive: true });
141
+ log('Temp files cleaned up');
142
+ }
143
+
144
+ // Update counter file
145
+ writeFileSync('.activity_counter', TOTAL_COMMITS.toString());
146
+ console.log(`Updated .activity_counter to ${TOTAL_COMMITS}`);
147
+
148
+ log('✅ Complete! All PRs merged and deleted.');
149
+ if (!DRY_RUN) {
150
+ console.log(`\nSummary: ${TOTAL_COMMITS} commits across ${NUM_PRS} PRs, all merged to main and PRs deleted.`);
151
+ }
152
+ }
153
+
154
+ main().catch(console.error);
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env tsx
2
+ import { createServer } from "node:http";
3
+ import { spawn } from "node:child_process";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { networkInterfaces } from "node:os";
7
+
8
+ const PORT = parseInt(process.env.PORT || "3456", 10);
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const REPO_DIR = join(__dirname, "..");
11
+
12
+ let childProcess: ReturnType<typeof spawn> | null = null;
13
+
14
+ const html = `<!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
19
+ <title>Strade Auto-Activity</title>
20
+ <style>
21
+ *{box-sizing:border-box;margin:0;padding:0}
22
+ body{font-family:system-ui,-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100dvh;background:#0f172a;color:#e2e8f0}
23
+ .container{text-align:center;padding:2rem;width:100%;max-width:400px}
24
+ h1{font-size:1.5rem;margin-bottom:.5rem;color:#f8fafc}
25
+ p{font-size:.875rem;color:#94a3b8;margin-bottom:2rem}
26
+ .btn{display:block;width:100%;padding:1rem;font-size:1.25rem;font-weight:600;border:none;border-radius:12px;cursor:pointer;margin-bottom:1rem;transition:opacity .2s}
27
+ .btn:active{opacity:.7}
28
+ .btn:disabled{opacity:.4;cursor:not-allowed}
29
+ .btn-start{background:#22c55e;color:#052e16}
30
+ .btn-stop{background:#ef4444;color:#450a0a}
31
+ #status{margin-top:1rem;padding:.75rem 1rem;border-radius:8px;font-weight:600;font-size:1rem}
32
+ .running{background:#166534;color:#86efac}
33
+ .idle{background:#1e293b;color:#94a3b8}
34
+ .error{background:#7f1d1d;color:#fca5a5}
35
+ .log{background:#1e293b;border-radius:8px;padding:.75rem;margin-top:1rem;max-height:200px;overflow-y:auto;text-align:left;font-family:monospace;font-size:.75rem;line-height:1.4;color:#94a3b8}
36
+ </style>
37
+ </head>
38
+ <body>
39
+ <div class="container">
40
+ <h1>Strade Auto-Activity</h1>
41
+ <p>Start or stop the auto-activity loop</p>
42
+ <button class="btn btn-start" id="startBtn" onclick="run('/start')">▶ Start</button>
43
+ <button class="btn btn-stop" id="stopBtn" onclick="run('/stop')">■ Stop</button>
44
+ <div id="status" class="idle">Idle</div>
45
+ <div class="log" id="log">Ready.</div>
46
+ </div>
47
+ <script>
48
+ function log(msg){const el=document.getElementById('log');el.textContent+=msg;el.scrollTop=el.scrollHeight}
49
+ async function run(path){const btn=event.target;btn.disabled=true;try{const r=await fetch(path,{method:'POST'});log(await r.text()+'. ')}catch(e){log('Error: '+e.message+'. ')}finally{btn.disabled=false;poll()}}
50
+ async function poll(){try{const r=await fetch('/status');const d=await r.json();const el=document.getElementById('status');if(d.running){el.textContent='Running';el.className='running'}else if(d.error){el.textContent='Error';el.className='error';log('['+d.error+'] ')}else{el.textContent='Idle';el.className='idle'}}catch(e){const el=document.getElementById('status');el.textContent='Offline';el.className='error'}}
51
+ setInterval(poll,3000);poll()
52
+ </script>
53
+ </body>
54
+ </html>`;
55
+
56
+ const server = createServer((req, res) => {
57
+ res.setHeader("Access-Control-Allow-Origin", "*");
58
+
59
+ if (req.method === "GET" && req.url === "/") {
60
+ res.writeHead(200, { "Content-Type": "text/html" });
61
+ res.end(html);
62
+ return;
63
+ }
64
+
65
+ if (req.method === "POST" && req.url === "/start") {
66
+ if (childProcess) {
67
+ res.writeHead(200, { "Content-Type": "text/plain" });
68
+ res.end("Already running");
69
+ return;
70
+ }
71
+ const scriptPath = join(REPO_DIR, "scripts", "auto-activity.sh");
72
+ childProcess = spawn("bash", [scriptPath], {
73
+ cwd: REPO_DIR,
74
+ stdio: "inherit",
75
+ });
76
+ childProcess.on("exit", (code) => {
77
+ childProcess = null;
78
+ });
79
+ childProcess.on("error", () => {
80
+ childProcess = null;
81
+ });
82
+ res.writeHead(200, { "Content-Type": "text/plain" });
83
+ res.end("Started");
84
+ return;
85
+ }
86
+
87
+ if (req.method === "POST" && req.url === "/stop") {
88
+ if (childProcess) {
89
+ childProcess.kill("SIGTERM");
90
+ childProcess = null;
91
+ }
92
+ res.writeHead(200, { "Content-Type": "text/plain" });
93
+ res.end("Stopped");
94
+ return;
95
+ }
96
+
97
+ if (req.method === "GET" && req.url === "/status") {
98
+ res.writeHead(200, { "Content-Type": "application/json" });
99
+ res.end(JSON.stringify({ running: childProcess !== null }));
100
+ return;
101
+ }
102
+
103
+ res.writeHead(404);
104
+ res.end("Not found");
105
+ });
106
+
107
+ server.listen(PORT, "0.0.0.0", () => {
108
+ const ifaces = networkInterfaces();
109
+ const ips: string[] = [];
110
+ for (const name of Object.keys(ifaces)) {
111
+ for (const iface of ifaces[name] || []) {
112
+ if (iface.family === "IPv4" && !iface.internal) {
113
+ ips.push(iface.address);
114
+ }
115
+ }
116
+ }
117
+ console.log(`Mobile control server running:`);
118
+ console.log(` Local: http://localhost:${PORT}`);
119
+ for (const ip of ips) {
120
+ console.log(` Network: http://${ip}:${PORT}`);
121
+ }
122
+ console.log(`Open one of the Network URLs on your phone's browser.`);
123
+ });
@@ -0,0 +1,155 @@
1
+ [network]
2
+ name = "devnet"
3
+ deployment_fee_rate = 10
4
+
5
+ [accounts.deployer]
6
+ mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw"
7
+ balance = 100_000_000_000_000
8
+ # secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601
9
+ # stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
10
+ # btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH
11
+
12
+ [accounts.wallet_1]
13
+ mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild"
14
+ balance = 100_000_000_000_000
15
+ # secret_key: 7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801
16
+ # stx_address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5
17
+ # btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC
18
+
19
+ [accounts.wallet_2]
20
+ mnemonic = "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital"
21
+ balance = 100_000_000_000_000
22
+ # secret_key: 530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101
23
+ # stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG
24
+ # btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG
25
+
26
+ [accounts.wallet_3]
27
+ mnemonic = "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high"
28
+ balance = 100_000_000_000_000
29
+ # secret_key: d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901
30
+ # stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC
31
+ # btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7
32
+
33
+ [accounts.wallet_4]
34
+ mnemonic = "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin"
35
+ balance = 100_000_000_000_000
36
+ # secret_key: f9d7206a47f14d2870c163ebab4bf3e70d18f5d14ce1031f3902fbbc894fe4c701
37
+ # stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND
38
+ # btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8
39
+
40
+ [accounts.wallet_5]
41
+ mnemonic = "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase"
42
+ balance = 100_000_000_000_000
43
+ # secret_key: 3eccc5dac8056590432db6a35d52b9896876a3d5cbdea53b72400bc9c2099fe801
44
+ # stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB
45
+ # btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx
46
+
47
+ [accounts.wallet_6]
48
+ mnemonic = "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy"
49
+ balance = 100_000_000_000_000
50
+ # secret_key: 7036b29cb5e235e5fd9b09ae3e8eec4404e44906814d5d01cbca968a60ed4bfb01
51
+ # stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0
52
+ # btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt
53
+
54
+ [accounts.wallet_7]
55
+ mnemonic = "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow"
56
+ balance = 100_000_000_000_000
57
+ # secret_key: b463f0df6c05d2f156393eee73f8016c5372caa0e9e29a901bb7171d90dc4f1401
58
+ # stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ
59
+ # btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7
60
+
61
+ [accounts.wallet_8]
62
+ mnemonic = "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune"
63
+ balance = 100_000_000_000_000
64
+ # secret_key: 6a1a754ba863d7bab14adbbc3f8ebb090af9e871ace621d3e5ab634e1422885e01
65
+ # stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP
66
+ # btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw
67
+
68
+ [accounts.faucet]
69
+ mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform"
70
+ balance = 100_000_000_000_000
71
+ # secret_key: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801
72
+ # stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6
73
+ # btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d
74
+
75
+ [devnet]
76
+ disable_stacks_explorer = false
77
+ disable_stacks_api = false
78
+ # disable_subnet_api = false
79
+ # disable_bitcoin_explorer = true
80
+ # working_dir = "tmp/devnet"
81
+ # stacks_node_events_observers = ["host.docker.internal:8002"]
82
+ # miner_mnemonic = "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce"
83
+ # miner_derivation_path = "m/44'/5757'/0'/0/0"
84
+ # faucet_mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform"
85
+ # faucet_derivation_path = "m/44'/5757'/0'/0/0"
86
+ # orchestrator_port = 20445
87
+ # bitcoin_node_p2p_port = 18444
88
+ # bitcoin_node_rpc_port = 18443
89
+ # bitcoin_node_username = "devnet"
90
+ # bitcoin_node_password = "devnet"
91
+ # bitcoin_controller_block_time = 30_000
92
+ # stacks_node_rpc_port = 20443
93
+ # stacks_node_p2p_port = 20444
94
+ # stacks_api_port = 3999
95
+ # stacks_api_events_port = 3700
96
+ # bitcoin_explorer_port = 8001
97
+ # stacks_explorer_port = 8000
98
+ # postgres_port = 5432
99
+ # postgres_username = "postgres"
100
+ # postgres_password = "postgres"
101
+ # postgres_database = "postgres"
102
+ # bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:26.0"
103
+ # stacks_node_image_url = "quay.io/hirosystems/stacks-node:devnet-2.5"
104
+ # stacks_signer_image_url = "quay.io/hirosystems/stacks-signer:devnet-2.5"
105
+ # stacks_api_image_url = "hirosystems/stacks-blockchain-api:master"
106
+ # stacks_explorer_image_url = "hirosystems/explorer:latest"
107
+ # bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet"
108
+ # postgres_image_url = "postgres:alpine"
109
+ # enable_subnet_node = true
110
+ # subnet_node_image_url = "hirosystems/stacks-subnets:0.8.1"
111
+ # subnet_leader_mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw"
112
+ # subnet_leader_derivation_path = "m/44'/5757'/0'/0/0"
113
+ # subnet_contract_id = "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1"
114
+ # subnet_node_rpc_port = 30443
115
+ # subnet_node_p2p_port = 30444
116
+ # subnet_events_ingestion_port = 30445
117
+ # subnet_node_events_observers = ["host.docker.internal:8002"]
118
+ # subnet_api_image_url = "hirosystems/stacks-blockchain-api:master"
119
+ # subnet_api_postgres_database = "subnet_api"
120
+
121
+ # epoch_2_0 = 100
122
+ # epoch_2_05 = 100
123
+ # epoch_2_1 = 101
124
+ # epoch_2_2 = 102
125
+ # epoch_2_3 = 103
126
+ # epoch_2_4 = 104
127
+ # epoch_2_5 = 108
128
+ # epoch_3_0 = 144
129
+
130
+
131
+ # Send some stacking orders
132
+ [[devnet.pox_stacking_orders]]
133
+ start_at_cycle = 1
134
+ duration = 10
135
+ auto_extend = true
136
+ wallet = "wallet_1"
137
+ slots = 2
138
+ btc_address = "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC"
139
+
140
+ [[devnet.pox_stacking_orders]]
141
+ start_at_cycle = 1
142
+ duration = 10
143
+ auto_extend = true
144
+ wallet = "wallet_2"
145
+ slots = 2
146
+ btc_address = "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG"
147
+
148
+ [[devnet.pox_stacking_orders]]
149
+ start_at_cycle = 1
150
+ duration = 10
151
+ auto_extend = true
152
+ wallet = "wallet_3"
153
+ slots = 2
154
+ btc_address = "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7"
155
+
@@ -0,0 +1,7 @@
1
+ [network]
2
+ name = "mainnet"
3
+ stacks_node_rpc_address = "https://api.hiro.so"
4
+ deployment_fee_rate = 10
5
+
6
+ [accounts.deployer]
7
+ mnemonic = "glare eternal august long hurt cricket catalog garlic unknown vehicle forum cruel"
@@ -0,0 +1,9 @@
1
+ [network]
2
+ name = "testnet"
3
+ stacks_node_rpc_address = "https://api.testnet.hiro.so"
4
+ deployment_fee_rate = 10
5
+
6
+ [accounts.deployer]
7
+ mnemonic = "desert replace point glare unit shift heart kid sample motor stock lizard public spirit crime attract quarter motor volume good mimic smart item prevent"
8
+ # After adding your mnemonic, the address will be derived automatically
9
+ # You can get testnet STX from: https://explorer.hiro.so/sandbox/faucet?chain=testnet