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.
- package/dist/src/cli/index.js +1 -1
- package/dist/src/cli/init.d.ts.map +1 -1
- package/dist/src/cli/init.js +38 -16
- package/dist/src/core/runtime-environment.d.ts +1 -0
- package/dist/src/core/runtime-environment.d.ts.map +1 -1
- package/dist/src/core/runtime-environment.js +19 -16
- package/dist/src/types/config.d.ts +2 -22
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/config.js +4 -3
- package/package.json +3 -7
- package/Clarinet.toml +0 -7
- package/contracts/sample-contract.clar +0 -2
- package/src/cli/index.ts +0 -105
- package/src/cli/init.ts +0 -57
- package/src/config/config-loading.ts +0 -31
- package/src/core/dsl.test.ts +0 -63
- package/src/core/dsl.ts +0 -73
- package/src/core/env.ts +0 -8
- package/src/core/extender.ts +0 -29
- package/src/core/runtime-environment.ts +0 -74
- package/src/core/simnet.ts +0 -56
- package/src/core/tasks-definitions.ts +0 -47
- package/src/index.ts +0 -4
- package/src/tasks/call-contract-function.ts +0 -31
- package/src/tasks/deploy-contract.ts +0 -49
- package/src/tasks/example-task.ts +0 -7
- package/src/tasks/faucet-request.ts +0 -21
- package/src/tasks/get-balance.ts +0 -34
- package/src/tasks/get-contract-info.ts +0 -18
- package/src/tasks/get-tx-history.ts +0 -39
- package/src/tasks/list-accounts.ts +0 -27
- package/src/tasks/send-stx.ts +0 -39
- package/src/tasks/verify-contract.ts +0 -33
- package/src/types/config.ts +0 -30
package/dist/src/cli/index.js
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,OAAO,kBAoF5B"}
|
package/dist/src/cli/init.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
//
|
|
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 '
|
|
60
|
-
|
|
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 +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;
|
|
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
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 (
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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;
|
|
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"}
|
package/dist/src/types/config.js
CHANGED
|
@@ -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:
|
|
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().
|
|
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
|
-
})
|
|
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
|
+
"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
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
|
-
}
|
package/src/core/dsl.test.ts
DELETED
|
@@ -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
package/src/core/extender.ts
DELETED
|
@@ -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
|
-
}
|
package/src/core/simnet.ts
DELETED
|
@@ -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,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
|
-
});
|
package/src/tasks/get-balance.ts
DELETED
|
@@ -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
|
-
});
|
package/src/tasks/send-stx.ts
DELETED
|
@@ -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
|
-
});
|
package/src/types/config.ts
DELETED
|
@@ -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>;
|