mobilestacks 0.1.4 → 0.1.6

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.
@@ -17,7 +17,7 @@ const path_1 = __importDefault(require("path"));
17
17
  // Auto-load all tasks in src/tasks
18
18
  const tasksDir = path_1.default.join(__dirname, '../tasks');
19
19
  fs_1.default.readdirSync(tasksDir)
20
- .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
20
+ .filter(f => (f.endsWith('.ts') || f.endsWith('.js')) && !f.endsWith('.d.ts') && !f.includes('.test.'))
21
21
  .forEach(f => {
22
22
  require(path_1.default.join(tasksDir, f));
23
23
  });
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,OAAO,kBAoD5B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,OAAO,kBAoF5B"}
@@ -28,34 +28,56 @@ async function runInit() {
28
28
  message: 'Stacks testnet node URL:',
29
29
  default: 'https://stacks-node-api.testnet.stacks.co',
30
30
  },
31
- {
32
- type: 'input',
33
- name: 'privateKey',
34
- message: 'Your wallet private key (leave blank to use seed phrase):',
35
- },
36
- {
37
- type: 'input',
38
- name: 'seedPhrase',
39
- message: 'Your wallet seed phrase (leave blank if using private key):',
40
- },
41
31
  {
42
32
  type: 'input',
43
33
  name: 'derivationPath',
44
- message: 'Derivation path (default: m/44\'/5757\'/0\'/0/0):',
34
+ message: "Derivation path (default: m/44'/5757'/0'/0/0):",
45
35
  default: "m/44'/5757'/0'/0/0",
46
36
  },
47
37
  ]);
48
- const config = `export default {\n networks: {\n mainnet: { url: '${answers.mainnetUrl}', name: 'mainnet' },\n testnet: { url: '${answers.testnetUrl}', name: 'testnet' }\n },\n defaultNetwork: 'testnet',\n wallet: {\n ${answers.privateKey ? `privateKey: '${answers.privateKey}',` : ''}\n ${answers.seedPhrase ? `seedPhrase: '${answers.seedPhrase}',` : ''}\n derivationPath: "${answers.derivationPath}"\n }\n};\n`;
38
+ // ── Config file: references env vars, never embeds secrets ──
39
+ const config = `import 'dotenv/config';
40
+
41
+ export default {
42
+ networks: {
43
+ mainnet: { url: process.env.STACKS_MAINNET_URL || '${answers.mainnetUrl}', name: 'mainnet' },
44
+ testnet: { url: process.env.STACKS_TESTNET_URL || '${answers.testnetUrl}', name: 'testnet' },
45
+ },
46
+ defaultNetwork: 'testnet',
47
+ wallet: {
48
+ // Secrets are read from environment variables — never hard-code them here.
49
+ privateKey: process.env.MOBILESTACKS_PRIVATE_KEY || '',
50
+ seedPhrase: process.env.MOBILESTACKS_SEED_PHRASE || '',
51
+ derivationPath: '${answers.derivationPath}',
52
+ },
53
+ };
54
+ `;
49
55
  fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'mobilestacks.config.ts'), config);
50
- // Scaffold example contract
56
+ // ── .env file (if it doesn't exist yet) ──
57
+ const envPath = path_1.default.join(process.cwd(), '.env');
58
+ if (!fs_1.default.existsSync(envPath)) {
59
+ const envContent = `# Mobilestacks secrets — NEVER commit this file!\nMOBILESTACKS_PRIVATE_KEY=\nMOBILESTACKS_SEED_PHRASE=\nSTACKS_MAINNET_URL=${answers.mainnetUrl}\nSTACKS_TESTNET_URL=${answers.testnetUrl}\n`;
60
+ fs_1.default.writeFileSync(envPath, envContent, { mode: 0o600 }); // owner-only permissions
61
+ }
62
+ // ── Scaffold example contract ──
51
63
  const contractsDir = path_1.default.join(process.cwd(), 'contracts');
52
64
  if (!fs_1.default.existsSync(contractsDir))
53
65
  fs_1.default.mkdirSync(contractsDir);
54
66
  fs_1.default.writeFileSync(path_1.default.join(contractsDir, 'sample-contract.clar'), '(define-public (hello-world)\n (ok "Hello, Stacks!"))\n');
55
- // Scaffold example user task
67
+ // ── Scaffold example user task ──
56
68
  const tasksDir = path_1.default.join(process.cwd(), 'src', 'tasks');
57
69
  if (!fs_1.default.existsSync(tasksDir))
58
70
  fs_1.default.mkdirSync(tasksDir, { recursive: true });
59
- fs_1.default.writeFileSync(path_1.default.join(tasksDir, 'example-task.ts'), "import { task } from '../core/dsl';\n\ntask('example', 'An example user task for onboarding')\n .addParam('name', 'Your name', { type: 'string', required: false, defaultValue: 'World' })\n .setAction(async (args) => {\n return `Hello, ${args.name}! Welcome to mobilestacks.`;\n });\n");
60
- console.log('Created mobilestacks.config.ts, contracts/sample-contract.clar, and src/tasks/example-task.ts!');
71
+ fs_1.default.writeFileSync(path_1.default.join(tasksDir, 'example-task.ts'), "import { task } from 'mobilestacks';\n\ntask('example', 'An example user task for onboarding')\n .addParam('name', 'Your name', { type: 'string', required: false, defaultValue: 'World' })\n .setAction(async (args: Record<string, string>) => {\n return `Hello, ${args.name}! Welcome to mobilestacks.`;\n });\n");
72
+ // ── Security notice ──
73
+ console.log('\n✅ Created:');
74
+ console.log(' • mobilestacks.config.ts (reads secrets from env vars)');
75
+ console.log(' • .env (store your secrets here)');
76
+ console.log(' • contracts/sample-contract.clar');
77
+ console.log(' • src/tasks/example-task.ts');
78
+ console.log('\n⚠️ SECURITY WARNING:');
79
+ console.log(' Your wallet secrets belong in the .env file, NOT in source code.');
80
+ console.log(' • .env is already listed in .gitignore — never remove that entry.');
81
+ console.log(' • Set MOBILESTACKS_PRIVATE_KEY or MOBILESTACKS_SEED_PHRASE in .env');
82
+ console.log(' • See .env.example for the full list of supported variables.\n');
61
83
  }
@@ -1,3 +1,4 @@
1
+ import 'dotenv/config';
1
2
  import { MobilestacksConfig } from '../types/config';
2
3
  import { StacksNetwork } from '@stacks/network';
