mobilestacks 0.1.3 → 0.1.5

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.
@@ -3,18 +3,16 @@ declare const _default: {
3
3
  mainnet: {
4
4
  url: string;
5
5
  name: string;
6
- explorerUrl: string;
7
- faucetUrl: null;
8
6
  };
9
7
  testnet: {
10
8
  url: string;
11
9
  name: string;
12
- explorerUrl: string;
13
- faucetUrl: string;
14
10
  };
15
11
  };
16
12
  defaultNetwork: string;
17
- wallet: {};
13
+ wallet: {
14
+ derivationPath: string;
15
+ };
18
16
  };
19
17
  export default _default;
20
18
  //# sourceMappingURL=mobilestacks.config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mobilestacks.config.d.ts","sourceRoot":"","sources":["../mobilestacks.config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AACA,wBAqBE"}
1
+ {"version":3,"file":"mobilestacks.config.d.ts","sourceRoot":"","sources":["../mobilestacks.config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wBAWE"}
@@ -1,25 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // Example mobilestacks.config.ts for testing
4
3
  exports.default = {
5
4
  networks: {
6
- mainnet: {
7
- url: "https://stacks-node-api.mainnet.stacks.co",
8
- name: "mainnet",
9
- explorerUrl: "https://explorer.stacks.co",
10
- faucetUrl: null
11
- },
12
- testnet: {
13
- url: "https://stacks-node-api.testnet.stacks.co",
14
- name: "testnet",
15
- explorerUrl: "https://explorer.stacks.co?chain=testnet",
16
- faucetUrl: "https://stacks-node-api.testnet.stacks.co/extended/v1/faucet/stx"
17
- }
5
+ mainnet: { url: 'https://stacks-node-api.mainnet.stacks.co', name: 'mainnet' },
6
+ testnet: { url: 'https://stacks-node-api.testnet.stacks.co', name: 'testnet' }
18
7
  },
19
- defaultNetwork: "testnet",
8
+ defaultNetwork: 'testnet',
20
9
  wallet: {
21
- // privateKey: "YOUR_PRIVATE_KEY_HERE",
22
- // seedPhrase: "your twelve word phrase here",
23
- // derivationPath: "m/44'/5757'/0'/0/0"
10
+ derivationPath: "m/44'/5757'/0'/0/0"
24
11
  }
25
12
  };
@@ -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
71
  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!');
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.3",
3
+ "version": "0.1.5",
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",
@@ -66,6 +66,7 @@
66
66
  "commander": "^11.0.0",
67
67
  "dotenv": "^17.3.1",
68
68
  "inquirer": "^8.2.7",
69
+ "mobilestacks": "^0.1.3",
69
70
  "node-fetch": "^2.7.0",
70
71
  "ts-node": "^10.9.1",
71
72
  "zod": "^3.25.76"
package/src/cli/init.ts CHANGED
@@ -23,35 +23,67 @@ export async function runInit() {
23
23
  message: 'Stacks testnet node URL:',
24
24
  default: 'https://stacks-node-api.testnet.stacks.co',
25
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
26
  {
37
27
  type: 'input',
38
28
  name: 'derivationPath',
39
- message: 'Derivation path (default: m/44\'/5757\'/0\'/0/0):',
29
+ message: "Derivation path (default: m/44'/5757'/0'/0/0):",
40
30
  default: "m/44'/5757'/0'/0/0",
41
31
  },
42
32
  ]);
43
33
 
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`;
34
+ // ── Config file: references env vars, never embeds secrets ──
35
+ const config = `import 'dotenv/config';
36
+
37
+ export default {
38
+ networks: {
39
+ mainnet: { url: process.env.STACKS_MAINNET_URL || '${answers.mainnetUrl}', name: 'mainnet' },
40
+ testnet: { url: process.env.STACKS_TESTNET_URL || '${answers.testnetUrl}', name: 'testnet' },
41
+ },
42
+ defaultNetwork: 'testnet',
43
+ wallet: {
44
+ // Secrets are read from environment variables — never hard-code them here.
45
+ privateKey: process.env.MOBILESTACKS_PRIVATE_KEY || '',
46
+ seedPhrase: process.env.MOBILESTACKS_SEED_PHRASE || '',
47
+ derivationPath: '${answers.derivationPath}',
48
+ },
49
+ };
50
+ `;
45
51
 
46
52
  fs.writeFileSync(path.join(process.cwd(), 'mobilestacks.config.ts'), config);
47
- // Scaffold example contract
53
+
54
+ // ── .env file (if it doesn't exist yet) ──
55
+ const envPath = path.join(process.cwd(), '.env');
56
+ if (!fs.existsSync(envPath)) {
57
+ 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`;
58
+ fs.writeFileSync(envPath, envContent, { mode: 0o600 }); // owner-only permissions
59
+ }
60
+
61
+ // ── Scaffold example contract ──
48
62
  const contractsDir = path.join(process.cwd(), 'contracts');
