@moltbankhq/openclaw 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.ts +137 -113
  3. package/package.json +37 -2
package/README.md CHANGED
@@ -32,11 +32,11 @@ openclaw plugins install @moltbankhq/openclaw
32
32
 
33
33
  This package connects your OpenClaw agent to MoltBank's stablecoin treasury infrastructure. During setup, it:
34
34
 
35
- - Downloads and configures the MoltBank skill (`skill.md` + scripts)
35
+ - Downloads and configures the MoltBank skill (`SKILL.md` + scripts)
36
36
  - Installs and registers [mcporter](https://www.npmjs.com/package/mcporter) for MCP server connectivity
37
37
  - Handles OAuth device-code authentication to link your agent to your MoltBank account
38
38
  - Configures sandbox (Docker) or host mode automatically based on your OpenClaw setup
39
- - Injects required environment variables (`MOLTBANK`, `SIGNER`, `ACTIVE_ORG_OVERRIDE`)
39
+ - Injects the sandbox auth path (`MOLTBANK_CREDENTIALS_PATH`) plus `ACTIVE_ORG_OVERRIDE`, while keeping tokens and signer keys in a skill-local credentials file
40
40
 
41
41
  Once set up, your agent can manage treasury operations, set per-agent spending limits, handle x402 payments, and interact with MoltBank's MCP server — all within OpenClaw.
42
42
 
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, spawnSync } from 'child_process';
2
2
  import { chmodSync, chownSync, existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, type Dirent } from 'fs';
3
- import { join, dirname } from 'path';
3
+ import { join, dirname, basename } from 'path';
4
4
  import { homedir, userInfo } from 'os';
5
5
  const IS_WIN = process.platform === 'win32';
6
6
 
@@ -16,6 +16,7 @@ interface CredentialsOrganization {
16
16
  name?: string;
17
17
  access_token?: string;
18
18
  x402_signer_private_key?: string;
19
+ x402_wallet?: Record<string, unknown>;
19
20
  }
20
21
 
