fourmm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/bin.d.ts +9 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +319 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +35 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +145 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/query.d.ts +51 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +364 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/token.d.ts +55 -0
- package/dist/commands/token.d.ts.map +1 -0
- package/dist/commands/token.js +650 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/tools.d.ts +54 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +499 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/trade.d.ts +63 -0
- package/dist/commands/trade.d.ts.map +1 -0
- package/dist/commands/trade.js +933 -0
- package/dist/commands/trade.js.map +1 -0
- package/dist/commands/transfer.d.ts +51 -0
- package/dist/commands/transfer.d.ts.map +1 -0
- package/dist/commands/transfer.js +728 -0
- package/dist/commands/transfer.js.map +1 -0
- package/dist/commands/wallet.d.ts +111 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +716 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/contracts/erc20.d.ts +72 -0
- package/dist/contracts/erc20.d.ts.map +1 -0
- package/dist/contracts/erc20.js +55 -0
- package/dist/contracts/erc20.js.map +1 -0
- package/dist/contracts/fourmemeMmRouter.d.ts +68 -0
- package/dist/contracts/fourmemeMmRouter.d.ts.map +1 -0
- package/dist/contracts/fourmemeMmRouter.js +48 -0
- package/dist/contracts/fourmemeMmRouter.js.map +1 -0
- package/dist/contracts/pancakeRouter.d.ts +73 -0
- package/dist/contracts/pancakeRouter.d.ts.map +1 -0
- package/dist/contracts/pancakeRouter.js +50 -0
- package/dist/contracts/pancakeRouter.js.map +1 -0
- package/dist/contracts/tokenManager2.d.ts +193 -0
- package/dist/contracts/tokenManager2.d.ts.map +1 -0
- package/dist/contracts/tokenManager2.js +108 -0
- package/dist/contracts/tokenManager2.js.map +1 -0
- package/dist/contracts/tokenManagerHelper3.d.ts +118 -0
- package/dist/contracts/tokenManagerHelper3.d.ts.map +1 -0
- package/dist/contracts/tokenManagerHelper3.js +66 -0
- package/dist/contracts/tokenManagerHelper3.js.map +1 -0
- package/dist/datastore/cache.d.ts +20 -0
- package/dist/datastore/cache.d.ts.map +1 -0
- package/dist/datastore/cache.js +45 -0
- package/dist/datastore/cache.js.map +1 -0
- package/dist/datastore/index.d.ts +85 -0
- package/dist/datastore/index.d.ts.map +1 -0
- package/dist/datastore/index.js +341 -0
- package/dist/datastore/index.js.map +1 -0
- package/dist/datastore/paths.d.ts +17 -0
- package/dist/datastore/paths.d.ts.map +1 -0
- package/dist/datastore/paths.js +39 -0
- package/dist/datastore/paths.js.map +1 -0
- package/dist/datastore/types.d.ts +105 -0
- package/dist/datastore/types.d.ts.map +1 -0
- package/dist/datastore/types.js +8 -0
- package/dist/datastore/types.js.map +1 -0
- package/dist/fourmeme/auth.d.ts +22 -0
- package/dist/fourmeme/auth.d.ts.map +1 -0
- package/dist/fourmeme/auth.js +78 -0
- package/dist/fourmeme/auth.js.map +1 -0
- package/dist/fourmeme/create.d.ts +31 -0
- package/dist/fourmeme/create.d.ts.map +1 -0
- package/dist/fourmeme/create.js +111 -0
- package/dist/fourmeme/create.js.map +1 -0
- package/dist/fourmeme/upload.d.ts +16 -0
- package/dist/fourmeme/upload.d.ts.map +1 -0
- package/dist/fourmeme/upload.js +52 -0
- package/dist/fourmeme/upload.js.map +1 -0
- package/dist/lib/bundle.d.ts +51 -0
- package/dist/lib/bundle.d.ts.map +1 -0
- package/dist/lib/bundle.js +95 -0
- package/dist/lib/bundle.js.map +1 -0
- package/dist/lib/config.d.ts +58 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +183 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/const.d.ts +165 -0
- package/dist/lib/const.d.ts.map +1 -0
- package/dist/lib/const.js +98 -0
- package/dist/lib/const.js.map +1 -0
- package/dist/lib/env.d.ts +14 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +18 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/guards.d.ts +44 -0
- package/dist/lib/guards.d.ts.map +1 -0
- package/dist/lib/guards.js +65 -0
- package/dist/lib/guards.js.map +1 -0
- package/dist/lib/identify.d.ts +85 -0
- package/dist/lib/identify.d.ts.map +1 -0
- package/dist/lib/identify.js +88 -0
- package/dist/lib/identify.js.map +1 -0
- package/dist/lib/pricing.d.ts +62 -0
- package/dist/lib/pricing.d.ts.map +1 -0
- package/dist/lib/pricing.js +302 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/routing.d.ts +57 -0
- package/dist/lib/routing.d.ts.map +1 -0
- package/dist/lib/routing.js +67 -0
- package/dist/lib/routing.js.map +1 -0
- package/dist/lib/slippage.d.ts +29 -0
- package/dist/lib/slippage.d.ts.map +1 -0
- package/dist/lib/slippage.js +110 -0
- package/dist/lib/slippage.js.map +1 -0
- package/dist/lib/tracker.d.ts +68 -0
- package/dist/lib/tracker.d.ts.map +1 -0
- package/dist/lib/tracker.js +155 -0
- package/dist/lib/tracker.js.map +1 -0
- package/dist/lib/viem.d.ts +12 -0
- package/dist/lib/viem.d.ts.map +1 -0
- package/dist/lib/viem.js +44 -0
- package/dist/lib/viem.js.map +1 -0
- package/dist/lib/wallet-rows.d.ts +30 -0
- package/dist/lib/wallet-rows.d.ts.map +1 -0
- package/dist/lib/wallet-rows.js +9 -0
- package/dist/lib/wallet-rows.js.map +1 -0
- package/dist/lib/walletClient.d.ts +16 -0
- package/dist/lib/walletClient.d.ts.map +1 -0
- package/dist/lib/walletClient.js +26 -0
- package/dist/lib/walletClient.js.map +1 -0
- package/dist/wallets/groups/encrypt.d.ts +26 -0
- package/dist/wallets/groups/encrypt.d.ts.map +1 -0
- package/dist/wallets/groups/encrypt.js +52 -0
- package/dist/wallets/groups/encrypt.js.map +1 -0
- package/dist/wallets/groups/generate.d.ts +19 -0
- package/dist/wallets/groups/generate.d.ts.map +1 -0
- package/dist/wallets/groups/generate.js +36 -0
- package/dist/wallets/groups/generate.js.map +1 -0
- package/dist/wallets/groups/store.d.ts +107 -0
- package/dist/wallets/groups/store.d.ts.map +1 -0
- package/dist/wallets/groups/store.js +254 -0
- package/dist/wallets/groups/store.js.map +1 -0
- package/package.json +50 -0
- package/skills/SKILL.md +187 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-house wallet group store.
|
|
3
|
+
*
|
|
4
|
+
* Storage: ~/.fourmm/wallets/wallet-store.json (mode 0600), JSON-encoded.
|
|
5
|
+
* Each wallet's privateKey is AES-encrypted with the master password.
|
|
6
|
+
*
|
|
7
|
+
* File shape:
|
|
8
|
+
* {
|
|
9
|
+
* encrypted: true,
|
|
10
|
+
* passwordCheck: "...", // AES('ALMM_PASSWORD_OK', password)
|
|
11
|
+
* groups: {
|
|
12
|
+
* "1": { groupId, name, wallets: [...], note, createdAt, updatedAt },
|
|
13
|
+
* ...
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* All public functions take the master password as an explicit first argument.
|
|
18
|
+
* There is NO module-level password state: that makes testing easier, hides
|
|
19
|
+
* fewer dependencies, and lets Week-2 code pass different passwords in the
|
|
20
|
+
* same process (e.g. import + decrypt two different vaults).
|
|
21
|
+
*
|
|
22
|
+
* Write path is atomic (write .tmp, rename) to avoid half-written files.
|
|
23
|
+
*/
|
|
24
|
+
import type { Address, Hex } from 'viem';
|
|
25
|
+
/** A single wallet inside a group */
|
|
26
|
+
export type StoredWallet = {
|
|
27
|
+
/** EVM checksummed address */
|
|
28
|
+
address: Address;
|
|
29
|
+
/** AES-encrypted hex private key (with 0x prefix before encryption) */
|
|
30
|
+
encryptedPrivateKey: string;
|
|
31
|
+
/** Free-form note */
|
|
32
|
+
note: string;
|
|
33
|
+
};
|
|
34
|
+
/** A wallet group (collection of wallets under a shared name) */
|
|
35
|
+
export type WalletGroup = {
|
|
36
|
+
/** Numeric group ID (auto-assigned) */
|
|
37
|
+
groupId: number;
|
|
38
|
+
/** Group name */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Free-form description */
|
|
41
|
+
note: string;
|
|
42
|
+
/** Wallets in this group */
|
|
43
|
+
wallets: StoredWallet[];
|
|
44
|
+
/** ISO 8601 timestamps */
|
|
45
|
+
createdAt: string;
|
|
46
|
+
updatedAt: string;
|
|
47
|
+
};
|
|
48
|
+
/** Root file format */
|
|
49
|
+
export type WalletStoreFile = {
|
|
50
|
+
encrypted: true;
|
|
51
|
+
passwordCheck: string;
|
|
52
|
+
groups: Record<number, WalletGroup>;
|
|
53
|
+
};
|
|
54
|
+
/** Group summary (what `list-groups` returns — no wallet contents) */
|
|
55
|
+
export type WalletGroupSummary = Omit<WalletGroup, 'wallets'> & {
|
|
56
|
+
walletCount: number;
|
|
57
|
+
};
|
|
58
|
+
/** Does a wallet store file already exist? */
|
|
59
|
+
export declare function storeExists(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Load the wallet store.
|
|
62
|
+
*
|
|
63
|
+
* - Returns `null` if the store doesn't exist yet. Read-only callers
|
|
64
|
+
* (e.g. `list-groups`) should treat this as "no groups".
|
|
65
|
+
* - Throws if the store exists but the password doesn't match.
|
|
66
|
+
* - **Never writes to disk.** Callers that want to create a new store
|
|
67
|
+
* must call `initStore(password)` explicitly.
|
|
68
|
+
*/
|
|
69
|
+
export declare function loadStore(password: string): WalletStoreFile | null;
|
|
70
|
+
/**
|
|
71
|
+
* Create a new empty wallet store, encrypted with the given password.
|
|
72
|
+
*
|
|
73
|
+
* Throws if a store file already exists. Callers should check `storeExists()`
|
|
74
|
+
* or use `loadOrInitStore()` if they want idempotent creation.
|
|
75
|
+
*/
|
|
76
|
+
export declare function initStore(password: string): WalletStoreFile;
|
|
77
|
+
/**
|
|
78
|
+
* Convenience: load the store, or init an empty one if it doesn't exist.
|
|
79
|
+
* Used by write operations (`createGroup`, etc.).
|
|
80
|
+
*/
|
|
81
|
+
export declare function loadOrInitStore(password: string): WalletStoreFile;
|
|
82
|
+
/** Persist the wallet store atomically */
|
|
83
|
+
export declare function saveStore(store: WalletStoreFile): void;
|
|
84
|
+
/** List all groups (summary form). Returns empty array if no store. */
|
|
85
|
+
export declare function listGroups(password: string): WalletGroupSummary[];
|
|
86
|
+
/** Get a single group by ID. Returns undefined if store or group missing. */
|
|
87
|
+
export declare function getGroup(password: string, groupId: number): WalletGroup | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Create a new wallet group with N freshly-generated wallets.
|
|
90
|
+
* Private keys are encrypted at rest immediately.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createGroup(password: string, options: {
|
|
93
|
+
name: string;
|
|
94
|
+
count: number;
|
|
95
|
+
note?: string | undefined;
|
|
96
|
+
}): WalletGroup;
|
|
97
|
+
/** Add newly-generated wallets to an existing group */
|
|
98
|
+
export declare function addGeneratedWallets(password: string, groupId: number, count: number): StoredWallet[];
|
|
99
|
+
/** Add a wallet from an existing private key */
|
|
100
|
+
export declare function addWalletFromPrivateKey(password: string, groupId: number, privateKey: Hex, note?: string): StoredWallet;
|
|
101
|
+
/** Delete an entire wallet group by ID. */
|
|
102
|
+
export declare function deleteGroup(password: string, groupId: number): void;
|
|
103
|
+
/** Remove a single wallet from a group by address. */
|
|
104
|
+
export declare function removeWalletFromGroup(password: string, groupId: number, address: Address): void;
|
|
105
|
+
/** Decrypt a wallet's private key (only for in-process signing — never log) */
|
|
106
|
+
export declare function decryptPrivateKey(stored: StoredWallet, password: string): Hex;
|
|
107
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/wallets/groups/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAaxC,qCAAqC;AACrC,MAAM,MAAM,YAAY,GAAG;IACzB,8BAA8B;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,uEAAuE;IACvE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACxB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,4BAA4B;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,uBAAuB;AACvB,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,IAAI,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CACpC,CAAA;AAED,sEAAsE;AACtE,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG;IAC9D,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAiCD,8CAA8C;AAC9C,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA0BlE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAkB3D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAEjE;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAEtD;AAMD,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAajE;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,SAAS,CAIzB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,GACA,WAAW,CA0Bb;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,YAAY,EAAE,CAqBhB;AAED,gDAAgD;AAChD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,GAAG,EACf,IAAI,SAAK,GACR,YAAY,CA2Bd;AAED,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAMnE;AAED,sDAAsD;AACtD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,GACf,IAAI,CAcN;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CAQ7E"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-house wallet group store.
|
|
3
|
+
*
|
|
4
|
+
* Storage: ~/.fourmm/wallets/wallet-store.json (mode 0600), JSON-encoded.
|
|
5
|
+
* Each wallet's privateKey is AES-encrypted with the master password.
|
|
6
|
+
*
|
|
7
|
+
* File shape:
|
|
8
|
+
* {
|
|
9
|
+
* encrypted: true,
|
|
10
|
+
* passwordCheck: "...", // AES('ALMM_PASSWORD_OK', password)
|
|
11
|
+
* groups: {
|
|
12
|
+
* "1": { groupId, name, wallets: [...], note, createdAt, updatedAt },
|
|
13
|
+
* ...
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* All public functions take the master password as an explicit first argument.
|
|
18
|
+
* There is NO module-level password state: that makes testing easier, hides
|
|
19
|
+
* fewer dependencies, and lets Week-2 code pass different passwords in the
|
|
20
|
+
* same process (e.g. import + decrypt two different vaults).
|
|
21
|
+
*
|
|
22
|
+
* Write path is atomic (write .tmp, rename) to avoid half-written files.
|
|
23
|
+
*/
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { walletsDir, ensureFourmmDirs } from '../../lib/config.js';
|
|
27
|
+
import { decrypt, encrypt, generatePasswordCheck, verifyPassword, } from './encrypt.js';
|
|
28
|
+
import { generateWallets, walletFromPrivateKey } from './generate.js';
|
|
29
|
+
// ============================================================
|
|
30
|
+
// Paths
|
|
31
|
+
// ============================================================
|
|
32
|
+
const STORE_FILE = 'wallet-store.json';
|
|
33
|
+
function storePath() {
|
|
34
|
+
return path.join(walletsDir(), STORE_FILE);
|
|
35
|
+
}
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Atomic file ops
|
|
38
|
+
// ============================================================
|
|
39
|
+
function atomicWriteJson(filePath, data) {
|
|
40
|
+
const dir = path.dirname(filePath);
|
|
41
|
+
if (!fs.existsSync(dir)) {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
43
|
+
}
|
|
44
|
+
const tmpPath = `${filePath}.tmp`;
|
|
45
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), {
|
|
46
|
+
encoding: 'utf-8',
|
|
47
|
+
mode: 0o600,
|
|
48
|
+
});
|
|
49
|
+
fs.renameSync(tmpPath, filePath);
|
|
50
|
+
}
|
|
51
|
+
// ============================================================
|
|
52
|
+
// Load / init
|
|
53
|
+
// ============================================================
|
|
54
|
+
/** Does a wallet store file already exist? */
|
|
55
|
+
export function storeExists() {
|
|
56
|
+
return fs.existsSync(storePath());
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load the wallet store.
|
|
60
|
+
*
|
|
61
|
+
* - Returns `null` if the store doesn't exist yet. Read-only callers
|
|
62
|
+
* (e.g. `list-groups`) should treat this as "no groups".
|
|
63
|
+
* - Throws if the store exists but the password doesn't match.
|
|
64
|
+
* - **Never writes to disk.** Callers that want to create a new store
|
|
65
|
+
* must call `initStore(password)` explicitly.
|
|
66
|
+
*/
|
|
67
|
+
export function loadStore(password) {
|
|
68
|
+
if (!password)
|
|
69
|
+
throw new Error('loadStore: password is empty');
|
|
70
|
+
const file = storePath();
|
|
71
|
+
if (!fs.existsSync(file))
|
|
72
|
+
return null;
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
76
|
+
parsed = JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error(`Wallet store at ${file} is corrupt (not valid JSON)`);
|
|
80
|
+
}
|
|
81
|
+
if (!parsed.encrypted || !parsed.passwordCheck) {
|
|
82
|
+
throw new Error(`Wallet store at ${file} is missing encrypted/passwordCheck fields`);
|
|
83
|
+
}
|
|
84
|
+
if (!verifyPassword(password, parsed.passwordCheck)) {
|
|
85
|
+
throw new Error('Wrong master password: unable to decrypt wallet store');
|
|
86
|
+
}
|
|
87
|
+
if (!parsed.groups)
|
|
88
|
+
parsed.groups = {};
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a new empty wallet store, encrypted with the given password.
|
|
93
|
+
*
|
|
94
|
+
* Throws if a store file already exists. Callers should check `storeExists()`
|
|
95
|
+
* or use `loadOrInitStore()` if they want idempotent creation.
|
|
96
|
+
*/
|
|
97
|
+
export function initStore(password) {
|
|
98
|
+
if (!password)
|
|
99
|
+
throw new Error('initStore: password is empty');
|
|
100
|
+
ensureFourmmDirs();
|
|
101
|
+
const file = storePath();
|
|
102
|
+
if (fs.existsSync(file)) {
|
|
103
|
+
throw new Error(`Wallet store at ${file} already exists. Refusing to overwrite.`);
|
|
104
|
+
}
|
|
105
|
+
const store = {
|
|
106
|
+
encrypted: true,
|
|
107
|
+
passwordCheck: generatePasswordCheck(password),
|
|
108
|
+
groups: {},
|
|
109
|
+
};
|
|
110
|
+
atomicWriteJson(file, store);
|
|
111
|
+
return store;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convenience: load the store, or init an empty one if it doesn't exist.
|
|
115
|
+
* Used by write operations (`createGroup`, etc.).
|
|
116
|
+
*/
|
|
117
|
+
export function loadOrInitStore(password) {
|
|
118
|
+
return loadStore(password) ?? initStore(password);
|
|
119
|
+
}
|
|
120
|
+
/** Persist the wallet store atomically */
|
|
121
|
+
export function saveStore(store) {
|
|
122
|
+
atomicWriteJson(storePath(), store);
|
|
123
|
+
}
|
|
124
|
+
// ============================================================
|
|
125
|
+
// Group operations
|
|
126
|
+
// ============================================================
|
|
127
|
+
/** List all groups (summary form). Returns empty array if no store. */
|
|
128
|
+
export function listGroups(password) {
|
|
129
|
+
const store = loadStore(password);
|
|
130
|
+
if (!store)
|
|
131
|
+
return [];
|
|
132
|
+
return Object.values(store.groups)
|
|
133
|
+
.sort((a, b) => a.groupId - b.groupId)
|
|
134
|
+
.map((g) => ({
|
|
135
|
+
groupId: g.groupId,
|
|
136
|
+
name: g.name,
|
|
137
|
+
note: g.note,
|
|
138
|
+
walletCount: g.wallets.length,
|
|
139
|
+
createdAt: g.createdAt,
|
|
140
|
+
updatedAt: g.updatedAt,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** Get a single group by ID. Returns undefined if store or group missing. */
|
|
144
|
+
export function getGroup(password, groupId) {
|
|
145
|
+
const store = loadStore(password);
|
|
146
|
+
if (!store)
|
|
147
|
+
return undefined;
|
|
148
|
+
return store.groups[groupId];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create a new wallet group with N freshly-generated wallets.
|
|
152
|
+
* Private keys are encrypted at rest immediately.
|
|
153
|
+
*/
|
|
154
|
+
export function createGroup(password, options) {
|
|
155
|
+
const store = loadOrInitStore(password);
|
|
156
|
+
const nextId = Object.values(store.groups).reduce((max, g) => Math.max(max, g.groupId), 0) +
|
|
157
|
+
1;
|
|
158
|
+
const fresh = generateWallets(options.count);
|
|
159
|
+
const now = new Date().toISOString();
|
|
160
|
+
const group = {
|
|
161
|
+
groupId: nextId,
|
|
162
|
+
name: options.name,
|
|
163
|
+
note: options.note ?? '',
|
|
164
|
+
wallets: fresh.map((w) => ({
|
|
165
|
+
address: w.address,
|
|
166
|
+
encryptedPrivateKey: encrypt(w.privateKey, password),
|
|
167
|
+
note: w.note,
|
|
168
|
+
})),
|
|
169
|
+
createdAt: now,
|
|
170
|
+
updatedAt: now,
|
|
171
|
+
};
|
|
172
|
+
store.groups[nextId] = group;
|
|
173
|
+
saveStore(store);
|
|
174
|
+
return group;
|
|
175
|
+
}
|
|
176
|
+
/** Add newly-generated wallets to an existing group */
|
|
177
|
+
export function addGeneratedWallets(password, groupId, count) {
|
|
178
|
+
const store = loadStore(password);
|
|
179
|
+
if (!store) {
|
|
180
|
+
throw new Error(`No wallet store found. Create a group first with \`fourmm wallet create-group\`.`);
|
|
181
|
+
}
|
|
182
|
+
const group = store.groups[groupId];
|
|
183
|
+
if (!group)
|
|
184
|
+
throw new Error(`Group ${groupId} does not exist`);
|
|
185
|
+
const fresh = generateWallets(count);
|
|
186
|
+
const stored = fresh.map((w) => ({
|
|
187
|
+
address: w.address,
|
|
188
|
+
encryptedPrivateKey: encrypt(w.privateKey, password),
|
|
189
|
+
note: w.note,
|
|
190
|
+
}));
|
|
191
|
+
group.wallets.push(...stored);
|
|
192
|
+
group.updatedAt = new Date().toISOString();
|
|
193
|
+
saveStore(store);
|
|
194
|
+
return stored;
|
|
195
|
+
}
|
|
196
|
+
/** Add a wallet from an existing private key */
|
|
197
|
+
export function addWalletFromPrivateKey(password, groupId, privateKey, note = '') {
|
|
198
|
+
const store = loadStore(password);
|
|
199
|
+
if (!store) {
|
|
200
|
+
throw new Error(`No wallet store found. Create a group first with \`fourmm wallet create-group\`.`);
|
|
201
|
+
}
|
|
202
|
+
const group = store.groups[groupId];
|
|
203
|
+
if (!group)
|
|
204
|
+
throw new Error(`Group ${groupId} does not exist`);
|
|
205
|
+
const w = walletFromPrivateKey(privateKey, note);
|
|
206
|
+
// Dedupe by address
|
|
207
|
+
if (group.wallets.some((existing) => existing.address === w.address)) {
|
|
208
|
+
throw new Error(`Wallet ${w.address} already exists in group ${groupId}`);
|
|
209
|
+
}
|
|
210
|
+
const stored = {
|
|
211
|
+
address: w.address,
|
|
212
|
+
encryptedPrivateKey: encrypt(privateKey, password),
|
|
213
|
+
note: w.note,
|
|
214
|
+
};
|
|
215
|
+
group.wallets.push(stored);
|
|
216
|
+
group.updatedAt = new Date().toISOString();
|
|
217
|
+
saveStore(store);
|
|
218
|
+
return stored;
|
|
219
|
+
}
|
|
220
|
+
/** Delete an entire wallet group by ID. */
|
|
221
|
+
export function deleteGroup(password, groupId) {
|
|
222
|
+
const store = loadStore(password);
|
|
223
|
+
if (!store)
|
|
224
|
+
throw new Error('No wallet store found.');
|
|
225
|
+
if (!store.groups[groupId])
|
|
226
|
+
throw new Error(`Group ${groupId} does not exist`);
|
|
227
|
+
delete store.groups[groupId];
|
|
228
|
+
saveStore(store);
|
|
229
|
+
}
|
|
230
|
+
/** Remove a single wallet from a group by address. */
|
|
231
|
+
export function removeWalletFromGroup(password, groupId, address) {
|
|
232
|
+
const store = loadStore(password);
|
|
233
|
+
if (!store)
|
|
234
|
+
throw new Error('No wallet store found.');
|
|
235
|
+
const group = store.groups[groupId];
|
|
236
|
+
if (!group)
|
|
237
|
+
throw new Error(`Group ${groupId} does not exist`);
|
|
238
|
+
const before = group.wallets.length;
|
|
239
|
+
group.wallets = group.wallets.filter((w) => w.address.toLowerCase() !== address.toLowerCase());
|
|
240
|
+
if (group.wallets.length === before) {
|
|
241
|
+
throw new Error(`Address ${address} not found in group ${groupId}`);
|
|
242
|
+
}
|
|
243
|
+
group.updatedAt = new Date().toISOString();
|
|
244
|
+
saveStore(store);
|
|
245
|
+
}
|
|
246
|
+
/** Decrypt a wallet's private key (only for in-process signing — never log) */
|
|
247
|
+
export function decryptPrivateKey(stored, password) {
|
|
248
|
+
const pk = decrypt(stored.encryptedPrivateKey, password);
|
|
249
|
+
if (!pk.startsWith('0x')) {
|
|
250
|
+
throw new Error(`Decrypted private key for ${stored.address} is malformed (missing 0x prefix)`);
|
|
251
|
+
}
|
|
252
|
+
return pk;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/wallets/groups/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAElE,OAAO,EACL,OAAO,EACP,OAAO,EACP,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AA2CrE,+DAA+D;AAC/D,QAAQ;AACR,+DAA+D;AAE/D,MAAM,UAAU,GAAG,mBAAmB,CAAA;AAEtC,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,UAAU,CAAC,CAAA;AAC5C,CAAC;AAED,+DAA+D;AAC/D,kBAAkB;AAClB,+DAA+D;AAE/D,SAAS,eAAe,CAAC,QAAgB,EAAE,IAAa;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAA;IACjC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACvD,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAA;IACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AAClC,CAAC;AAED,+DAA+D;AAC/D,cAAc;AACd,+DAA+D;AAE/D,8CAA8C;AAC9C,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAA;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IAE9D,MAAM,IAAI,GAAG,SAAS,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,IAAI,MAAuB,CAAA;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1C,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,8BAA8B,CAAC,CAAA;IACxE,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,4CAA4C,CACpE,CAAA;IACH,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;IAC1E,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,MAAM,CAAC,MAAM,GAAG,EAAE,CAAA;IACtC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IAE9D,gBAAgB,EAAE,CAAA;IAClB,MAAM,IAAI,GAAG,SAAS,EAAE,CAAA;IACxB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,mBAAmB,IAAI,yCAAyC,CACjE,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAoB;QAC7B,SAAS,EAAE,IAAI;QACf,aAAa,EAAE,qBAAqB,CAAC,QAAQ,CAAC;QAC9C,MAAM,EAAE,EAAE;KACX,CAAA;IACD,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC5B,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAA;AACnD,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,SAAS,CAAC,KAAsB;IAC9C,eAAe,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,CAAA;AACrC,CAAC;AAED,+DAA+D;AAC/D,mBAAmB;AACnB,+DAA+D;AAE/D,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;SAC/B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM;QAC7B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS;KACvB,CAAC,CAAC,CAAA;AACP,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,QAAQ,CACtB,QAAgB,EAChB,OAAe;IAEf,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,QAAgB,EAChB,OAIC;IAED,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;IAEvC,MAAM,MAAM,GACV,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3E,CAAC,CAAA;IAEH,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAEpC,MAAM,KAAK,GAAgB;QACzB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;QACxB,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,mBAAmB,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC;YACpD,IAAI,EAAE,CAAC,CAAC,IAAI;SACb,CAAC,CAAC;QACH,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAA;IAED,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;IAC5B,SAAS,CAAC,KAAK,CAAC,CAAA;IAChB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,OAAe,EACf,KAAa;IAEb,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAA;IACH,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,iBAAiB,CAAC,CAAA;IAE9D,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACpC,MAAM,MAAM,GAAmB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,mBAAmB,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC;QACpD,IAAI,EAAE,CAAC,CAAC,IAAI;KACb,CAAC,CAAC,CAAA;IAEH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAA;IAC7B,KAAK,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC1C,SAAS,CAAC,KAAK,CAAC,CAAA;IAChB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,uBAAuB,CACrC,QAAgB,EAChB,OAAe,EACf,UAAe,EACf,IAAI,GAAG,EAAE;IAET,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAA;IACH,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,iBAAiB,CAAC,CAAA;IAE9D,MAAM,CAAC,GAAG,oBAAoB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IAEhD,oBAAoB;IACpB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,4BAA4B,OAAO,EAAE,CAAC,CAAA;IAC3E,CAAC;IAED,MAAM,MAAM,GAAiB;QAC3B,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,mBAAmB,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC;QAClD,IAAI,EAAE,CAAC,CAAC,IAAI;KACb,CAAA;IAED,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC1B,KAAK,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC1C,SAAS,CAAC,KAAK,CAAC,CAAA;IAChB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,OAAe;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;IACrD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,iBAAiB,CAAC,CAAA;IAC9E,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC5B,SAAS,CAAC,KAAK,CAAC,CAAA;AAClB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,OAAe,EACf,OAAgB;IAEhB,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,iBAAiB,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAA;IACnC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CACzD,CAAA;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,uBAAuB,OAAO,EAAE,CAAC,CAAA;IACrE,CAAC;IACD,KAAK,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC1C,SAAS,CAAC,KAAK,CAAC,CAAA;AAClB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAAC,MAAoB,EAAE,QAAgB;IACtE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAA;IACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,6BAA6B,MAAM,CAAC,OAAO,mCAAmC,CAC/E,CAAA;IACH,CAAC;IACD,OAAO,EAAS,CAAA;AAClB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fourmm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "fourMM — agent-first CLI for Four.meme market making on BSC",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "linwe",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"bsc",
|
|
10
|
+
"four.meme",
|
|
11
|
+
"market-making",
|
|
12
|
+
"agent",
|
|
13
|
+
"cli",
|
|
14
|
+
"defi",
|
|
15
|
+
"meme"
|
|
16
|
+
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"fourmm": "./dist/bin.js"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/cli.js",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"skills",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"crypto-js": "^4.2.0",
|
|
31
|
+
"incur": "^0.3.25",
|
|
32
|
+
"viem": "^2.21.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/crypto-js": "^4.2.2",
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"tsx": "^4.19.0",
|
|
38
|
+
"typescript": "^5.6.0",
|
|
39
|
+
"vitest": "^2.1.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc",
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"dev": "tsx src/bin.ts",
|
|
45
|
+
"fourmm": "tsx src/bin.ts",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"typecheck": "tsc --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/skills/SKILL.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fourmm
|
|
3
|
+
description: fourMM — Agent-first market-making CLI for Four.meme on BSC. Create tokens, trade (buy/sell/sniper), pump/dump via Flashbot bundles, generate volume, manage wallets, transfer funds.
|
|
4
|
+
requires_bin: fourmm
|
|
5
|
+
install: pnpm install && pnpm build && npx fourmm skills add
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# fourMM
|
|
9
|
+
|
|
10
|
+
Market-making CLI for [Four.meme](https://four.meme) on BNB Smart Chain.
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
# Clone & build
|
|
16
|
+
git clone <repo-url> && cd fourMM
|
|
17
|
+
pnpm install && pnpm build
|
|
18
|
+
|
|
19
|
+
# Install skills for your agent
|
|
20
|
+
npx fourmm skills add
|
|
21
|
+
|
|
22
|
+
# Or start as MCP server
|
|
23
|
+
npx fourmm --mcp
|
|
24
|
+
|
|
25
|
+
# Initialize config (BlockRazor MEV-protected RPC)
|
|
26
|
+
fourmm config init
|
|
27
|
+
|
|
28
|
+
# Create wallets
|
|
29
|
+
fourmm wallet create-group --name main --count 5
|
|
30
|
+
|
|
31
|
+
# Fund wallets
|
|
32
|
+
fourmm transfer out --from <your-funded-wallet> --to-group 1 --value 0.01
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Environment:** Set `FOURMM_PASSWORD` to avoid `--password` on every command.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Intent Routing
|
|
40
|
+
|
|
41
|
+
### I want to create a token
|
|
42
|
+
```sh
|
|
43
|
+
fourmm token create --name "Name" --symbol "SYM" --image logo.png --dev-wallet <groupId> [--preset-buy 0.01]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### I want to buy tokens
|
|
47
|
+
```sh
|
|
48
|
+
# All wallets in group buy the same amount
|
|
49
|
+
fourmm trade buy --group <id> --token <CA> --amount <BNB> [--slippage 500]
|
|
50
|
+
|
|
51
|
+
# Each wallet buys a different amount
|
|
52
|
+
fourmm trade sniper --group <id> --token <CA> --amounts "0.01,0.02,0.05"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### I want to sell tokens
|
|
56
|
+
```sh
|
|
57
|
+
# Sell everything
|
|
58
|
+
fourmm trade sell --group <id> --token <CA> --amount all
|
|
59
|
+
|
|
60
|
+
# Sell 50%
|
|
61
|
+
fourmm trade sell --group <id> --token <CA> --amount 50%
|
|
62
|
+
|
|
63
|
+
# Sell fixed amount
|
|
64
|
+
fourmm trade sell --group <id> --token <CA> --amount 1000000
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### I want to pump the price
|
|
68
|
+
```sh
|
|
69
|
+
fourmm tools robot-price --group <id> --token <CA> --direction up --target-price <BNB_PER_TOKEN> --amount <BNB_PER_ROUND>
|
|
70
|
+
```
|
|
71
|
+
All wallets' buys are bundled via Flashbot (`eth_sendBundle`) — atomic, same block, no MEV.
|
|
72
|
+
|
|
73
|
+
### I want to dump the price
|
|
74
|
+
```sh
|
|
75
|
+
fourmm tools robot-price --group <id> --token <CA> --direction down --target-price <BNB_PER_TOKEN> --amount <BNB_PER_ROUND>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### I want to generate volume
|
|
79
|
+
```sh
|
|
80
|
+
# Atomic buy+sell per wallet (zero net position, generates on-chain volume)
|
|
81
|
+
fourmm tools volume --group <id> --token <CA> --amount <BNB> --rounds 10
|
|
82
|
+
|
|
83
|
+
# Single atomic round-trip per wallet
|
|
84
|
+
fourmm trade batch --group <id> --token <CA> --amount <BNB>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### I want to distribute holder count
|
|
88
|
+
```sh
|
|
89
|
+
# Group A pays BNB, Group B receives tokens (via Router.turnover)
|
|
90
|
+
fourmm tools turnover --from-group <A> --to-group <B> --token <CA> --amount <BNB>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### I want to check my positions
|
|
94
|
+
```sh
|
|
95
|
+
fourmm query monitor --group <id> --token <CA> # Holdings + PnL
|
|
96
|
+
fourmm query balance --address <ADDR> # BNB balance
|
|
97
|
+
fourmm query price --token <CA> # Live price
|
|
98
|
+
fourmm query transactions --group <id> # Trade history
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### I want to manage wallets
|
|
102
|
+
```sh
|
|
103
|
+
fourmm wallet create-group --name <NAME> --count <N> # Create group
|
|
104
|
+
fourmm wallet list-groups # List all groups
|
|
105
|
+
fourmm wallet group-info --id <ID> # View wallets
|
|
106
|
+
fourmm wallet generate --group <ID> --count <N> # Add wallets
|
|
107
|
+
fourmm wallet add --group <ID> --private-key <KEY> # Import key
|
|
108
|
+
fourmm wallet remove --group <ID> --address <ADDR> # Remove wallet
|
|
109
|
+
fourmm wallet import --group <ID> --file wallets.csv # Bulk import
|
|
110
|
+
fourmm wallet export --group <ID> --file out.csv # Export keys
|
|
111
|
+
fourmm wallet delete-group --id <ID> --force # Delete group
|
|
112
|
+
fourmm wallet overview # Aggregate PnL
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### I want to move funds
|
|
116
|
+
```sh
|
|
117
|
+
fourmm transfer out --from <ADDR> --to-group <ID> --value <BNB> # Distribute
|
|
118
|
+
fourmm transfer in --to <ADDR> --from-group <ID> --amount all # Collect all
|
|
119
|
+
fourmm transfer in --to <ADDR> --from-group <ID> --amount reserve --value 0.002 # Keep reserve
|
|
120
|
+
fourmm transfer many-to-many --from-group <A> --to-group <B> # Pair transfer
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### I want to check a token
|
|
124
|
+
```sh
|
|
125
|
+
fourmm token info --ca <CA> # Full on-chain metadata
|
|
126
|
+
fourmm token identify --ca <CA> # Variant: standard / anti-sniper / tax / x-mode
|
|
127
|
+
fourmm token graduate-status --ca <CA> # Graduation progress
|
|
128
|
+
fourmm token pool --ca <CA> # Pool / liquidity info
|
|
129
|
+
fourmm query kline --token <CA> # K-line candles (graduated only)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### I want to change config
|
|
133
|
+
```sh
|
|
134
|
+
fourmm config get # View all
|
|
135
|
+
fourmm config get --key rpcUrl # View one
|
|
136
|
+
fourmm config set rpcUrl <URL> # Change RPC
|
|
137
|
+
fourmm config set defaultSlippageBps 500 # Change slippage
|
|
138
|
+
fourmm config init # Reset to defaults
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Key Concepts
|
|
144
|
+
|
|
145
|
+
**Wallet Groups:** Wallets are organized in numbered groups (1, 2, 3...). Each group has N wallets. Trade/volume/transfer commands operate on entire groups.
|
|
146
|
+
|
|
147
|
+
**Bonding Curve vs PancakeSwap:** Tokens start on Four.meme's bonding curve. After raising 18 BNB, they "graduate" to PancakeSwap. fourMM auto-detects and routes accordingly.
|
|
148
|
+
|
|
149
|
+
**Token Variants:**
|
|
150
|
+
- `standard` — normal token, fully supported
|
|
151
|
+
- `anti-sniper-fee` — dynamic fee decreasing block-by-block, supported (use higher slippage)
|
|
152
|
+
- `tax-token` — fourMM refuses (round-trip loss too high)
|
|
153
|
+
- `x-mode` — fourMM refuses (special buy interface)
|
|
154
|
+
|
|
155
|
+
**Slippage:** In basis points. 300 = 3%, 500 = 5%, 1500 = 15%. Higher = more tolerance for price movement.
|
|
156
|
+
|
|
157
|
+
**Flashbot Bundle:** `robot-price` signs all wallets' txs offline and submits via `eth_sendBundle`. All execute in the same block or none do. No public mempool exposure.
|
|
158
|
+
|
|
159
|
+
**Router Contract:** `0xd62c2fd94176f98424af83e4b9a333d454b2216c` — atomic `volume()` (buy+sell), `turnover()` (buy for another wallet), `volumePancake()` (graduated tokens).
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Common Workflows
|
|
164
|
+
|
|
165
|
+
### Launch + Market Make
|
|
166
|
+
```sh
|
|
167
|
+
fourmm wallet create-group --name launch --count 10
|
|
168
|
+
fourmm transfer out --from <funded-wallet> --to-group 1 --value 0.05
|
|
169
|
+
fourmm token create --name "My Token" --symbol "MTK" --image logo.png --dev-wallet 1 --preset-buy 0.01
|
|
170
|
+
# Wait for CA in output, then:
|
|
171
|
+
fourmm tools volume --group 1 --token <CA> --amount 0.01 --rounds 20
|
|
172
|
+
fourmm tools robot-price --group 1 --token <CA> --direction up --target-price 0.00001 --amount 0.01
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Snipe + Dump
|
|
176
|
+
```sh
|
|
177
|
+
fourmm trade sniper --group 1 --token <CA> --amounts "0.05,0.03,0.02,0.01,0.01"
|
|
178
|
+
fourmm query monitor --group 1 --token <CA>
|
|
179
|
+
# When ready:
|
|
180
|
+
fourmm trade sell --group 1 --token <CA> --amount all
|
|
181
|
+
fourmm transfer in --to <your-wallet> --from-group 1 --amount all
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Volume Bot
|
|
185
|
+
```sh
|
|
186
|
+
fourmm tools volume --group 1 --token <CA> --amount 0.01 --rounds 50 --interval 3000
|
|
187
|
+
```
|