3
4
  export declare class RuntimeEnvironment {
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-environment.d.ts","sourceRoot":"","sources":["../../../src/core/runtime-environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAiB,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAiB,MAAM,iBAAiB,CAAC;AAG/D,qBAAa,kBAAkB;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;gBAEX,MAAM,EAAE,kBAAkB;CA4DvC"}
1
+ {"version":3,"file":"runtime-environment.d.ts","sourceRoot":"","sources":["../../../src/core/runtime-environment.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,kBAAkB,EAAiB,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAiB,MAAM,iBAAiB,CAAC;AAG/D,qBAAa,kBAAkB;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;gBAEX,MAAM,EAAE,kBAAkB;CAkEvC"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeEnvironment = void 0;
4
+ require("dotenv/config");
4
5
  const transactions_1 = require("@stacks/transactions");
5
6
  const wallet_sdk_1 = require("@stacks/wallet-sdk");
6
7
  const network_1 = require("@stacks/network");
@@ -27,25 +28,26 @@ class RuntimeEnvironment {
27
28
  client: { baseUrl: networkConfig.url }
28
29
  });
29
30
  }
30
- // Wallet: support privateKey, seedPhrase+derivationPath, or both (prefer privateKey)
31
- if (config.wallet.privateKey) {
32
- // Derive address from privateKey
33
- const address = (0, transactions_1.getAddressFromPrivateKey)(config.wallet.privateKey, networkName === 'mainnet' ? 'mainnet' : 'testnet');
34
- this.wallet = {
35
- privateKey: config.wallet.privateKey,
36
- address,
37
- };
31
+ // ── Resolve wallet secrets (env vars take priority over config) ──
32
+ const privateKey = process.env.MOBILESTACKS_PRIVATE_KEY ||
33
+ process.env.STACKS_PRIVATE_KEY ||
34
+ config.wallet.privateKey ||
35
+ '';
36
+ const seedPhrase = process.env.MOBILESTACKS_SEED_PHRASE ||
37
+ process.env.STACKS_SEED_PHRASE ||
38
+ config.wallet.seedPhrase ||
39
+ '';
40
+ if (privateKey) {
41
+ const address = (0, transactions_1.getAddressFromPrivateKey)(privateKey, networkName === 'mainnet' ? 'mainnet' : 'testnet');
42
+ this.wallet = { privateKey, address };
38
43
  }
39
- else if (config.wallet.seedPhrase) {
40
- const path = config.wallet.derivationPath || "m/44'/5757'/0'/0/0"; // Stacks main path
41
- // generateWallet requires a password param
42
- (0, wallet_sdk_1.generateWallet)({ secretKey: config.wallet.seedPhrase, password: '' }).then(wallet => {
43
- const index = parseInt(path.split('/').pop() || '0', 10);
44
+ else if (seedPhrase) {
45
+ const derivPath = config.wallet.derivationPath || "m/44'/5757'/0'/0/0";
46
+ (0, wallet_sdk_1.generateWallet)({ secretKey: seedPhrase, password: '' }).then(wallet => {
47
+ const index = parseInt(derivPath.split('/').pop() || '0', 10);
44
48
  const account = wallet.accounts[index] || wallet.accounts[0];
45
- // Use getStxAddress to get the address
46
49
  // eslint-disable-next-line @typescript-eslint/no-var-requires
47
50
  const { getStxAddress } = require('@stacks/wallet-sdk/dist/models/account');
48
- // network.addressVersion has singleSig/multiSig lines.
49
51
  const address = getStxAddress(account, this.network.transactionVersion);
50
52
  this.wallet = {
51
53
  privateKey: account.stxPrivateKey,
@@ -57,7 +59,8 @@ class RuntimeEnvironment {
57
59
  });
58
60
  }
59
61
  else {
60
- throw new Error('No privateKey or seedPhrase provided in wallet config');
62
+ // No secrets anywhere — warn but don't crash (devnet may not need a wallet)
63
+ console.warn('⚠️ No wallet secret found. Set MOBILESTACKS_PRIVATE_KEY or MOBILESTACKS_SEED_PHRASE in your .env file.');
61
64
  }
62
65
  this.stacks = {};
63
66
  // Apply extensions
@@ -15,7 +15,7 @@ export declare const NetworkConfigSchema: z.ZodObject<{
15
15
  explorerUrl?: string | undefined;
16
16
  faucetUrl?: string | null | undefined;
17
17
  }>;
18
- export declare const WalletConfigSchema: z.ZodEffects<z.ZodObject<{
18
+ export declare const WalletConfigSchema: z.ZodObject<{
19
19
  privateKey: z.ZodOptional<z.ZodString>;
20
20
  seedPhrase: z.ZodOptional<z.ZodString>;
21
21
  derivationPath: z.ZodOptional<z.ZodString>;
@@ -30,16 +30,6 @@ export declare const WalletConfigSchema: z.ZodEffects<z.ZodObject<{
30
30
  seedPhrase?: string | undefined;
31
31
  derivationPath?: string | undefined;
32
32
  address?: string | undefined;
33
- }>, {
34
- privateKey?: string | undefined;
35
- seedPhrase?: string | undefined;
36
- derivationPath?: string | undefined;
37
- address?: string | undefined;
38
- }, {
39
- privateKey?: string | undefined;
40
- seedPhrase?: string | undefined;
41
- derivationPath?: string | undefined;
42
- address?: string | undefined;
43
33
  }>;
44
34
  export declare const MobilestacksConfigSchema: z.ZodObject<{
45
35
  networks: z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -59,7 +49,7 @@ export declare const MobilestacksConfigSchema: z.ZodObject<{
59
49
  faucetUrl?: string | null | undefined;
60
50
  }>>;
61
51
  defaultNetwork: z.ZodString;
62
- wallet: z.ZodEffects<z.ZodObject<{
52
+ wallet: z.ZodObject<{
63
53
  privateKey: z.ZodOptional<z.ZodString>;
64
54
  seedPhrase: z.ZodOptional<z.ZodString>;
65
55
  derivationPath: z.ZodOptional<z.ZodString>;
@@ -74,16 +64,6 @@ export declare const MobilestacksConfigSchema: z.ZodObject<{
74
64
  seedPhrase?: string | undefined;
75
65
  derivationPath?: string | undefined;
76
66
  address?: string | undefined;
77
- }>, {
78
- privateKey?: string | undefined;
79
- seedPhrase?: string | undefined;
80
- derivationPath?: string | undefined;
81
- address?: string | undefined;
82
- }, {
83
- privateKey?: string | undefined;
84
- seedPhrase?: string | undefined;
85
- derivationPath?: string | undefined;
86
- address?: string | undefined;
87
67
  }>;
88
68
  }, "strip", z.ZodTypeAny, {
89
69
  networks: Record<string, {
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAIH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;EAQ9B,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAInC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAKH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAK7B,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAInC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -8,13 +8,14 @@ exports.NetworkConfigSchema = zod_1.z.object({
8
8
  explorerUrl: zod_1.z.string().url().optional(),
9
9
  faucetUrl: zod_1.z.string().url().nullable().optional(),
10
10
  });
11
- // Wallet config: allow privateKey, seedPhrase, or both. If both, privateKey is used.
11
+ // Wallet config: secrets are resolved at runtime from env vars.
12
+ // Config values are optional fallbacks.
12
13
  exports.WalletConfigSchema = zod_1.z.object({
13
- privateKey: zod_1.z.string().min(1, "Private key is required").optional(),
14
+ privateKey: zod_1.z.string().optional(),
14
15
  seedPhrase: zod_1.z.string().optional(),
15
16
  derivationPath: zod_1.z.string().optional(),
16
17
  address: zod_1.z.string().optional(),
17
- }).refine((data) => data.privateKey || data.seedPhrase, { message: "Either privateKey or seedPhrase is required" });
18
+ });
18
19
  exports.MobilestacksConfigSchema = zod_1.z.object({
19
20
  networks: zod_1.z.record(exports.NetworkConfigSchema),
20
21
  defaultNetwork: zod_1.z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobilestacks",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Professional Task Runner & CLI for the Stacks Blockchain",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -9,9 +9,6 @@
9
9
  },
10
10
  "files": [
11
11
  "dist/",
12
- "src/",
13
- "contracts/",
14
- "Clarinet.toml",
15
12
  "README.md",
16
13
  "LICENSE"
17
14
  ],
@@ -37,7 +34,7 @@
37
34
  "license": "MIT",
38
35
  "repository": {
39
36
  "type": "git",
40
- "url": "https://github.com/Wizbisy/mobilestacks.git"
37
+ "url": "git+https://github.com/Wizbisy/mobilestacks.git"
41
38
  },
42
39
  "homepage": "https://github.com/Wizbisy/mobilestacks#readme",
43
40
  "bugs": {
@@ -56,6 +53,7 @@
56
53
  "eslint-config-prettier": "^10.1.8",
57
54
  "eslint-import-resolver-typescript": "^4.4.4",
58
55
  "eslint-plugin-import": "^2.32.0",
56
+ "ts-node": "^10.9.1",
59
57
  "vitest": "^4.0.18"
60
58
  },
61
59
  "dependencies": {
@@ -66,9 +64,7 @@
66
64
  "commander": "^11.0.0",
67
65
  "dotenv": "^17.3.1",
68
66
  "inquirer": "^8.2.7",
69
- "mobilestacks": "^0.1.3",
70
67
  "node-fetch": "^2.7.0",
71
- "ts-node": "^10.9.1",
72
68
  "zod": "^3.25.76"
73
69
  }
74
70
  }
package/Clarinet.toml DELETED
@@ -1,7 +0,0 @@
1
- [project]
2
- name = "mobilestacks"
3
- requirements = []
4
-
5
- [contracts.sample-contract]
6
- path = "contracts/sample-contract.clar"
7
- clarity_version = 1
@@ -1,2 +0,0 @@
1
- (define-public (hello-world)
2
- (ok "Hello, Stacks!"))
package/src/cli/index.ts DELETED
@@ -1,105 +0,0 @@
1
- #!/usr/bin/env node
2
- import chalk from 'chalk';
3
- import { Command } from 'commander';
4
- import { loadConfig } from '../config/config-loading';
5
- import { RuntimeEnvironment } from '../core/runtime-environment';
6
- import { TaskDefinitions } from '../core/tasks-definitions';
7
- import { runInit } from './init';
8
- import inquirer from 'inquirer';
9
-
10
- // Import all user tasks
11
- import fs from 'fs';
12
- import path from 'path';
13
- // Auto-load all tasks in src/tasks
14
- const tasksDir = path.join(__dirname, '../tasks');
15
- fs.readdirSync(tasksDir)
16
- .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
17
- .forEach(f => {
18
- require(path.join(tasksDir, f));
19
- });
20
-
21
- const program = new Command();
22
- program
23
- .name('mobilestacks')
24
- .description('Professional Task Runner for Stacks')
25
- .version('0.1.0');
26
-
27
-
28
- // Init command for project scaffolding
29
- program
30
- .command('init')
31
- .description('Scaffold a new mobilestacks project and config')
32
- .action(async () => {
33
- await runInit();
34
- process.exit(0);
35
- });
36
-
37
- // List all tasks if no command is given
38
- program.action(() => {
39
- console.log(chalk.bold.blue('\nMobilestacks - Professional Task Runner for Stacks\n'));
40
- console.log(chalk.white('USAGE: ') + chalk.green('mobilestacks <task> [options]\n'));
41
- console.log(chalk.bold('Available tasks:'));
42
- TaskDefinitions.getInstance().getAllTasks().forEach(task => {
43
- const params = task.params.map(p => chalk.yellow(`--${p.name}`)).join(' ');
44
- console.log(' ' + chalk.cyan(task.name) + ' ' + params);
45
- console.log(' ' + chalk.gray(task.description));
46
- });
47
- console.log('\n' + chalk.white('Use ') + chalk.green('mobilestacks <task> --help') + chalk.white(' for more info on a task.'));
48
- console.log(chalk.white('\nExample:'));
49
- console.log(' ' + chalk.green('mobilestacks deploy-contract --contractName my-contract --file ./contracts/my-contract.clar --network testnet'));
50
- console.log(chalk.white('\nDocs: ') + chalk.underline('https://github.com/your-org/mobilestacks#readme'));
51
- program.help({ error: false });
52
- });
53
-
54
- // Dynamically add all registered tasks
55
-
56
- TaskDefinitions.getInstance().getAllTasks().forEach(task => {
57
- const cmd = program.command(task.name)
58
- .description(task.description);
59
- task.params.forEach(param => {
60
- const optStr = `--${param.name} <value>`;
61
- if (param.required !== false) {
62
- cmd.option(optStr, param.description);
63
- } else {
64
- cmd.option(optStr, param.description, param.defaultValue as string | boolean | undefined);
65
- }
66
- });
67
- cmd.action(async (opts) => {
68
- try {
69
- // Prompt for missing required params
70
- const missing = task.params.filter(p => p.required !== false && !opts[p.name]);
71
- if (missing.length > 0) {
72
- const answers = await inquirer.prompt(missing.map(p => ({
73
- type: p.type === 'boolean' ? 'confirm' : 'input',
74
- name: p.name,
75
- message: p.description,
76
- default: p.defaultValue
77
- })));
78
- Object.assign(opts, answers);
79
- }
80
- // Type conversion
81
- task.params.forEach(p => {
82
- if (opts[p.name] && p.type === 'number') opts[p.name] = Number(opts[p.name]);
83
- if (opts[p.name] && p.type === 'boolean') opts[p.name] = Boolean(opts[p.name]);
84
- });
85
- const config = loadConfig();
86
- const env = new RuntimeEnvironment(config);
87
- const result = await task.action(opts, env);
88
- if (typeof result === 'object') {
89
- console.log(chalk.greenBright('Success!'));
90
- console.dir(result, { depth: null, colors: true });
91
- } else {
92
- console.log(chalk.greenBright(result));
93
- }
94
- } catch (err) {
95
- const error = err as Error;
96
- console.error(chalk.redBright('Task failed:'), error.message || error);
97
- if (error && typeof error === 'object' && 'stack' in error && error.stack) {
98
- console.error(chalk.gray(error.stack));
99
- }
100
- process.exit(1);
101
- }
102
- });
103
- });
104
-
105
- program.parse(process.argv);
package/src/cli/init.ts DELETED
@@ -1,57 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import inquirer from 'inquirer';
4
-
5
- export async function runInit() {
6
- console.log('Welcome to mobilestacks project initialization!');
7
- const answers = await inquirer.prompt([
8
- {
9
- type: 'input',
10
- name: 'projectName',
11
- message: 'Project name:',
12
- default: path.basename(process.cwd()),
13
- },
14
- {
15
- type: 'input',
16
- name: 'mainnetUrl',
17
- message: 'Stacks mainnet node URL:',
18
- default: 'https://stacks-node-api.mainnet.stacks.co',
19
- },
20
- {
21
- type: 'input',
22
- name: 'testnetUrl',
23
- message: 'Stacks testnet node URL:',
24
- default: 'https://stacks-node-api.testnet.stacks.co',
25
- },
26
- {
27
- type: 'input',
28
- name: 'privateKey',
29
- message: 'Your wallet private key (leave blank to use seed phrase):',
30
- },
31
- {
32
- type: 'input',
33
- name: 'seedPhrase',
34
- message: 'Your wallet seed phrase (leave blank if using private key):',
35
- },
36
- {
37
- type: 'input',
38
- name: 'derivationPath',
39
- message: 'Derivation path (default: m/44\'/5757\'/0\'/0/0):',
40
- default: "m/44'/5757'/0'/0/0",
41
- },
42
- ]);
43
-
44
- const config = `export default {\n networks: {\n mainnet: { url: '${answers.mainnetUrl}', name: 'mainnet' },\n testnet: { url: '${answers.testnetUrl}', name: 'testnet' }\n },\n defaultNetwork: 'testnet',\n wallet: {\n ${answers.privateKey ? `privateKey: '${answers.privateKey}',` : ''}\n ${answers.seedPhrase ? `seedPhrase: '${answers.seedPhrase}',` : ''}\n derivationPath: "${answers.derivationPath}"\n }\n};\n`;
45
-
46
- fs.writeFileSync(path.join(process.cwd(), 'mobilestacks.config.ts'), config);
47
- // Scaffold example contract
48
- const contractsDir = path.join(process.cwd(), 'contracts');
49
- if (!fs.existsSync(contractsDir)) fs.mkdirSync(contractsDir);
50
- fs.writeFileSync(path.join(contractsDir, 'sample-contract.clar'), '(define-public (hello-world)\n (ok "Hello, Stacks!"))\n');
51
- // Scaffold example user task
52
- const tasksDir = path.join(process.cwd(), 'src', 'tasks');
53
- if (!fs.existsSync(tasksDir)) fs.mkdirSync(tasksDir, { recursive: true });
54
- fs.writeFileSync(path.join(tasksDir, 'example-task.ts'),
55
- "import { task } from '../core/dsl';\n\ntask('example', 'An example user task for onboarding')\n .addParam('name', 'Your name', { type: 'string', required: false, defaultValue: 'World' })\n .setAction(async (args) => {\n return `Hello, ${args.name}! Welcome to mobilestacks.`;\n });\n");
56
- console.log('Created mobilestacks.config.ts, contracts/sample-contract.clar, and src/tasks/example-task.ts!');
57
- }
@@ -1,31 +0,0 @@
1
- import { MobilestacksConfigSchema, MobilestacksConfig } from '../types/config';
2
- import path from 'path';
3
- import fs from 'fs';
4
- import { env } from '../core/env';
5
-
6
- export function loadConfig(configPath?: string, cliOverrides: Record<string, unknown> = {}): MobilestacksConfig {
7
- const configFile = configPath || path.resolve(process.cwd(), 'mobilestacks.config.ts');
8
- if (!fs.existsSync(configFile)) {
9
- throw new Error(`Config file not found: ${configFile}`);
10
- }
11
-
12
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
13
- require('ts-node').register();
14
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
15
- const userConfig = require(configFile);
16
- let config = userConfig.default || userConfig;
17
-
18
- // .env override
19
- if (env.privateKey) config.wallet.privateKey = env.privateKey;
20
- if (env.mainnetUrl) config.networks.mainnet.url = env.mainnetUrl;
21
- if (env.testnetUrl) config.networks.testnet.url = env.testnetUrl;
22
-
23
- // CLI overrides
24
- config = { ...config, ...cliOverrides };
25
-
26
- const parsed = MobilestacksConfigSchema.safeParse(config);
27
- if (!parsed.success) {
28
- throw new Error('Invalid mobilestacks.config.ts: ' + JSON.stringify(parsed.error.format(), null, 2));
29
- }
30
- return parsed.data;
31
- }
@@ -1,63 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { task } from './dsl';
3
- import { TaskDefinitions } from './tasks-definitions';
4
- import { z } from 'zod';
5
- import { RuntimeEnvironment } from './runtime-environment';
6
- import { extendEnvironment, Extender } from './extender';
7
-
8
- describe('DSL', () => {
9
- beforeEach(() => {
10
- // Clear tasks before each test
11
- (TaskDefinitions.getInstance() as unknown as { _tasks: unknown[] })._tasks = [];
12
- });
13
-
14
- it('should register a task with generic parameters', () => {
15
- task('test-task', 'A test task')
16
- .addParam('p1', 'param 1')
17
- .setAction(async () => { return 'done'; });
18
-
19
- const def = TaskDefinitions.getInstance().getTask('test-task');
20
- expect(def).toBeDefined();
21
- expect(def?.name).toBe('test-task');
22
- expect(def?.params.length).toBe(1);
23
- });
24
-
25
- it('should validate Zod schema', async () => {
26
- task('zod-task', 'Task with validation')
27
- .addParam('age', 'Age param', { schema: z.number().min(18) })
28
- .setAction(async (args) => {
29
- return args.age;
30
- });
31
-
32
- const def = TaskDefinitions.getInstance().getTask('zod-task');
33
- expect(def).toBeDefined();
34
-
35
- // Mock environment
36
- const env = {} as RuntimeEnvironment;
37
-
38
- // Valid call
39
- const result = await def?.action({ age: 20 }, env);
40
- expect(result).toBe(20);
41
-
42
- // Invalid call
43
- await expect(def?.action({ age: 10 }, env)).rejects.toThrow('Invalid value for parameter \'age\'');
44
- });
45
-
46
- it('should support environment extensions', () => {
47
-
48
-
49
- // Clear extensions
50
- (Extender.getInstance() as unknown as { _extensions: unknown[] })._extensions = [];
51
-
52
- extendEnvironment((env: RuntimeEnvironment) => {
53
- env['foo'] = 'bar';
54
- });
55
-
56
- const extensions = Extender.getInstance().getExtensions();
57
- expect(extensions.length).toBe(1);
58
-
59
- const mockEnv = {} as RuntimeEnvironment;
60
- extensions[0](mockEnv);
61
- expect(mockEnv['foo']).toBe('bar');
62
- });
63
- });
package/src/core/dsl.ts DELETED
@@ -1,73 +0,0 @@
1
- import { TaskDefinitions, TaskDefinition, TaskParam, TaskParamType } from './tasks-definitions';
2
- import { ZodSchema } from 'zod';
3
- import { RuntimeEnvironment } from './runtime-environment';
4
- import { extendEnvironment } from './extender';
5
-
6
- function task(name: string, description: string) {
7
- const params: TaskParam[] = [];
8
- return {
9
- addParam(
10
- paramName: string,
11
- paramDesc: string,
12
- options?: { type?: TaskParamType; required?: boolean; defaultValue?: unknown; schema?: ZodSchema }
13
- ) {
14
- params.push({
15
- name: paramName,
16
- description: paramDesc,
17
- type: options?.type || 'string',
18
- required: options?.required !== false, // default true
19
- defaultValue: options?.defaultValue,
20
- schema: options?.schema,
21
- });
22
- return this;
23
- },
24
- setAction(action: (args: Record<string, unknown>, env: RuntimeEnvironment) => Promise<unknown>) {
25
- const def: TaskDefinition = {
26
- name,
27
- description,
28
- params,
29
- action: async (args: Record<string, unknown>, env: RuntimeEnvironment) => {
30
- // Validate args against schemas if present
31
- for (const param of params) {
32
- if (param.schema && args[param.name] !== undefined) {
33
- try {
34
- param.schema.parse(args[param.name]);
35
- } catch (error) {
36
- throw new Error(`Invalid value for parameter '${param.name}': ${error}`);
37
- }
38
- }
39
- }
40
- return action(args, env);
41
- },
42
- };
43
- TaskDefinitions.getInstance().addTask(def);
44
- return def;
45
- },
46
- };
47
- }
48
-
49
- function subtask(name: string, description: string, parentTaskName?: string) {
50
- // Register as a subtask with a parent if provided
51
- const sub = task(name, description);
52
- if (parentTaskName) {
53
- // Attach parent info for future use (e.g., dependency graph, CLI grouping)
54
- const def = TaskDefinitions.getInstance().getTask(name);
55
- if (def) (def as TaskDefinition & { parent?: string }).parent = parentTaskName;
56
- }
57
- return sub;
58
- }
59
-
60
- // Workflow system: define and run ordered task/subtask sequences
61
- export type WorkflowStep = { taskName: string; args?: Record<string, unknown> };
62
- export type Workflow = WorkflowStep[];
63
-
64
- export async function runWorkflow(workflow: Workflow, env: RuntimeEnvironment) {
65
- for (const step of workflow) {
66
- const def = TaskDefinitions.getInstance().getTask(step.taskName);
67
- if (!def) throw new Error(`Task not found: ${step.taskName}`);
68
- // eslint-disable-next-line no-await-in-loop
69
- await def.action(step.args || {}, env);
70
- }
71
- }
72
-
73
- export { task, subtask, extendEnvironment };
package/src/core/env.ts DELETED
@@ -1,8 +0,0 @@
1
- import dotenv from 'dotenv';
2
- dotenv.config();
3
-
4
- export const env = {
5
- privateKey: process.env.STACKS_PRIVATE_KEY,
6
- mainnetUrl: process.env.STACKS_MAINNET_URL,
7
- testnetUrl: process.env.STACKS_TESTNET_URL,
8
- };
@@ -1,29 +0,0 @@
1
- import { RuntimeEnvironment } from './runtime-environment';
2
-
3
- export type ExtensionFunc = (env: RuntimeEnvironment) => void;
4
-
5
- export class Extender {
6
- private static _instance: Extender;
7
- private _extensions: ExtensionFunc[] = [];
8
-
9
- private constructor() {}
10
-
11
- public static getInstance(): Extender {
12
- if (!Extender._instance) {
13
- Extender._instance = new Extender();
14
- }
15
- return Extender._instance;
16
- }
17
-
18
- public addExtension(func: ExtensionFunc) {
19
- this._extensions.push(func);
20
- }
21
-
22
- public getExtensions(): ExtensionFunc[] {
23
- return this._extensions;
24
- }
25
- }
26
-
27
- export function extendEnvironment(func: ExtensionFunc) {
28
- Extender.getInstance().addExtension(func);
29
- }
@@ -1,74 +0,0 @@
1
- import { MobilestacksConfig, NetworkConfig } from '../types/config';
2
- import { getAddressFromPrivateKey } from '@stacks/transactions';
3
- import { generateWallet } from '@stacks/wallet-sdk';
4
- import { StacksNetwork, createNetwork } from '@stacks/network';
5
- import { Extender } from './extender';
6
-
7
- export class RuntimeEnvironment {
8
- public config: MobilestacksConfig;
9
- public network: StacksNetwork;
10
- public wallet!: { privateKey: string; address: string; accountIndex?: number };
11
- public stacks: Record<string, unknown>; // Extension point
12
- [key: string]: unknown; // Allow extensions
13
-
14
- constructor(config: MobilestacksConfig) {
15
- this.config = config;
16
- const networkName = config.defaultNetwork;
17
- const networkConfig: NetworkConfig = config.networks[networkName];
18
- if (!networkConfig) {
19
- throw new Error(`Network config not found for: ${networkName}`);
20
- }
21
-
22
- // Instantiate proper StacksNetwork
23
- if (networkName === 'mainnet' || networkName === 'testnet') {
24
- this.network = createNetwork({
25
- network: networkName,
26
- client: { baseUrl: networkConfig.url }
27
- });
28
- } else {
29
- // Default to testnet structure for custom networks
30
- this.network = createNetwork({
31
- network: 'testnet',
32
- client: { baseUrl: networkConfig.url }
33
- });
34
- }
35
-
36
- // Wallet: support privateKey, seedPhrase+derivationPath, or both (prefer privateKey)
37
- if (config.wallet.privateKey) {
38
- // Derive address from privateKey
39
- const address = getAddressFromPrivateKey(config.wallet.privateKey, networkName === 'mainnet' ? 'mainnet' : 'testnet');
40
- this.wallet = {
41
- privateKey: config.wallet.privateKey,
42
- address,
43
- };
44
- } else if (config.wallet.seedPhrase) {
45
- const path = config.wallet.derivationPath || "m/44'/5757'/0'/0/0"; // Stacks main path
46
- // generateWallet requires a password param
47
- generateWallet({ secretKey: config.wallet.seedPhrase, password: '' }).then(wallet => {
48
- const index = parseInt(path.split('/').pop() || '0', 10);
49
- const account = wallet.accounts[index] || wallet.accounts[0];
50
- // Use getStxAddress to get the address
51
- // eslint-disable-next-line @typescript-eslint/no-var-requires
52
- const { getStxAddress } = require('@stacks/wallet-sdk/dist/models/account');
53
- // network.addressVersion has singleSig/multiSig lines.
54
- const address = getStxAddress(account, this.network.transactionVersion);
55
-
56
- this.wallet = {
57
- privateKey: account.stxPrivateKey,
58
- address,
59
- accountIndex: account.index,
60
- };
61
- }).catch(() => {
62
- throw new Error('Failed to generate wallet from seed phrase');
63
- });
64
- } else {
65
- throw new Error('No privateKey or seedPhrase provided in wallet config');
66
- }
67
- this.stacks = {};
68
-
69
- // Apply extensions
70
- for (const extension of Extender.getInstance().getExtensions()) {
71
- extension(this);
72
- }
73
- }
74
- }
@@ -1,56 +0,0 @@
1
- import { initSimnet } from '@hirosystems/clarinet-sdk';
2
- import { ClarityValue } from '@stacks/transactions';
3
-
4
-
5
-
6
- // Import the SDK's Simnet type under an alias
7
- type SimnetInstance = Awaited<ReturnType<typeof initSimnet>>;
8
-
9
- export class Simnet {
10
- private static _instance: Simnet;
11
- public simnet!: SimnetInstance;
12
- public accounts!: Map<string, string>;
13
-
14
- private constructor() {}
15
-
16
- public static async init(): Promise<Simnet> {
17
- if (!Simnet._instance) {
18
- Simnet._instance = new Simnet();
19
- // Initialize SDK - auto-detects Clarinet.toml from CWD
20
- try {
21
- console.log('Initializing Simnet...');
22
- Simnet._instance.simnet = await initSimnet();
23
- // Get accounts
24
- Simnet._instance.accounts = Simnet._instance.simnet.getAccounts();
25
- } catch (error) {
26
- console.error('Failed to initialize Simnet:', error);
27
- throw error;
28
- }
29
- }
30
- return Simnet._instance;
31
- }
32
-
33
- public getDeployer(): string {
34
- const deployer = this.accounts.get('deployer');
35
- if (!deployer) throw new Error('Deployer account not found');
36
- return deployer;
37
- }
38
-
39
- public getAccount(name: string): string {
40
- const acc = this.accounts.get(name);
41
- if (!acc) throw new Error(`Account ${name} not found`);
42
- return acc;
43
- }
44
-
45
- public callPublic(contract: string, func: string, args: ClarityValue[], sender: string) {
46
- return this.simnet.callPublicFn(contract, func, args, sender);
47
- }
48
-
49
- public callReadOnly(contract: string, func: string, args: ClarityValue[], sender: string) {
50
- return this.simnet.callReadOnlyFn(contract, func, args, sender);
51
- }
52
-
53
- public async mineBlock(txs: Parameters<SimnetInstance['mineBlock']>[0]) {
54
- return this.simnet.mineBlock(txs);
55
- }
56
- }
@@ -1,47 +0,0 @@
1
- import { ZodSchema } from 'zod';
2
- import { RuntimeEnvironment } from './runtime-environment';
3
-
4
- export type TaskParamType = 'string' | 'number' | 'boolean';
5
- export interface TaskParam {
6
- name: string;
7
- description: string;
8
- type?: TaskParamType;
9
- required?: boolean;
10
- defaultValue?: unknown;
11
- schema?: ZodSchema;
12
- }
13
-
14
- export interface TaskDefinition {
15
- name: string;
16
- description: string;
17
- params: TaskParam[];
18
- action: (args: Record<string, unknown>, env: RuntimeEnvironment) => Promise<unknown>;
19
- }
20
-
21
- class TaskDefinitions {
22
- private static _instance: TaskDefinitions;
23
- private _tasks: TaskDefinition[] = [];
24
-
25
- private constructor() {}
26
-
27
- public static getInstance(): TaskDefinitions {
28
- if (!TaskDefinitions._instance) {
29
- TaskDefinitions._instance = new TaskDefinitions();
30
- }
31
- return TaskDefinitions._instance;
32
- }
33
-
34
- public addTask(task: TaskDefinition) {
35
- this._tasks.push(task);
36
- }
37
-
38
- public getTask(name: string): TaskDefinition | undefined {
39
- return this._tasks.find((t) => t.name === name);
40
- }
41
-
42
- public getAllTasks(): TaskDefinition[] {
43
- return this._tasks;
44
- }
45
- }
46
-
47
- export { TaskDefinitions };
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export { task, subtask, extendEnvironment } from './core/dsl';
2
- export { Simnet } from './core/simnet';
3
- export { RuntimeEnvironment } from './core/runtime-environment';
4
- export * from './core/tasks-definitions';
@@ -1,31 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
-
4
- task('call-contract-function', 'Call a public function on a deployed Clarity contract')
5
- .addParam('contractAddress', 'Deployed contract address (STX...)', { type: 'string', required: true })
6
- .addParam('contractName', 'Contract name', { type: 'string', required: true })
7
- .addParam('functionName', 'Function name', { type: 'string', required: true })
8
- .addParam('args', 'Comma-separated function arguments', { type: 'string', required: false, defaultValue: '' })
9
- .addParam('network', 'Network (mainnet|testnet)', { type: 'string', required: false, defaultValue: 'testnet' })
10
- .setAction(async (args) => {
11
- const contractAddress = args.contractAddress as string;
12
- const contractName = args.contractName as string;
13
- const functionName = args.functionName as string;
14
- const fnArgs = args.args as string;
15
- const network = args.network as string;
16
- const apiUrl = network === 'mainnet'
17
- ? 'https://stacks-node-api.mainnet.stacks.co'
18
- : 'https://stacks-node-api.testnet.stacks.co';
19
- const url = `${apiUrl}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`;
20
- const body = fnArgs
21
- ? { arguments: fnArgs.split(',').map((a: string) => a.trim()) }
22
- : { arguments: [] };
23
- const res = await fetch(url, {
24
- method: 'POST',
25
- headers: { 'Content-Type': 'application/json' },
26
- body: JSON.stringify(body)
27
- });
28
- if (!res.ok) throw new Error(`Failed to call contract function: ${res.statusText}`);
29
- const result = await res.json();
30
- return result;
31
- });
@@ -1,49 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import {
3
- makeContractDeploy,
4
- broadcastTransaction,
5
- } from '@stacks/transactions';
6
- import { createNetwork } from '@stacks/network';
7
- import fs from 'fs';
8
-
9
- function maskAddress(address: string) {
10
- return address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
11
- }
12
-
13
- function containsSecret(obj: unknown): boolean {
14
- const str = JSON.stringify(obj);
15
- return /[A-Za-z0-9]{32,}/.test(str); // crude check for long secrets
16
- }
17
-
18
- // Deploy a Clarity contract to Stacks mainnet or testnet
19
- task('deploy-contract', 'Deploy a Clarity smart contract to Stacks blockchain')
20
- .addParam('contractName', 'Name of the contract', { type: 'string', required: true })
21
- .addParam('file', 'Path to Clarity contract file', { type: 'string', required: true })
22
- .addParam('network', 'Network to deploy to (mainnet|testnet)', { type: 'string', required: false, defaultValue: 'testnet' })
23
- .setAction(async (args, env) => {
24
- const contractName = args.contractName as string;
25
- const file = args.file as string;
26
- const network = args.network as string;
27
- const codeBody = fs.readFileSync(file, 'utf8');
28
- // Switch network if needed
29
- if (network === 'mainnet') {
30
- env.network = createNetwork({ network: 'mainnet', client: { baseUrl: env.config.networks.mainnet.url } });
31
- } else {
32
- env.network = createNetwork({ network: 'testnet', client: { baseUrl: env.config.networks.testnet.url } });
33
- }
34
- const tx = await makeContractDeploy({
35
- contractName,
36
- codeBody,
37
- senderKey: env.wallet.privateKey,
38
- network: env.network,
39
- });
40
- const result = await broadcastTransaction({ transaction: tx, network: env.network });
41
- if (containsSecret(result)) {
42
- console.warn('[mobilestacks] Warning: Output may contain sensitive data.');
43
- }
44
- // Mask any address fields if present
45
- if (result && result.txid) {
46
- result.txid = maskAddress(result.txid);
47
- }
48
- return result;
49
- });
@@ -1,7 +0,0 @@
1
- import { task } from '../core/dsl';
2
-
3
- task('example', 'An example user task for onboarding')
4
- .addParam('name', 'Your name', { type: 'string', required: false, defaultValue: 'World' })
5
- .setAction(async (args) => {
6
- return `Hello, ${args.name}! Welcome to mobilestacks.`;
7
- });
@@ -1,21 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
-
4
- task('faucet-request', 'Request STX from the testnet faucet')
5
- .addParam('address', 'STX address to fund', { type: 'string', required: true })
6
- .setAction(async (args, env) => {
7
- if (env.network.client.baseUrl && env.network.client.baseUrl.includes('testnet')) {
8
- const url = `https://stacks-node-api.testnet.stacks.co/extended/v1/faucet/stx`; // Default testnet faucet
9
- const res = await fetch(url, {
10
- method: 'POST',
11
- headers: { 'Content-Type': 'application/json' },
12
- body: JSON.stringify({ address: args.address })
13
- });
14
- if (!res.ok) {
15
- throw new Error(`Faucet request failed: ${res.statusText}`);
16
- }
17
- return await res.json();
18
- } else {
19
- throw new Error('Faucet is only available on testnet.');
20
- }
21
- });
@@ -1,34 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
-
4
- function maskAddress(address: string) {
5
- return address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
6
- }
7
-
8
- function containsSecret(obj: unknown): boolean {
9
- const str = JSON.stringify(obj);
10
- return /[A-Za-z0-9]{32,}/.test(str); // crude check for long secrets
11
- }
12
-
13
- task('get-balance', 'Get STX balance for the configured wallet address or a provided address')
14
- .addParam('address', 'STX address to check (optional, defaults to wallet main address)', { type: 'string', required: false })
15
- .setAction(async (args, env) => {
16
- const address = (args.address as string) || env.wallet.address;
17
- if (!address) throw new Error('No wallet address found.');
18
- const network = env.network;
19
- const apiUrl = network.client.baseUrl;
20
- const url = `${apiUrl}/extended/v1/address/${address}/balances`;
21
- const res = await fetch(url);
22
- if (!res.ok) throw new Error(`Failed to fetch balance: ${res.statusText}`);
23
- const data: unknown = await res.json();
24
- const result = {
25
- address: maskAddress(address),
26
- stx: (data as { stx: { balance: string; locked: string; unlock_height: number } }).stx.balance,
27
- locked: (data as { stx: { balance: string; locked: string; unlock_height: number } }).stx.locked,
28
- unlock_height: (data as { stx: { balance: string; locked: string; unlock_height: number } }).stx.unlock_height
29
- };
30
- if (containsSecret(result)) {
31
- console.warn('[mobilestacks] Warning: Output may contain sensitive data.');
32
- }
33
- return result;
34
- });
@@ -1,18 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
-
4
- task('get-contract-info', 'Get contract attributes and details from Stacks blockchain')
5
- .addParam('contractAddress', 'Deployed contract address (STX...)', { type: 'string', required: true })
6
- .addParam('contractName', 'Contract name', { type: 'string', required: true })
7
- .addParam('network', 'Network (mainnet|testnet)', { type: 'string', required: false, defaultValue: 'testnet' })
8
- .setAction(async (args) => {
9
- const { contractAddress, contractName, network } = args;
10
- const apiUrl = network === 'mainnet'
11
- ? 'https://stacks-node-api.mainnet.stacks.co'
12
- : 'https://stacks-node-api.testnet.stacks.co';
13
- const url = `${apiUrl}/extended/v1/contract/${contractAddress}/${contractName}`;
14
- const res = await fetch(url);
15
- if (!res.ok) throw new Error(`Failed to fetch contract info: ${res.statusText}`);
16
- const info = await res.json();
17
- return info;
18
- });
@@ -1,39 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
-
4
- function maskAddress(address: string) {
5
- return address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
6
- }
7
-
8
- function containsSecret(obj: unknown): boolean {
9
- const str = JSON.stringify(obj);
10
- return /[A-Za-z0-9]{32,}/.test(str); // crude check for long secrets
11
- }
12
-
13
- task('get-tx-history', 'Get transaction history for the configured wallet address')
14
- .addParam('limit', 'Number of transactions to fetch', { type: 'number', required: false, defaultValue: 10 })
15
- .setAction(async (args, env) => {
16
- const address = env.wallet.address;
17
- if (!address) throw new Error('No wallet address found.');
18
- const network = env.network;
19
- const apiUrl = network.client.baseUrl;
20
- const url = `${apiUrl}/extended/v1/address/${address}/transactions?limit=${args.limit}`;
21
- const res = await fetch(url);
22
- if (!res.ok) throw new Error(`Failed to fetch tx history: ${res.statusText}`);
23
- const data = await res.json() as { results: unknown[] };
24
- const txs = data.results.map((tx) => ({
25
- tx_id: (tx as { tx_id: string }).tx_id,
26
- type: (tx as { tx_type: string }).tx_type,
27
- status: (tx as { tx_status: string }).tx_status,
28
- fee_rate: (tx as { fee_rate: string }).fee_rate,
29
- block_height: (tx as { block_height: number }).block_height
30
- }));
31
- // Mask address in logs and warn if secrets detected
32
- if (containsSecret(txs)) {
33
- console.warn('[mobilestacks] Warning: Output may contain sensitive data.');
34
- }
35
- return {
36
- address: maskAddress(address),
37
- transactions: txs
38
- };
39
- });
@@ -1,27 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import { generateWallet } from '@stacks/wallet-sdk';
3
-
4
- function maskAddress(address: string) {
5
- return address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
6
- }
7
-
8
- function containsSecret(obj: unknown): boolean {
9
- const str = JSON.stringify(obj);
10
- return /[A-Za-z0-9]{32,}/.test(str); // crude check for long secrets
11
- }
12
-
13
- task('list-accounts', 'List all accounts derived from the configured seed phrase')
14
- .setAction(async (args, env) => {
15
- if (!env.config.wallet.seedPhrase) {
16
- throw new Error('No seed phrase configured.');
17
- }
18
- const wallet = await generateWallet({ secretKey: env.config.wallet.seedPhrase, password: '' });
19
- const result = wallet.accounts.map((a: unknown) => ({
20
- address: maskAddress((a as { address: string }).address || (a as { stxAddress: string }).stxAddress),
21
- index: (a as { index: number }).index
22
- }));
23
- if (containsSecret(result)) {
24
- console.warn('[mobilestacks] Warning: Output may contain sensitive data.');
25
- }
26
- return result;
27
- });
@@ -1,39 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import { broadcastTransaction, makeSTXTokenTransfer } from '@stacks/transactions';
3
-
4
- function maskAddress(address: string) {
5
- return address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
6
- }
7
-
8
- function containsSecret(obj: unknown): boolean {
9
- const str = JSON.stringify(obj);
10
- return /[A-Za-z0-9]{32,}/.test(str); // crude check for long secrets
11
- }
12
-
13
- // This task sends STX to an address using the loaded SRE (env)
14
- task('send-stx', 'Sends STX to an address')
15
- .addParam('to', 'Recipient STX address', { type: 'string', required: true })
16
- .addParam('amount', 'Amount in microSTX', { type: 'number', required: true })
17
- .addParam('memo', 'Optional memo', { type: 'string', required: false, defaultValue: '' })
18
- .setAction(async (args, env) => {
19
- const to = args.to as string;
20
- const amount = args.amount as number;
21
- const memo = args.memo as string | undefined;
22
- const { wallet, network } = env;
23
- const tx = await makeSTXTokenTransfer({
24
- recipient: to,
25
- amount: BigInt(amount),
26
- senderKey: wallet.privateKey,
27
- network,
28
- memo: memo || undefined
29
- });
30
- const result = await broadcastTransaction({ transaction: tx, network });
31
- if (containsSecret(result)) {
32
- console.warn('[mobilestacks] Warning: Output may contain sensitive data.');
33
- }
34
- // Mask any address fields if present
35
- if (result && result.txid) {
36
- result.txid = maskAddress(result.txid);
37
- }
38
- return result;
39
- });
@@ -1,33 +0,0 @@
1
- import { task } from '../core/dsl';
2
- import fetch from 'node-fetch';
3
- import fs from 'fs';
4
-
5
- task('verify-contract', 'Verify a deployed Clarity contract on the Stacks explorer')
6
- .addParam('contractAddress', 'Deployed contract address (STX...)', { type: 'string', required: true })
7
- .addParam('contractName', 'Contract name', { type: 'string', required: true })
8
- .addParam('source', 'Path to contract source file', { type: 'string', required: true })
9
- .addParam('network', 'Network (mainnet|testnet)', { type: 'string', required: false, defaultValue: 'testnet' })
10
- .setAction(async (args) => {
11
- const contractAddress = args.contractAddress as string;
12
- const contractName = args.contractName as string;
13
- const source = args.source as string;
14
- const network = args.network as string;
15
- const codeBody = fs.readFileSync(source, 'utf8');
16
- const apiUrl = network === 'mainnet'
17
- ? 'https://stacks-node-api.mainnet.stacks.co'
18
- : 'https://stacks-node-api.testnet.stacks.co';
19
- // Fetch contract source from chain
20
- const url = `${apiUrl}/extended/v1/contract/source/${contractAddress}/${contractName}`;
21
- const res = await fetch(url);
22
- if (!res.ok) throw new Error(`Failed to fetch on-chain contract source: ${res.statusText}`);
23
- const onChain = await res.json() as { source_code?: string };
24
- const verified = onChain.source_code && onChain.source_code.trim() === codeBody.trim();
25
- const explorer = network === 'mainnet'
26
- ? `https://explorer.stacks.co/txid/${contractAddress}.${contractName}`
27
- : `https://explorer.stacks.co/txid/${contractAddress}.${contractName}?chain=testnet`;
28
- return {
29
- verified,
30
- message: verified ? 'Contract source matches on-chain code!' : 'Source does NOT match on-chain code!',
31
- explorer
32
- };
33
- });
@@ -1,30 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const NetworkConfigSchema = z.object({
4
- url: z.string().url(),
5
- name: z.string(),
6
- explorerUrl: z.string().url().optional(),
7
- faucetUrl: z.string().url().nullable().optional(),
8
- });
9
-
10
-
11
- // Wallet config: allow privateKey, seedPhrase, or both. If both, privateKey is used.
12
- export const WalletConfigSchema = z.object({
13
- privateKey: z.string().min(1, "Private key is required").optional(),
14
- seedPhrase: z.string().optional(),
15
- derivationPath: z.string().optional(),
16
- address: z.string().optional(),
17
- }).refine(
18
- (data) => data.privateKey || data.seedPhrase,
19
- { message: "Either privateKey or seedPhrase is required" }
20
- );
21
-
22
- export const MobilestacksConfigSchema = z.object({
23
- networks: z.record(NetworkConfigSchema),
24
- defaultNetwork: z.string(),
25
- wallet: WalletConfigSchema,
26
- });
27
-
28
- export type NetworkConfig = z.infer<typeof NetworkConfigSchema>;
29
- export type WalletConfig = z.infer<typeof WalletConfigSchema>;
30
- export type MobilestacksConfig = z.infer<typeof MobilestacksConfigSchema>;