21
22
  interface CredentialsFile {
@@ -952,6 +953,77 @@ function getCredentialsPath(): string {
952
953
  return process.env.MOLTBANK_CREDENTIALS_PATH || join(homedir(), '.MoltBank', 'credentials.json');
953
954
  }
954
955
 
956
+ function getSandboxCredentialsHostPath(skillDir: string): string {
957
+ return join(skillDir, '.sandbox', 'credentials.json');
958
+ }
959
+
960
+ function getSandboxCredentialsContainerPath(skillName: string): string {
961
+ return `/workspace/skills/${skillName}/.sandbox/credentials.json`;
962
+ }
963
+
964
+ function writeSandboxCredentialsFile(
965
+ skillDir: string,
966
+ activeOrg: string,
967
+ org: CredentialsOrganization,
968
+ privateKey: string,
969
+ api: LoggerApi
970
+ ): boolean {
971
+ const sandboxDir = dirname(getSandboxCredentialsHostPath(skillDir));
972
+ const sandboxCredentialsPath = getSandboxCredentialsHostPath(skillDir);
973
+ const sandboxOrg: Record<string, unknown> = {
974
+ name: activeOrg,
975
+ access_token: org.access_token ?? ''
976
+ };
977
+
978
+ if (privateKey) {
979
+ sandboxOrg.x402_signer_private_key = privateKey;
980
+ }
981
+ if (isRecord(org.x402_wallet)) {
982
+ sandboxOrg.x402_wallet = org.x402_wallet;
983
+ }
984
+
985
+ const sandboxCredentials = {
986
+ active_organization: activeOrg,
987
+ organizations: [sandboxOrg]
988
+ };
989
+
990
+ try {
991
+ mkdirSync(sandboxDir, { recursive: true });
992
+ writeFileSync(sandboxCredentialsPath, JSON.stringify(sandboxCredentials, null, 2) + '\n', 'utf8');
993
+
994
+ if (!IS_WIN) {
995
+ try {
996
+ chmodSync(sandboxDir, 0o700);
997
+ } catch {
998
+ // ignore
999
+ }
1000
+ try {
1001
+ chmodSync(sandboxCredentialsPath, 0o600);
1002
+ } catch {
1003
+ // ignore
1004
+ }
1005
+ }
1006
+
1007
+ api.logger.info('[moltbank] ✓ sandbox credentials file updated');
1008
+ return true;
1009
+ } catch (e) {
1010
+ api.logger.warn('[moltbank] ✗ could not write sandbox credentials file: ' + String(e));
1011
+ return false;
1012
+ }
1013
+ }
1014
+
1015
+ function removeSandboxSecretEnvKeys(envObj: Record<string, string>, api: LoggerApi): boolean {
1016
+ let changed = false;
1017
+ for (const key of ['MOLTBANK', 'SIGNER']) {
1018
+ if (key in envObj) {
1019
+ delete envObj[key];
1020
+ changed = true;
1021
+ api.logger.info(`[moltbank] ✓ removed legacy sandbox secret env key ${key}`);
1022
+ }
1023
+ }
1024
+ return changed;
1025
+ }
1026
+
955
1027
  function readOpenclawConfig(): OpenclawConfig {
956
1028
  const configPath = join(homedir(), '.openclaw', 'openclaw.json');
957
1029
  try {
@@ -1902,10 +1974,47 @@ export function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api:
1902
1974
  api.logger.info('[moltbank] ✓ MoltBank will use skill-local mcporter config only');
1903
1975
  }
1904
1976
 
1977
+ function runHostReadinessCheck(skillDir: string, api: LoggerApi): boolean {
1978
+ const active = parseActiveTokenFromCredentials();
1979
+ if (!active.ok || !active.activeOrg) {
1980
+ api.logger.warn('[moltbank] host setup incomplete: active organization could not be resolved for readiness verification');
1981
+ return false;
1982
+ }
1983
+
1984
+ const wrapperPath = IS_WIN ? join(skillDir, 'scripts', 'moltbank.ps1') : join(skillDir, 'scripts', 'moltbank.sh');
1985
+ const readinessArgs = ['call', 'MoltBank.get_balance', `organizationName=${active.activeOrg}`, 'date=today'];
1986
+ const readinessTimeoutMs = IS_WIN ? 30000 : 25000;
1987
+
1988
+ api.logger.info(`[moltbank] verifying readiness with authenticated balance read (org: ${active.activeOrg})...`);
1989
+
1990
+ const readiness = runCommand(wrapperPath, readinessArgs, {
1991
+ cwd: skillDir,
1992
+ silent: true,
1993
+ timeoutMs: readinessTimeoutMs,
1994
+ env: {
1995
+ ACTIVE_ORG_OVERRIDE: active.activeOrg
1996
+ }
1997
+ });
1998
+
1999
+ if (!readiness.ok) {
2000
+ const combined = `${readiness.stderr}\n${readiness.stdout}`.trim();
2001
+ const detail = lastNonEmptyLine(combined);
2002
+ if (detail) {
2003
+ api.logger.warn(`[moltbank] readiness detail: ${detail}`);
2004
+ }
2005
+ api.logger.warn('[moltbank] host setup incomplete: authenticated balance-read verification failed');
2006
+ return false;
2007
+ }
2008
+
2009
+ api.logger.info('[moltbank] host readiness verified (`MoltBank.get_balance`)');
2010
+ return true;
2011
+ }
2012
+
1905
2013
  // ─── sandbox env vars ────────────────────────────────────────────────────────
1906
2014
 
1907
2015
  export function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
1908
2016
  const credsPath = getCredentialsPath();
2017
+ const skillName = basename(skillDir);
1909
2018
 
1910
2019
  if (!existsSync(credsPath)) {
1911
2020
  api.logger.info('[moltbank] no credentials.json found — skipping sandbox env injection');
@@ -1933,14 +2042,7 @@ export function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
1933
2042
  const envPath = ['agents', 'defaults', 'sandbox', 'docker', 'env'];
1934
2043
  setNestedValue(config, envPath, getNestedValue(config, envPath) ?? {});
1935
2044
  const envObj = getNestedValue(config, envPath) as Record<string, string>;
1936
-
1937
- if (envObj.MOLTBANK !== org.access_token) {
1938
- envObj.MOLTBANK = org.access_token;
1939
- api.logger.info(`[moltbank] ✓ MOLTBANK injected (org: ${activeOrg})`);
1940
- changed = true;
1941
- } else {
1942
- api.logger.info(`[moltbank] ✓ MOLTBANK already injected (org: ${activeOrg})`);
1943
- }
2045
+ changed = removeSandboxSecretEnvKeys(envObj, api) || changed;
1944
2046
 
1945
2047
  if (envObj.ACTIVE_ORG_OVERRIDE !== activeOrg) {
1946
2048
  envObj.ACTIVE_ORG_OVERRIDE = activeOrg;
@@ -1976,21 +2078,28 @@ export function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
1976
2078
  }
1977
2079
  }
1978
2080
 
2081
+ if (!writeSandboxCredentialsFile(skillDir, activeOrg, org, privateKey, api)) {
2082
+ return false;
2083
+ }
2084
+
2085
+ const sandboxCredentialsContainerPath = getSandboxCredentialsContainerPath(skillName);
2086
+ if (envObj.MOLTBANK_CREDENTIALS_PATH !== sandboxCredentialsContainerPath) {
2087
+ envObj.MOLTBANK_CREDENTIALS_PATH = sandboxCredentialsContainerPath;
2088
+ api.logger.info(`[moltbank] ✓ MOLTBANK_CREDENTIALS_PATH injected (${sandboxCredentialsContainerPath})`);
2089
+ changed = true;
2090
+ } else {
2091
+ api.logger.info(`[moltbank] ✓ MOLTBANK_CREDENTIALS_PATH already set (${sandboxCredentialsContainerPath})`);
2092
+ }
2093
+
1979
2094
  if (privateKey) {
1980
- if (envObj.SIGNER !== privateKey) {
1981
- envObj.SIGNER = privateKey;
1982
- api.logger.info('[moltbank] ✓ SIGNER injected');
1983
- changed = true;
1984
- } else {
1985
- api.logger.info('[moltbank] ✓ SIGNER already injected');
1986
- }
2095
+ api.logger.info('[moltbank] sandbox x402 signer available via sandbox credentials file');
1987
2096
  } else {
1988
- api.logger.info('[moltbank] ℹ SIGNER not available — skipped (agent will handle on first use)');
2097
+ api.logger.info('[moltbank] ℹ sandbox x402 signer not available yet helper scripts will use sandbox credentials path if generated later');
1989
2098
  }
1990
2099
 
1991
2100
  if (changed) {
1992
2101
  writeOpenclawConfig(config);
1993
- api.logger.info('[moltbank] ✓ openclaw.json updated with sandbox env vars');
2102
+ api.logger.info('[moltbank] ✓ openclaw.json updated with sandbox auth path only (no token/private key persisted)');
1994
2103
  }
1995
2104
 
1996
2105
  return changed;
@@ -2212,7 +2321,7 @@ export async function runSetup(
2212
2321
  api.logger.info('[moltbank] [sandbox 8/10] configuring sandbox docker settings...');
2213
2322
  const sandboxChanged = configureSandbox(api);
2214
2323
 
2215
- api.logger.info('[moltbank] [sandbox 9/10] injecting sandbox env vars (MOLTBANK, ACTIVE_ORG_OVERRIDE, SIGNER)...');
2324
+ api.logger.info('[moltbank] [sandbox 9/10] injecting sandbox auth env (MOLTBANK_CREDENTIALS_PATH, ACTIVE_ORG_OVERRIDE)...');
2216
2325
  const envChanged = injectSandboxEnv(skillDir, api);
2217
2326
 
2218
2327
  api.logger.info('[moltbank] [sandbox 10/10] apply sandbox changes (recreate + gateway stop)...');
@@ -2265,98 +2374,8 @@ export async function runSetup(
2265
2374
  api.logger.info('[moltbank] [host 7/8] writing/registering mcporter config...');
2266
2375
  ensureMcporterConfig(skillDir, appBaseUrl, api);
2267
2376
 
2268
- api.logger.info('[moltbank] [host 8/8] running wrapper smoke test...');
2269
- const smokeTimeoutMs = IS_WIN ? 15000 : 20000;
2270
- let smokeOk = false;
2271
-
2272
- if (IS_WIN) {
2273
- const mcporterConfigPath = join(skillDir, 'assets', 'mcporter.json');
2274
- const active = parseActiveTokenFromCredentials();
2275
- const mcpSmoke = runCommand('mcporter', ['--config', mcporterConfigPath, 'list', 'MoltBank'], {
2276
- cwd: skillDir,
2277
- silent: true,
2278
- timeoutMs: smokeTimeoutMs,
2279
- env: {
2280
- MOLTBANK: active.token ?? '__MOLTBANK_PLACEHOLDER__'
2281
- }
2282
- });
2283
-
2284
- if (mcpSmoke.ok) {
2285
- api.logger.info('[moltbank] host smoke test passed (`mcporter list MoltBank`)');
2286
- smokeOk = true;
2287
- } else {
2288
- const mcpCombined = `${mcpSmoke.stderr}\n${mcpSmoke.stdout}`.trim();
2289
- const mcpCombinedLower = mcpCombined.toLowerCase();
2290
- const mcpDetail = lastNonEmptyLine(mcpCombined);
2291
- if (mcpDetail) {
2292
- api.logger.warn(`[moltbank] smoke detail: ${mcpDetail}`);
2293
- }
2294
-
2295
- const timedOut = mcpSmoke.stderr.includes('ETIMEDOUT') || mcpCombinedLower.includes('timed out');
2296
- if (timedOut) {
2297
- api.logger.warn(
2298
- `[moltbank] host MCP smoke timed out after ${Math.floor(smokeTimeoutMs / 1000)}s; running local fallback checks`
2299
- );
2300
-
2301
- const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
2302
- const fallback = runCommand(
2303
- 'powershell',
2304
- [
2305
- '-NoProfile',
2306
- '-NonInteractive',
2307
- '-ExecutionPolicy',
2308
- 'Bypass',
2309
- '-Command',
2310
- "$ErrorActionPreference = 'Stop'; if (-not (Test-Path -LiteralPath $env:MOLTBANK_WRAPPER_PATH)) { throw 'moltbank.ps1 not found' }; if (-not (Get-Command bash -ErrorAction SilentlyContinue)) { throw 'bash is not installed or not on PATH' }; if (-not (Get-Command mcporter -ErrorAction SilentlyContinue)) { throw 'mcporter is not installed or not on PATH' }; Write-Output 'ok'"
2311
- ],
2312
- {
2313
- cwd: skillDir,
2314
- silent: true,
2315
- timeoutMs: 10000,
2316
- env: { MOLTBANK_WRAPPER_PATH: ps1Path }
2317
- }
2318
- );
2319
-
2320
- if (fallback.ok) {
2321
- api.logger.warn('[moltbank] host smoke fallback passed (wrapper prerequisites available), but MCP endpoint was not verified');
2322
- smokeOk = true;
2323
- } else {
2324
- const fallbackCombined = `${fallback.stderr}\n${fallback.stdout}`.trim();
2325
- const fallbackDetail = lastNonEmptyLine(fallbackCombined);
2326
- if (fallbackDetail) {
2327
- api.logger.warn(`[moltbank] fallback detail: ${fallbackDetail}`);
2328
- }
2329
- api.logger.warn('[moltbank] host setup incomplete: MCP smoke timed out and fallback checks failed');
2330
- }
2331
- } else if (
2332
- mcpCombinedLower.includes('mcporter') &&
2333
- (mcpCombinedLower.includes('not recognized') || mcpCombinedLower.includes('not found'))
2334
- ) {
2335
- api.logger.warn('[moltbank] mcporter binary not found in PATH during smoke test');
2336
- } else {
2337
- api.logger.warn('[moltbank] host setup incomplete: smoke test failed (`mcporter list MoltBank`)');
2338
- }
2339
- }
2340
- } else {
2341
- const smoke = runCommand(join(skillDir, 'scripts', 'moltbank.sh'), ['list', 'MoltBank'], {
2342
- cwd: skillDir,
2343
- silent: true,
2344
- timeoutMs: smokeTimeoutMs
2345
- });
2346
- if (!smoke.ok) {
2347
- const smokeCombined = `${smoke.stderr}\n${smoke.stdout}`.trim();
2348
- const smokeDetail = lastNonEmptyLine(smokeCombined);
2349
- if (smokeDetail) {
2350
- api.logger.warn(`[moltbank] smoke detail: ${smokeDetail}`);
2351
- }
2352
- api.logger.warn('[moltbank] host setup incomplete: smoke test failed (`moltbank list MoltBank` via platform script)');
2353
- } else {
2354
- api.logger.info('[moltbank] host smoke test passed (`moltbank list MoltBank`)');
2355
- smokeOk = true;
2356
- }
2357
- }
2358
-
2359
- hostReady = smokeOk;
2377
+ api.logger.info('[moltbank] [host 8/8] running readiness balance check...');
2378
+ hostReady = runHostReadinessCheck(skillDir, api);
2360
2379
 
2361
2380
  api.logger.info('[moltbank] host mode — setup completed with onboarding flow');
2362
2381
 
@@ -2379,13 +2398,18 @@ export async function runSetup(
2379
2398
  const finalEnv = asStringRecord(getNestedValue(finalConfig, ['agents', 'defaults', 'sandbox', 'docker', 'env']));
2380
2399
  const finalNetwork = asString(getNestedValue(finalConfig, ['agents', 'defaults', 'sandbox', 'docker', 'network']));
2381
2400
  const finalCmd = asString(getNestedValue(finalConfig, ['agents', 'defaults', 'sandbox', 'docker', 'setupCommand']));
2401
+ const sandboxCredentialsPath = getSandboxCredentialsHostPath(skillDir);
2382
2402
 
2383
2403
  api.logger.info(`[moltbank] openclaw.json sandbox env:`);
2384
- api.logger.info(`[moltbank] MOLTBANK: ${finalEnv.MOLTBANK ? '✓ set' : '✗ missing'}`);
2404
+ api.logger.info(
2405
+ `[moltbank] MOLTBANK_CREDENTIALS_PATH: ${finalEnv.MOLTBANK_CREDENTIALS_PATH ? `✓ ${finalEnv.MOLTBANK_CREDENTIALS_PATH}` : '✗ missing'}`
2406
+ );
2385
2407
  api.logger.info(
2386
2408
  `[moltbank] ACTIVE_ORG_OVERRIDE: ${finalEnv.ACTIVE_ORG_OVERRIDE ? `✓ "${finalEnv.ACTIVE_ORG_OVERRIDE}"` : '✗ missing'}`
2387
2409
  );
2388
- api.logger.info(`[moltbank] SIGNER: ${finalEnv.SIGNER ? '✓ set' : '✗ missing (agent may generate on first use)'}`);
2410
+ api.logger.info(
2411
+ `[moltbank] sandbox credentials: ${existsSync(sandboxCredentialsPath) ? '✓ present (skill-local, 600)' : '✗ missing'}`
2412
+ );
2389
2413
  api.logger.info(`[moltbank] sandbox docker:`);
2390
2414
  api.logger.info(
2391
2415
  `[moltbank] network: ${finalNetwork === 'bridge' ? '✓ bridge' : `✗ "${finalNetwork}" (expected bridge)`}`
package/package.json CHANGED
@@ -1,20 +1,55 @@
1
1
  {
2
2
  "name": "@moltbankhq/openclaw",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "MoltBank stablecoin treasury CLI and OpenClaw plugin",
5
+ "homepage": "https://github.com/moltbankhq/openclaw-plugin#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/moltbankhq/openclaw-plugin.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/moltbankhq/openclaw-plugin/issues"
12
+ },
5
13
  "main": "index.ts",
14
+ "types": "index.ts",
15
+ "exports": {
16
+ ".": "./index.ts",
17
+ "./cli": "./cli.ts",
18
+ "./package.json": "./package.json"
19
+ },
6
20
  "bin": {
7
21
  "moltbank": "./bin/moltbank.mjs"
8
22
  },
23
+ "files": [
24
+ "README.md",
25
+ "bin/",
26
+ "cli.ts",
27
+ "index.ts",
28
+ "openclaw.plugin.json"
29
+ ],
9
30
  "openclaw": {
10
31
  "extensions": [
11
32
  "./index.ts"
12
33
  ]
13
34
  },
14
- "keywords": [],
35
+ "keywords": [
36
+ "moltbank",
37
+ "openclaw",
38
+ "treasury",
39
+ "mcp",
40
+ "cli",
41
+ "stablecoin",
42
+ "x402"
43
+ ],
15
44
  "author": "",
16
45
  "license": "ISC",
17
46
  "type": "module",
47
+ "engines": {
48
+ "node": ">=22"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
18
53
  "dependencies": {
19
54
  "tsx": "^4.20.6"
20
55
  },