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.
- package/.activity_counter +1 -0
- package/.gitattributes +3 -0
- package/.vscode/settings.json +4 -0
- package/.vscode/tasks.json +19 -0
- package/CHANGELOG.md +1 -0
- package/Clarinet.toml +56 -0
- package/Clarinet.toml.backup +174 -0
- package/Clarinet.toml.old +146 -0
- package/DEPLOYMENT_RESULTS.md +160 -0
- package/README.md +344 -0
- package/TODO.md +34 -0
- package/contracts/CoreMarketPlace.clar +227 -0
- package/contracts/DisputeResolution_clar.clar +265 -0
- package/contracts/EscrowService.clar +171 -0
- package/contracts/UserProfile.clar +280 -0
- package/contracts/ft-trait.clar +24 -0
- package/contracts/token.clar +178 -0
- package/costs-reports.json +76026 -0
- package/deployments/default.mainnet-plan.yaml +67 -0
- package/deployments/default.simnet-plan.yaml +105 -0
- package/deployments/default.testnet-plan.yaml +67 -0
- package/deployments/new-contracts.testnet-plan.yaml +32 -0
- package/frontend/README.md +10 -0
- package/frontend/components.json +22 -0
- package/frontend/dist/assets/index-BacuuL66.css +1 -0
- package/frontend/dist/assets/index-jryypd5B.js +194 -0
- package/frontend/dist/favicon.png +0 -0
- package/frontend/dist/index.html +15 -0
- package/frontend/dist/manifest.json +15 -0
- package/frontend/dist/vite.svg +1 -0
- package/frontend/empty-mock.js +1 -0
- package/frontend/eslint.config.js +23 -0
- package/frontend/eslint.config.mjs +25 -0
- package/frontend/index.html +14 -0
- package/frontend/next.config.ts +17 -0
- package/frontend/package-lock.json +14740 -0
- package/frontend/package.json +56 -0
- package/frontend/postcss.config.js +5 -0
- package/frontend/postcss.config.mjs +5 -0
- package/frontend/public/favicon.png +0 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/manifest.json +15 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/vite.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/src/App.css +42 -0
- package/frontend/src/App.tsx +177 -0
- package/frontend/src/app/about/page.tsx +208 -0
- package/frontend/src/app/favicon.ico +0 -0
- package/frontend/src/app/globals.css +129 -0
- package/frontend/src/app/help/page.tsx +167 -0
- package/frontend/src/app/how-it-works/page.tsx +274 -0
- package/frontend/src/app/layout.tsx +55 -0
- package/frontend/src/app/marketplace/page.tsx +324 -0
- package/frontend/src/app/my-listings/page.tsx +318 -0
- package/frontend/src/app/page.tsx +15 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/components/ConfirmDialog.tsx +54 -0
- package/frontend/src/components/CreateListingForm.tsx +231 -0
- package/frontend/src/components/ErrorBoundary.tsx +73 -0
- package/frontend/src/components/FilterPanel.tsx +10 -0
- package/frontend/src/components/Footer.tsx +100 -0
- package/frontend/src/components/Header.tsx +268 -0
- package/frontend/src/components/ImageUpload.tsx +147 -0
- package/frontend/src/components/LandingPage.tsx +322 -0
- package/frontend/src/components/ListingCard.tsx +154 -0
- package/frontend/src/components/LoadingSkeleton.tsx +44 -0
- package/frontend/src/components/MobileNav.tsx +89 -0
- package/frontend/src/components/NotificationBell.tsx +8 -0
- package/frontend/src/components/NotificationPanel.tsx +14 -0
- package/frontend/src/components/README.md +14 -0
- package/frontend/src/components/SearchBar.tsx +10 -0
- package/frontend/src/components/TestnetBanner.tsx +29 -0
- package/frontend/src/components/ThemeToggle.tsx +32 -0
- package/frontend/src/components/__tests__/Header.test.tsx +70 -0
- package/frontend/src/components/__tests__/ListingCard.test.tsx +86 -0
- package/frontend/src/components/providers/ThemeProvider.tsx +9 -0
- package/frontend/src/components/ui/alert-dialog.tsx +141 -0
- package/frontend/src/components/ui/avatar.tsx +53 -0
- package/frontend/src/components/ui/badge.tsx +46 -0
- package/frontend/src/components/ui/button.tsx +60 -0
- package/frontend/src/components/ui/card.tsx +92 -0
- package/frontend/src/components/ui/dialog.tsx +143 -0
- package/frontend/src/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/src/components/ui/input.tsx +21 -0
- package/frontend/src/components/ui/label.tsx +24 -0
- package/frontend/src/components/ui/select.tsx +187 -0
- package/frontend/src/components/ui/sonner.tsx +40 -0
- package/frontend/src/components/ui/textarea.tsx +18 -0
- package/frontend/src/context/README.md +27 -0
- package/frontend/src/index.css +166 -0
- package/frontend/src/lib/notificationEvents.ts +10 -0
- package/frontend/src/lib/notificationStore.ts +13 -0
- package/frontend/src/lib/notifications.ts +13 -0
- package/frontend/src/lib/search.ts +28 -0
- package/frontend/src/lib/stacks.ts +189 -0
- package/frontend/src/lib/utils.ts +6 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/test/setup.ts +23 -0
- package/frontend/src/types.d.ts +9 -0
- package/frontend/tsconfig.app.json +28 -0
- package/frontend/tsconfig.json +41 -0
- package/frontend/tsconfig.node.json +26 -0
- package/frontend/vercel.json +4 -0
- package/frontend/vite.config.ts +6 -0
- package/frontend/vitest.config.ts +17 -0
- package/lcov.info +31338 -0
- package/mainnetcontracts.md +16 -0
- package/package.json +53 -0
- package/scripts/auto-activity.sh +9 -0
- package/scripts/cancel-pending.ts +67 -0
- package/scripts/check-balances.ts +23 -0
- package/scripts/distribute-evenly.ts +56 -0
- package/scripts/drain-accounts.ts +70 -0
- package/scripts/fund-accounts.ts +88 -0
- package/scripts/fund-active.ts +59 -0
- package/scripts/fund-unfunded.ts +88 -0
- package/scripts/generate-activity.ts +181 -0
- package/scripts/git-activity-generator.ts +154 -0
- package/scripts/mobile-server.ts +123 -0
- package/settings/Devnet.toml +155 -0
- package/settings/Mainnet.toml +7 -0
- package/settings/Testnet.toml +9 -0
- package/tests/CoreMarketPlace.fuzz.test.ts +435 -0
- package/tests/CoreMarketPlace.test.ts +564 -0
- package/tsconfig.json +26 -0
- package/vitest.config.js +49 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Strade Mainnet Contracts
|
|
2
|
+
|
|
3
|
+
**Deployer Address**: `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N`
|
|
4
|
+
|
|
5
|
+
| Contract | Address |
|
|
6
|
+
|----------|---------|
|
|
7
|
+
| CoreMarketPlace | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.CoreMarketPlace` |
|
|
8
|
+
| EscrowService | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.EscrowService` |
|
|
9
|
+
| DisputeResolution | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.DisputeResolution_clar` |
|
|
10
|
+
| UserProfile | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.UserProfile` |
|
|
11
|
+
| BST Token | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.token` |
|
|
12
|
+
| FT Trait | `SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N.ft-trait` |
|
|
13
|
+
|
|
14
|
+
**Explorer**: https://explorer.hiro.so/address/SPB669EVRTKWYGY5GNQ7VEBZ7RF8A3K01EP6GN8N
|
|
15
|
+
|
|
16
|
+
**Deployed**: 2026-03-21
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "strade-stx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stacks blockchain project for Strade - smart contracts and tooling",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"test:report": "vitest run -- --coverage --costs",
|
|
9
|
+
"test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\""
|
|
10
|
+
},
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@hirosystems/clarinet-sdk": "^2.3.2",
|
|
15
|
+
"@prisma/client": "^7.8.0",
|
|
16
|
+
"@stacks/transactions": "^6.12.0",
|
|
17
|
+
"@stacks/wallet-sdk": "^7.2.0",
|
|
18
|
+
"axios": "^1.18.1",
|
|
19
|
+
"chalk": "^5.6.2",
|
|
20
|
+
"cheerio": "^1.2.0",
|
|
21
|
+
"chokidar-cli": "^3.0.0",
|
|
22
|
+
"cli-table3": "^0.6.5",
|
|
23
|
+
"commander": "^15.0.0",
|
|
24
|
+
"date-fns": "^4.4.0",
|
|
25
|
+
"express": "^5.2.1",
|
|
26
|
+
"fast-check": "^3.15.1",
|
|
27
|
+
"figlet": "^1.11.0",
|
|
28
|
+
"graphql": "^17.0.1",
|
|
29
|
+
"inquirer": "^14.0.2",
|
|
30
|
+
"ioredis": "^5.11.1",
|
|
31
|
+
"lodash": "^4.18.1",
|
|
32
|
+
"luxon": "^3.7.2",
|
|
33
|
+
"moment": "^2.30.1",
|
|
34
|
+
"mongoose": "^9.7.2",
|
|
35
|
+
"nanoid": "^5.1.16",
|
|
36
|
+
"next": "^16.2.9",
|
|
37
|
+
"ora": "^9.4.1",
|
|
38
|
+
"pino": "^10.3.1",
|
|
39
|
+
"playwright": "^1.61.1",
|
|
40
|
+
"prisma": "^7.8.0",
|
|
41
|
+
"react": "^19.2.7",
|
|
42
|
+
"react-dom": "^19.2.7",
|
|
43
|
+
"redis": "^6.0.1",
|
|
44
|
+
"sharp": "^0.35.2",
|
|
45
|
+
"socket.io": "^4.8.3",
|
|
46
|
+
"typescript": "^5.3.3",
|
|
47
|
+
"vite": "^5.1.4",
|
|
48
|
+
"vitest": "^1.3.1",
|
|
49
|
+
"vitest-environment-clarinet": "^2.0.0",
|
|
50
|
+
"yargs": "^18.0.0",
|
|
51
|
+
"zod": "^4.4.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
3
|
+
echo "Auto-activity started. Running every 3 minutes. Press Ctrl+C to stop."
|
|
4
|
+
while true; do
|
|
5
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Running generate-activity..."
|
|
6
|
+
cd "$REPO_DIR" && npx tsx scripts/generate-activity.ts
|
|
7
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done. Sleeping 3 minutes..."
|
|
8
|
+
sleep 180
|
|
9
|
+
done
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cancels all pending mempool txs by replacing them with 0-value self-transfers at higher fee
|
|
3
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
4
|
+
import { getAddressFromPrivateKey, makeSTXTokenTransfer, AnchorMode } from '@stacks/transactions';
|
|
5
|
+
import { StacksMainnet } from '@stacks/network';
|
|
6
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const ACCOUNTS_TO_CANCEL = [24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49];
|
|
11
|
+
const CANCEL_FEE = 2000n; // higher than original to replace
|
|
12
|
+
|
|
13
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
14
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
15
|
+
|
|
16
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
17
|
+
for (let i = 1; i < 50; i++) wallet = generateNewAccount(wallet);
|
|
18
|
+
|
|
19
|
+
const network = new StacksMainnet();
|
|
20
|
+
|
|
21
|
+
for (const i of ACCOUNTS_TO_CANCEL) {
|
|
22
|
+
const pk = wallet.accounts[i].stxPrivateKey;
|
|
23
|
+
const address = getAddressFromPrivateKey(pk, network.version);
|
|
24
|
+
|
|
25
|
+
// Get pending txs from mempool
|
|
26
|
+
const mempool = JSON.parse(execSync(`curl -s 'https://api.hiro.so/extended/v1/address/${address}/mempool?limit=50'`).toString());
|
|
27
|
+
const pendingTxs = mempool.results ?? [];
|
|
28
|
+
|
|
29
|
+
if (pendingTxs.length === 0) {
|
|
30
|
+
console.log(`Account ${i}: ${address} — no pending txs`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get unique nonces of pending txs
|
|
35
|
+
const nonces: Set<number> = new Set(pendingTxs.map((tx: any) => tx.nonce));
|
|
36
|
+
console.log(`Account ${i}: ${address} — cancelling nonces: ${[...nonces].join(', ')}`);
|
|
37
|
+
|
|
38
|
+
for (const nonce of nonces) {
|
|
39
|
+
const bal = BigInt(JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${address}?proof=0'`).toString()).balance);
|
|
40
|
+
if (bal < CANCEL_FEE) { console.log(` nonce ${nonce}: insufficient balance, skipping`); continue; }
|
|
41
|
+
|
|
42
|
+
const tx = await makeSTXTokenTransfer({
|
|
43
|
+
recipient: address, // send to self
|
|
44
|
+
amount: 1n,
|
|
45
|
+
senderKey: pk,
|
|
46
|
+
network,
|
|
47
|
+
anchorMode: AnchorMode.Any,
|
|
48
|
+
nonce,
|
|
49
|
+
fee: CANCEL_FEE,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const tmp = `/tmp/cancel_${i}_${nonce}.bin`;
|
|
53
|
+
writeFileSync(tmp, tx.serialize());
|
|
54
|
+
const result = execSync(`curl -s --max-time 30 -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmp}`, { timeout: 35000 }).toString();
|
|
55
|
+
unlinkSync(tmp);
|
|
56
|
+
|
|
57
|
+
const parsed = JSON.parse(result);
|
|
58
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
59
|
+
console.log(` ✅ nonce ${nonce} replaced — txid: ${parsed}`);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(` ❌ nonce ${nonce} — ${result}`);
|
|
62
|
+
}
|
|
63
|
+
await new Promise(r => setTimeout(r, 500));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('\nDone! Run drain-accounts.ts after these confirm.');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
2
|
+
import { getAddressFromPrivateKey } from '@stacks/transactions';
|
|
3
|
+
import { StacksMainnet } from '@stacks/network';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
9
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
10
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
11
|
+
for (let i = 1; i < 50; i++) wallet = generateNewAccount(wallet);
|
|
12
|
+
const network = new StacksMainnet();
|
|
13
|
+
|
|
14
|
+
let total = 0;
|
|
15
|
+
for (let i = 0; i < 50; i++) {
|
|
16
|
+
const address = getAddressFromPrivateKey(wallet.accounts[i].stxPrivateKey, network.version);
|
|
17
|
+
const d = JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${address}?proof=0'`).toString());
|
|
18
|
+
const bal = parseInt(d.balance, 16) / 1e6;
|
|
19
|
+
total += bal;
|
|
20
|
+
console.log(`Account ${i.toString().padStart(2)}: ${address} — ${bal.toFixed(6)} STX`);
|
|
21
|
+
await new Promise(r => setTimeout(r, 300));
|
|
22
|
+
}
|
|
23
|
+
console.log(`\nTotal: ${total.toFixed(6)} STX`);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Distributes 15 STX evenly among all 50 accounts: 0.3 STX each. Account 0 retains its 0.3 STX share, sends 0.3 STX to accounts 1-49.
|
|
3
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
4
|
+
import { getAddressFromPrivateKey, makeSTXTokenTransfer, AnchorMode } from '@stacks/transactions';
|
|
5
|
+
import { StacksMainnet } from '@stacks/network';
|
|
6
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const curlFetch = async (url: string, opts: any = {}) => {
|
|
11
|
+
const method = opts.method || 'GET';
|
|
12
|
+
const headers = opts.headers ? Object.entries(opts.headers).map(([k,v]) => `-H '${k}: ${v}'`).join(' ') : '';
|
|
13
|
+
if (opts.body) {
|
|
14
|
+
const tmp = `/tmp/cf_${Date.now()}.bin`;
|
|
15
|
+
writeFileSync(tmp, opts.body);
|
|
16
|
+
const r = execSync(`curl -s --max-time 30 -X ${method} ${headers} --data-binary @${tmp} '${url}'`, { timeout: 35000 }).toString();
|
|
17
|
+
unlinkSync(tmp);
|
|
18
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
19
|
+
}
|
|
20
|
+
const r = execSync(`curl -s --max-time 15 '${url}'`, { timeout: 20000 }).toString();
|
|
21
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SEND_AMOUNT = 300_000n; // 0.3 STX each × 49 = 14.7 STX sent, account 0 retains 0.3 STX = 15 STX total
|
|
25
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
26
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
27
|
+
|
|
28
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
29
|
+
for (let i = 1; i < 50; i++) wallet = generateNewAccount(wallet);
|
|
30
|
+
|
|
31
|
+
const network = new StacksMainnet({ fetchFn: curlFetch as any });
|
|
32
|
+
const senderKey = wallet.accounts[0].stxPrivateKey;
|
|
33
|
+
const senderAddress = getAddressFromPrivateKey(senderKey, network.version);
|
|
34
|
+
const nonceData = JSON.parse(execSync(`curl -s 'https://api.hiro.so/extended/v1/address/${senderAddress}/nonces'`).toString());
|
|
35
|
+
let nonce = nonceData.possible_next_nonce;
|
|
36
|
+
|
|
37
|
+
console.log(`\nSender: ${senderAddress} | Nonce: ${nonce}`);
|
|
38
|
+
console.log(`Distributing ${Number(SEND_AMOUNT * 49n) / 1e6} STX total — account 0 retains ${Number(SEND_AMOUNT) / 1e6} STX, accounts 1-49 each receive ${Number(SEND_AMOUNT) / 1e6} STX\n`);
|
|
39
|
+
|
|
40
|
+
for (let i = 1; i < 50; i++) {
|
|
41
|
+
const address = getAddressFromPrivateKey(wallet.accounts[i].stxPrivateKey, network.version);
|
|
42
|
+
const tx = await makeSTXTokenTransfer({ recipient: address, amount: SEND_AMOUNT, senderKey, network, anchorMode: AnchorMode.Any, nonce });
|
|
43
|
+
const tmp = `/tmp/dist_${i}.bin`;
|
|
44
|
+
writeFileSync(tmp, tx.serialize());
|
|
45
|
+
const result = execSync(`curl -s --max-time 30 -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmp}`, { timeout: 35000 }).toString();
|
|
46
|
+
unlinkSync(tmp);
|
|
47
|
+
const parsed = JSON.parse(result);
|
|
48
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
49
|
+
console.log(`✅ Account ${i}: ${address} — sent ${Number(SEND_AMOUNT) / 1e6} STX`);
|
|
50
|
+
} else {
|
|
51
|
+
console.error(`❌ Account ${i}: ${address} — ${result}`);
|
|
52
|
+
}
|
|
53
|
+
nonce++;
|
|
54
|
+
await new Promise(r => setTimeout(r, 500));
|
|
55
|
+
}
|
|
56
|
+
console.log(`\nDone! ${Number(SEND_AMOUNT * 49n) / 1e6} STX distributed across 49 accounts. Account 0 retains ${Number(SEND_AMOUNT) / 1e6} STX. Total: 15 STX evenly split among all 50 accounts.`);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Drains accounts 10-49 back to account 0
|
|
3
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
4
|
+
import { getAddressFromPrivateKey, makeSTXTokenTransfer, AnchorMode } from '@stacks/transactions';
|
|
5
|
+
import { StacksMainnet } from '@stacks/network';
|
|
6
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const NUM_ACCOUNTS = 41; // only drain accounts 35 and 40
|
|
11
|
+
const KEEP_ACCOUNTS = 35;
|
|
12
|
+
const FEE = 300n;
|
|
13
|
+
|
|
14
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
15
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
16
|
+
|
|
17
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
18
|
+
for (let i = 1; i < NUM_ACCOUNTS; i++) wallet = generateNewAccount(wallet);
|
|
19
|
+
|
|
20
|
+
const network = new StacksMainnet();
|
|
21
|
+
const recipient = getAddressFromPrivateKey(wallet.accounts[0].stxPrivateKey, network.version);
|
|
22
|
+
console.log(`Draining accounts ${KEEP_ACCOUNTS}-${NUM_ACCOUNTS - 1} → ${recipient}\n`);
|
|
23
|
+
|
|
24
|
+
for (let i = KEEP_ACCOUNTS; i < NUM_ACCOUNTS; i++) {
|
|
25
|
+
await new Promise(r => setTimeout(r, 4000)); // delay before each account to avoid rate limits
|
|
26
|
+
const pk = wallet.accounts[i].stxPrivateKey;
|
|
27
|
+
const address = getAddressFromPrivateKey(pk, network.version);
|
|
28
|
+
let d: any;
|
|
29
|
+
try {
|
|
30
|
+
d = JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${address}?proof=0'`).toString());
|
|
31
|
+
} catch { console.log(`Account ${i}: rate limited, skipping`); continue; }
|
|
32
|
+
const balance = BigInt(d.balance ?? '0x0');
|
|
33
|
+
const sendAmount = balance - FEE;
|
|
34
|
+
|
|
35
|
+
if (sendAmount <= 0n) {
|
|
36
|
+
console.log(`Account ${i}: ${address} — skipped (balance: ${Number(balance)/1e6} STX)`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const nonceRaw = execSync(`curl -s 'https://api.hiro.so/extended/v1/address/${address}/nonces'`).toString();
|
|
41
|
+
if (nonceRaw.includes('rate limit')) { console.log(`Account ${i}: rate limited on nonce, skipping`); continue; }
|
|
42
|
+
const nonceData = JSON.parse(nonceRaw);
|
|
43
|
+
const nonce = nonceData.possible_next_nonce;
|
|
44
|
+
|
|
45
|
+
const tx = await makeSTXTokenTransfer({
|
|
46
|
+
recipient,
|
|
47
|
+
amount: sendAmount,
|
|
48
|
+
senderKey: pk,
|
|
49
|
+
network,
|
|
50
|
+
anchorMode: AnchorMode.Any,
|
|
51
|
+
nonce,
|
|
52
|
+
fee: FEE,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const tmp = `/tmp/drain_${i}.bin`;
|
|
56
|
+
writeFileSync(tmp, tx.serialize());
|
|
57
|
+
const result = execSync(`curl -s --max-time 30 -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmp}`, { timeout: 35000 }).toString();
|
|
58
|
+
unlinkSync(tmp);
|
|
59
|
+
|
|
60
|
+
if (result.includes('rate limit')) { console.log(`Account ${i}: rate limited on broadcast, skipping`); continue; }
|
|
61
|
+
const parsed = JSON.parse(result);
|
|
62
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
63
|
+
console.log(`✅ Account ${i}: ${address} — drained ${Number(sendAmount)/1e6} STX — txid: ${parsed}`);
|
|
64
|
+
} else {
|
|
65
|
+
console.error(`❌ Account ${i}: ${address} — ${result}`);
|
|
66
|
+
}
|
|
67
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log('\nDone!');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Funds derived accounts 1-49 from account 0.
|
|
4
|
+
* Run once before generate-activity.ts
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
makeSTXTokenTransfer,
|
|
8
|
+
AnchorMode,
|
|
9
|
+
getAddressFromPrivateKey,
|
|
10
|
+
} from '@stacks/transactions';
|
|
11
|
+
import { StacksMainnet } from '@stacks/network';
|
|
12
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
13
|
+
import { readFileSync } from 'fs';
|
|
14
|
+
import { resolve } from 'path';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
|
|
17
|
+
// Use curl-based fetch to bypass Node.js network restrictions
|
|
18
|
+
const curlFetch = async (url: string, opts: any = {}) => {
|
|
19
|
+
const method = opts.method || 'GET';
|
|
20
|
+
const body = opts.body ? `-d '${opts.body}'` : '';
|
|
21
|
+
const headers = opts.headers ? Object.entries(opts.headers).map(([k,v]) => `-H '${k}: ${v}'`).join(' ') : '';
|
|
22
|
+
const result = execSync(`curl -s -X ${method} ${headers} ${body} '${url}'`, { timeout: 15000 }).toString();
|
|
23
|
+
return { ok: true, json: async () => JSON.parse(result), text: async () => result };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const NUM_ACCOUNTS = 50;
|
|
27
|
+
const FUND_AMOUNT = 100_000n; // 0.1 STX in microSTX
|
|
28
|
+
const FEE = 300n;
|
|
29
|
+
|
|
30
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
31
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
32
|
+
|
|
33
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
34
|
+
for (let i = 1; i < NUM_ACCOUNTS; i++) wallet = generateNewAccount(wallet);
|
|
35
|
+
|
|
36
|
+
const network = new StacksMainnet({ fetchFn: curlFetch as any });
|
|
37
|
+
const senderKey = wallet.accounts[0].stxPrivateKey;
|
|
38
|
+
const senderAddress = getAddressFromPrivateKey(senderKey, network.version);
|
|
39
|
+
|
|
40
|
+
const res = await curlFetch(`https://api.hiro.so/v2/accounts/${senderAddress}?proof=0`);
|
|
41
|
+
const data = await res.json() as { nonce: number; balance: string };
|
|
42
|
+
let nonce = data.nonce;
|
|
43
|
+
const balance = BigInt(data.balance);
|
|
44
|
+
|
|
45
|
+
console.log(`Sender: ${senderAddress}`);
|
|
46
|
+
console.log(`Balance: ${Number(balance) / 1e6} STX`);
|
|
47
|
+
console.log(`Funding ${NUM_ACCOUNTS - 1} accounts with ${Number(FUND_AMOUNT) / 1e6} STX each\n`);
|
|
48
|
+
|
|
49
|
+
const totalNeeded = (FUND_AMOUNT + FEE) * BigInt(NUM_ACCOUNTS - 1) + FEE;
|
|
50
|
+
if (balance < totalNeeded) {
|
|
51
|
+
console.error(`Insufficient balance. Need ${Number(totalNeeded) / 1e6} STX, have ${Number(balance) / 1e6} STX`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
import { writeFileSync, unlinkSync } from 'fs';
|
|
56
|
+
|
|
57
|
+
for (let i = 1; i < NUM_ACCOUNTS; i++) {
|
|
58
|
+
const recipient = getAddressFromPrivateKey(wallet.accounts[i].stxPrivateKey, network.version);
|
|
59
|
+
const tx = await makeSTXTokenTransfer({
|
|
60
|
+
recipient,
|
|
61
|
+
amount: FUND_AMOUNT,
|
|
62
|
+
senderKey,
|
|
63
|
+
network,
|
|
64
|
+
anchorMode: AnchorMode.Any,
|
|
65
|
+
nonce,
|
|
66
|
+
fee: FEE,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Write serialized tx to temp file and POST via curl
|
|
70
|
+
const tmpFile = `/tmp/stx_tx_${i}.bin`;
|
|
71
|
+
writeFileSync(tmpFile, tx.serialize());
|
|
72
|
+
const result = execSync(
|
|
73
|
+
`curl -s -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmpFile}`,
|
|
74
|
+
{ timeout: 15000 }
|
|
75
|
+
).toString();
|
|
76
|
+
unlinkSync(tmpFile);
|
|
77
|
+
|
|
78
|
+
const parsed = JSON.parse(result);
|
|
79
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
80
|
+
console.log(`✅ Funded account ${i}: ${recipient} — txid: ${parsed}`);
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`❌ Failed account ${i}: ${recipient} — ${result}`);
|
|
83
|
+
}
|
|
84
|
+
nonce++;
|
|
85
|
+
await new Promise(r => setTimeout(r, 500));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('\nDone! Wait ~10 min for txs to confirm before running generate-activity.ts');
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Funds only accounts 1-9 (the 10 active accounts) if balance < 0.05 STX
|
|
3
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
4
|
+
import { getAddressFromPrivateKey, makeSTXTokenTransfer, AnchorMode } from '@stacks/transactions';
|
|
5
|
+
import { StacksMainnet } from '@stacks/network';
|
|
6
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const curlFetch = async (url: string, opts: any = {}) => {
|
|
11
|
+
const method = opts.method || 'GET';
|
|
12
|
+
const headers = opts.headers ? Object.entries(opts.headers).map(([k,v]) => `-H '${k}: ${v}'`).join(' ') : '';
|
|
13
|
+
if (opts.body) {
|
|
14
|
+
const tmp = `/tmp/cf_${Date.now()}.bin`;
|
|
15
|
+
writeFileSync(tmp, opts.body);
|
|
16
|
+
const r = execSync(`curl -s --max-time 30 -X ${method} ${headers} --data-binary @${tmp} '${url}'`, { timeout: 35000 }).toString();
|
|
17
|
+
unlinkSync(tmp);
|
|
18
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
19
|
+
}
|
|
20
|
+
const r = execSync(`curl -s --max-time 15 '${url}'`, { timeout: 20000 }).toString();
|
|
21
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const FUND_AMOUNT = 300_000n; // 0.3 STX — evenly distributes ~15 STX across 50 accounts
|
|
25
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
26
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
27
|
+
|
|
28
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
29
|
+
for (let i = 1; i < 50; i++) wallet = generateNewAccount(wallet);
|
|
30
|
+
|
|
31
|
+
const network = new StacksMainnet({ fetchFn: curlFetch as any });
|
|
32
|
+
const senderKey = wallet.accounts[0].stxPrivateKey;
|
|
33
|
+
const senderAddress = getAddressFromPrivateKey(senderKey, network.version);
|
|
34
|
+
const nonceData = JSON.parse(execSync(`curl -s 'https://api.hiro.so/extended/v1/address/${senderAddress}/nonces'`).toString());
|
|
35
|
+
let nonce = nonceData.possible_next_nonce;
|
|
36
|
+
|
|
37
|
+
console.log(`Sender: ${senderAddress} | Nonce: ${nonce}\n`);
|
|
38
|
+
|
|
39
|
+
for (let i = 1; i < 50; i++) {
|
|
40
|
+
const address = getAddressFromPrivateKey(wallet.accounts[i].stxPrivateKey, network.version);
|
|
41
|
+
const d = JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${address}?proof=0'`).toString());
|
|
42
|
+
const bal = parseInt(d.balance, 16);
|
|
43
|
+
if (bal >= 50_000) { console.log(`Account ${i}: ${address} — ${(bal/1e6).toFixed(3)} STX OK`); continue; }
|
|
44
|
+
|
|
45
|
+
const tx = await makeSTXTokenTransfer({ recipient: address, amount: FUND_AMOUNT, senderKey, network, anchorMode: AnchorMode.Any, nonce });
|
|
46
|
+
const tmp = `/tmp/fund10_${i}.bin`;
|
|
47
|
+
writeFileSync(tmp, tx.serialize());
|
|
48
|
+
const result = execSync(`curl -s --max-time 30 -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmp}`, { timeout: 35000 }).toString();
|
|
49
|
+
unlinkSync(tmp);
|
|
50
|
+
const parsed = JSON.parse(result);
|
|
51
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
52
|
+
console.log(`✅ Account ${i}: ${address} — funded 0.2 STX`);
|
|
53
|
+
} else {
|
|
54
|
+
console.error(`❌ Account ${i}: ${address} — ${result}`);
|
|
55
|
+
}
|
|
56
|
+
nonce++;
|
|
57
|
+
await new Promise(r => setTimeout(r, 500));
|
|
58
|
+
}
|
|
59
|
+
console.log('\nDone!');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateWallet, generateNewAccount } from '@stacks/wallet-sdk';
|
|
3
|
+
import { getAddressFromPrivateKey, makeSTXTokenTransfer, AnchorMode } from '@stacks/transactions';
|
|
4
|
+
import { StacksMainnet } from '@stacks/network';
|
|
5
|
+
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
const curlFetch = async (url: string, opts: any = {}) => {
|
|
10
|
+
const method = opts.method || 'GET';
|
|
11
|
+
const headers = opts.headers ? Object.entries(opts.headers).map(([k,v]) => `-H '${k}: ${v}'`).join(' ') : '';
|
|
12
|
+
if (opts.body) {
|
|
13
|
+
const tmp = `/tmp/stx_fund_${Date.now()}.bin`;
|
|
14
|
+
writeFileSync(tmp, opts.body);
|
|
15
|
+
const r = execSync(`curl -s -X ${method} ${headers} --data-binary @${tmp} '${url}'`, { timeout: 15000 }).toString();
|
|
16
|
+
unlinkSync(tmp);
|
|
17
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
18
|
+
}
|
|
19
|
+
const r = execSync(`curl -s '${url}'`, { timeout: 15000 }).toString();
|
|
20
|
+
return { ok: true, json: async () => JSON.parse(r), text: async () => r };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const FUND_AMOUNT = 200_000n; // 0.2 STX
|
|
24
|
+
const NUM_ACCOUNTS = 50;
|
|
25
|
+
|
|
26
|
+
const toml = readFileSync(resolve('settings/Mainnet.toml'), 'utf8');
|
|
27
|
+
const mnemonic = toml.match(/mnemonic\s*=\s*"([^"]+)"/)![1];
|
|
28
|
+
|
|
29
|
+
let wallet = await generateWallet({ secretKey: mnemonic, password: '' });
|
|
30
|
+
for (let i = 1; i < NUM_ACCOUNTS; i++) wallet = generateNewAccount(wallet);
|
|
31
|
+
|
|
32
|
+
const network = new StacksMainnet({ fetchFn: curlFetch as any });
|
|
33
|
+
const senderKey = wallet.accounts[0].stxPrivateKey;
|
|
34
|
+
const senderAddress = getAddressFromPrivateKey(senderKey, network.version);
|
|
35
|
+
|
|
36
|
+
// Get sender nonce (possible_next to include mempool)
|
|
37
|
+
const nonceData = JSON.parse(execSync(`curl -s 'https://api.hiro.so/extended/v1/address/${senderAddress}/nonces'`).toString());
|
|
38
|
+
let nonce = nonceData.possible_next_nonce;
|
|
39
|
+
|
|
40
|
+
const balData = JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${senderAddress}?proof=0'`).toString());
|
|
41
|
+
const balance = BigInt(balData.balance);
|
|
42
|
+
console.log(`Sender: ${senderAddress} | Balance: ${Number(balance)/1e6} STX | Nonce: ${nonce}\n`);
|
|
43
|
+
|
|
44
|
+
// Find accounts with low balance (< 0.05 STX)
|
|
45
|
+
const unfunded: { idx: number; address: string }[] = [];
|
|
46
|
+
for (let i = 1; i < NUM_ACCOUNTS; i++) {
|
|
47
|
+
const address = getAddressFromPrivateKey(wallet.accounts[i].stxPrivateKey, network.version);
|
|
48
|
+
const d = JSON.parse(execSync(`curl -s 'https://api.hiro.so/v2/accounts/${address}?proof=0'`).toString());
|
|
49
|
+
const bal = parseInt(d.balance, 16);
|
|
50
|
+
console.log(`Account ${i}: ${address} — ${(bal/1e6).toFixed(3)} STX`);
|
|
51
|
+
if (bal < 50_000) unfunded.push({ idx: i, address }); // top up if < 0.05 STX
|
|
52
|
+
await new Promise(r => setTimeout(r, 600));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`\nFound ${unfunded.length} accounts with low balance. Topping up each with 0.1 STX...\n`);
|
|
56
|
+
|
|
57
|
+
const totalNeeded = FUND_AMOUNT * BigInt(unfunded.length);
|
|
58
|
+
if (balance < totalNeeded) {
|
|
59
|
+
console.error(`Insufficient balance. Need ${Number(totalNeeded)/1e6} STX, have ${Number(balance)/1e6} STX`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const { idx, address } of unfunded) {
|
|
64
|
+
const tx = await makeSTXTokenTransfer({
|
|
65
|
+
recipient: address,
|
|
66
|
+
amount: FUND_AMOUNT,
|
|
67
|
+
senderKey,
|
|
68
|
+
network,
|
|
69
|
+
anchorMode: AnchorMode.Any,
|
|
70
|
+
nonce,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const tmp = `/tmp/stx_fund_${idx}.bin`;
|
|
74
|
+
writeFileSync(tmp, tx.serialize());
|
|
75
|
+
const result = execSync(`curl -s --max-time 30 -X POST 'https://api.hiro.so/v2/transactions' -H 'Content-Type: application/octet-stream' --data-binary @${tmp}`, { timeout: 35000 }).toString();
|
|
76
|
+
unlinkSync(tmp);
|
|
77
|
+
|
|
78
|
+
const parsed = JSON.parse(result);
|
|
79
|
+
if (typeof parsed === 'string' && parsed.length === 64) {
|
|
80
|
+
console.log(`✅ Account ${idx}: ${address} — txid: ${parsed}`);
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`❌ Account ${idx}: ${address} — ${result}`);
|
|
83
|
+
}
|
|
84
|
+
nonce++;
|
|
85
|
+
await new Promise(r => setTimeout(r, 300));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('\nDone!');
|