49
63
  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
64
+ fs.writeFileSync(
65
+ path.join(contractsDir, 'sample-contract.clar'),
66
+ '(define-public (hello-world)\n (ok "Hello, Stacks!"))\n',
67
+ );
68
+
69
+ // ── Scaffold example user task ──
52
70
  const tasksDir = path.join(process.cwd(), 'src', 'tasks');
53
71
  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!');
72
+ fs.writeFileSync(
73
+ path.join(tasksDir, 'example-task.ts'),
74
+ "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",
75
+ );
76
+
77
+ // ── Security notice ──
78
+ console.log('\n✅ Created:');
79
+ console.log(' • mobilestacks.config.ts (reads secrets from env vars)');
80
+ console.log(' • .env (store your secrets here)');
81
+ console.log(' • contracts/sample-contract.clar');
82
+ console.log(' • src/tasks/example-task.ts');
83
+
84
+ console.log('\n⚠️ SECURITY WARNING:');
85
+ console.log(' Your wallet secrets belong in the .env file, NOT in source code.');
86
+ console.log(' • .env is already listed in .gitignore — never remove that entry.');
87
+ console.log(' • Set MOBILESTACKS_PRIVATE_KEY or MOBILESTACKS_SEED_PHRASE in .env');
88
+ console.log(' • See .env.example for the full list of supported variables.\n');
57
89
  }
@@ -1,3 +1,4 @@
1
+ import 'dotenv/config';
1
2
  import { MobilestacksConfig, NetworkConfig } from '../types/config';
2
3
  import { getAddressFromPrivateKey } from '@stacks/transactions';
3
4
  import { generateWallet } from '@stacks/wallet-sdk';
@@ -33,26 +34,29 @@ export class RuntimeEnvironment {
33
34
  });
34
35
  }
35
36
 
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);
37
+ // ── Resolve wallet secrets (env vars take priority over config) ──
38
+ const privateKey =
39
+ process.env.MOBILESTACKS_PRIVATE_KEY ||
40
+ process.env.STACKS_PRIVATE_KEY ||
41
+ config.wallet.privateKey ||
42
+ '';
43
+ const seedPhrase =
44
+ process.env.MOBILESTACKS_SEED_PHRASE ||
45
+ process.env.STACKS_SEED_PHRASE ||
46
+ config.wallet.seedPhrase ||
47
+ '';
48
+
49
+ if (privateKey) {
50
+ const address = getAddressFromPrivateKey(privateKey, networkName === 'mainnet' ? 'mainnet' : 'testnet');
51
+ this.wallet = { privateKey, address };
52
+ } else if (seedPhrase) {
53
+ const derivPath = config.wallet.derivationPath || "m/44'/5757'/0'/0/0";
54
+ generateWallet({ secretKey: seedPhrase, password: '' }).then(wallet => {
55
+ const index = parseInt(derivPath.split('/').pop() || '0', 10);
49
56
  const account = wallet.accounts[index] || wallet.accounts[0];
50
- // Use getStxAddress to get the address
51
57
  // eslint-disable-next-line @typescript-eslint/no-var-requires
52
58
  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
-
59
+ const address = getStxAddress(account, this.network.transactionVersion);
56
60
  this.wallet = {
57
61
  privateKey: account.stxPrivateKey,
58
62
  address,
@@ -62,7 +66,10 @@ export class RuntimeEnvironment {
62
66
  throw new Error('Failed to generate wallet from seed phrase');
63
67
  });
64
68
  } else {
65
- throw new Error('No privateKey or seedPhrase provided in wallet config');
69
+ // No secrets anywhere — warn but don't crash (devnet may not need a wallet)
70
+ console.warn(
71
+ '⚠️ No wallet secret found. Set MOBILESTACKS_PRIVATE_KEY or MOBILESTACKS_SEED_PHRASE in your .env file.',
72
+ );
66
73
  }
67
74
  this.stacks = {};
68
75
 
@@ -8,16 +8,14 @@ export const NetworkConfigSchema = z.object({
8
8
  });
9
9
 
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
  export const WalletConfigSchema = z.object({
13
- privateKey: z.string().min(1, "Private key is required").optional(),
14
+ privateKey: z.string().optional(),
14
15
  seedPhrase: z.string().optional(),
15
16
  derivationPath: z.string().optional(),
16
17
  address: z.string().optional(),
17
- }).refine(
18
- (data) => data.privateKey || data.seedPhrase,
19
- { message: "Either privateKey or seedPhrase is required" }
20
- );
18
+ });
21
19
 
22
20
  export const MobilestacksConfigSchema = z.object({
23
21
  networks: z.record(NetworkConfigSchema),