@nullpay/mcp 1.0.0 → 1.0.3

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/server.js CHANGED
@@ -1,59 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const fs_1 = __importDefault(require("fs"));
7
- const path_1 = __importDefault(require("path"));
3
+ exports.startServer = startServer;
8
4
  const backend_client_1 = require("./backend-client");
5
+ const env_1 = require("./env");
9
6
  const protocol_1 = require("./protocol");
10
7
  const session_store_1 = require("./session-store");
11
8
  const service_1 = require("./service");
12
- function parseDotEnv(content) {
13
- const parsed = {};
14
- for (const rawLine of content.split(/\r?\n/)) {
15
- const line = rawLine.trim();
16
- if (!line || line.startsWith('#')) {
17
- continue;
18
- }
19
- const normalized = line.startsWith('export ') ? line.slice(7).trim() : line;
20
- const separatorIndex = normalized.indexOf('=');
21
- if (separatorIndex <= 0) {
22
- continue;
23
- }
24
- const key = normalized.slice(0, separatorIndex).trim();
25
- let value = normalized.slice(separatorIndex + 1).trim();
26
- if (!key) {
27
- continue;
28
- }
29
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
30
- value = value.slice(1, -1);
31
- }
32
- parsed[key] = value;
33
- }
34
- return parsed;
35
- }
36
- function loadEnvFiles() {
37
- const packageRoot = path_1.default.resolve(__dirname, '..');
38
- const repoRoot = path_1.default.resolve(packageRoot, '..', '..');
39
- const candidates = [
40
- path_1.default.resolve(process.cwd(), '.env'),
41
- path_1.default.resolve(packageRoot, '.env'),
42
- path_1.default.resolve(repoRoot, '.env'),
43
- path_1.default.resolve(repoRoot, 'backend', '.env'),
44
- ];
45
- for (const filePath of candidates) {
46
- if (!fs_1.default.existsSync(filePath)) {
47
- continue;
48
- }
49
- const values = parseDotEnv(fs_1.default.readFileSync(filePath, 'utf8'));
50
- for (const [key, value] of Object.entries(values)) {
51
- if (!process.env[key]) {
52
- process.env[key] = value;
53
- }
54
- }
55
- }
56
- }
57
9
  function shieldStdoutForMcp() {
58
10
  const protocolWrite = process.stdout.write.bind(process.stdout);
59
11
  globalThis.__nullpayMcpStdoutWrite = (chunk) => protocolWrite(chunk);
@@ -73,39 +25,38 @@ function shieldStdoutForMcp() {
73
25
  return true;
74
26
  });
75
27
  }
76
- loadEnvFiles();
77
- shieldStdoutForMcp();
78
- const backendBaseUrl = process.env.NULLPAY_BACKEND_URL || 'http://localhost:3000/api';
79
- const publicBaseUrl = process.env.NULLPAY_PUBLIC_BASE_URL || 'https://nullpay.app';
80
- const mcpSecret = process.env.NULLPAY_MCP_SHARED_SECRET;
81
- const backend = new backend_client_1.NullPayBackendClient(backendBaseUrl, mcpSecret);
82
- const sessions = new session_store_1.SessionStore();
83
- const service = new service_1.NullPayMcpService(backend, sessions, publicBaseUrl);
84
- const server = new protocol_1.StdioJsonRpcServer(async (request) => {
85
- if (request.method === 'initialize') {
86
- return {
87
- protocolVersion: String(request.params?.protocolVersion || '2025-11-25'),
88
- capabilities: {
89
- tools: {}
90
- },
91
- serverInfo: {
92
- name: 'nullpay-mcp',
93
- version: '0.1.0'
94
- }
95
- };
96
- }
97
- if (request.method === 'notifications/initialized') {
28
+ function startServer() {
29
+ shieldStdoutForMcp();
30
+ const { backendBaseUrl, publicBaseUrl } = (0, env_1.getRuntimeConfig)();
31
+ const backend = new backend_client_1.NullPayBackendClient(backendBaseUrl);
32
+ const sessions = new session_store_1.SessionStore();
33
+ const service = new service_1.NullPayMcpService(backend, sessions, publicBaseUrl);
34
+ const server = new protocol_1.StdioJsonRpcServer(async (request) => {
35
+ if (request.method === 'initialize') {
36
+ return {
37
+ protocolVersion: String(request.params?.protocolVersion || '2025-11-25'),
38
+ capabilities: {
39
+ tools: {}
40
+ },
41
+ serverInfo: {
42
+ name: 'nullpay-mcp',
43
+ version: '0.2.0'
44
+ }
45
+ };
46
+ }
47
+ if (request.method === 'notifications/initialized') {
48
+ return {};
49
+ }
50
+ if (request.method === 'tools/list') {
51
+ return { tools: service.listTools() };
52
+ }
53
+ if (request.method === 'tools/call') {
54
+ const name = String(request.params?.name || '');
55
+ const args = (request.params?.arguments || {});
56
+ return await service.callTool(name, args);
57
+ }
98
58
  return {};
99
- }
100
- if (request.method === 'tools/list') {
101
- return { tools: service.listTools() };
102
- }
103
- if (request.method === 'tools/call') {
104
- const name = String(request.params?.name || '');
105
- const args = (request.params?.arguments || {});
106
- return await service.callTool(name, args);
107
- }
108
- return {};
109
- });
110
- console.error('nullpay mcp starting');
111
- server.start();
59
+ });
60
+ console.error(`nullpay mcp starting against ${backendBaseUrl}`);
61
+ server.start();
62
+ }
package/dist/service.js CHANGED
@@ -142,7 +142,7 @@ class NullPayMcpService {
142
142
  return [
143
143
  {
144
144
  name: 'login',
145
- description: 'Login to NullPay, validate password, create burner wallet, or switch active wallet. If NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD are configured, call this tool with empty arguments and do not ask the user to share secrets in chat. The MCP server can also read NULLPAY_MAIN_PRIVATE_KEY from env without exposing it to the model.',
145
+ description: 'Login to NullPay, validate password, create burner wallet, recover a backed-up password or burner wallet from on-chain records, or switch active wallet. If NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD are configured, call this tool with empty arguments and do not ask the user to share secrets in chat. The MCP server can also read NULLPAY_MAIN_PRIVATE_KEY from env without exposing it to the model.',
146
146
  inputSchema: {
147
147
  type: 'object',
148
148
  properties: {
@@ -222,21 +222,70 @@ class NullPayMcpService {
222
222
  async login(args) {
223
223
  const envMain = getMainWalletEnv();
224
224
  const address = (args.address || envMain.address || '').trim();
225
- const password = args.password || envMain.password || '';
225
+ let password = args.password || envMain.password || '';
226
226
  const mainPrivateKey = args.main_private_key || envMain.privateKey || null;
227
- if (!address || !password) {
228
- throw new Error('Address and password are required. You can pass them directly or set NULLPAY_MAIN_ADDRESS and NULLPAY_MAIN_PASSWORD in env.');
227
+ if (!address) {
228
+ throw new Error('Address is required. You can pass it directly or set NULLPAY_MAIN_ADDRESS in env.');
229
229
  }
230
230
  const addressHash = (0, crypto_1.hashAddress)(address);
231
231
  const existingProfile = await this.backend.getUserProfile(addressHash);
232
232
  let encryptedMainAddress = existingProfile?.main_address || null;
233
+ let recoveredBackupSource = null;
234
+ let recoveredBurnerAddress = null;
235
+ let recoveredEncryptedBurnerKey = null;
236
+ let usedRecoveredPassword = false;
237
+ let restoredBurnerFromChain = false;
238
+ const attemptRecovery = async () => {
239
+ if (!mainPrivateKey) {
240
+ return false;
241
+ }
242
+ const recovered = await (0, aleo_1.recoverOnChainWalletBackup)(mainPrivateKey, address);
243
+ if (!recovered?.password) {
244
+ return false;
245
+ }
246
+ password = recovered.password;
247
+ recoveredBackupSource = recovered.source;
248
+ recoveredBurnerAddress = recovered.burnerAddress || null;
249
+ recoveredEncryptedBurnerKey = recovered.encryptedBurnerKey || null;
250
+ usedRecoveredPassword = true;
251
+ return true;
252
+ };
233
253
  if (encryptedMainAddress) {
234
- const decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
254
+ if (!password) {
255
+ const recovered = await attemptRecovery();
256
+ if (!recovered) {
257
+ throw new Error('Password is required for this NullPay account. Set NULLPAY_MAIN_PASSWORD, pass password directly, or provide NULLPAY_MAIN_PRIVATE_KEY so the MCP can recover a backed-up password from on-chain records.');
258
+ }
259
+ }
260
+ let decrypted;
261
+ try {
262
+ decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
263
+ }
264
+ catch {
265
+ const recovered = await attemptRecovery();
266
+ if (!recovered) {
267
+ throw new Error('Password is incorrect for this NullPay account, and no recoverable on-chain password backup was found for the provided main private key.');
268
+ }
269
+ decrypted = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
270
+ }
235
271
  if (decrypted !== address) {
236
- throw new Error('Password is incorrect for this NullPay account.');
272
+ const recovered = await attemptRecovery();
273
+ if (!recovered) {
274
+ throw new Error('Password is incorrect for this NullPay account.');
275
+ }
276
+ const recoveredAddress = await (0, crypto_1.decryptWithPassword)(encryptedMainAddress, password);
277
+ if (recoveredAddress !== address) {
278
+ throw new Error('Recovered password does not match the provided address.');
279
+ }
237
280
  }
238
281
  }
239
282
  else {
283
+ if (!password) {
284
+ const recovered = await attemptRecovery();
285
+ if (!recovered) {
286
+ throw new Error('Password is required to create a new NullPay account unless the MCP can recover it from on-chain backup records using NULLPAY_MAIN_PRIVATE_KEY.');
287
+ }
288
+ }
240
289
  encryptedMainAddress = await (0, crypto_1.encryptWithPassword)(address, password);
241
290
  }
242
291
  if (!encryptedMainAddress) {
@@ -246,7 +295,18 @@ class NullPayMcpService {
246
295
  let encryptedBurnerKey = existingProfile?.encrypted_burner_key || null;
247
296
  let burnerAddress = null;
248
297
  if (encryptedBurnerAddress) {
249
- burnerAddress = await (0, crypto_1.decryptWithPassword)(encryptedBurnerAddress, password);
298
+ try {
299
+ burnerAddress = await (0, crypto_1.decryptWithPassword)(encryptedBurnerAddress, password);
300
+ }
301
+ catch {
302
+ burnerAddress = null;
303
+ }
304
+ }
305
+ if ((!encryptedBurnerAddress || !encryptedBurnerKey || !burnerAddress) && recoveredBurnerAddress && recoveredEncryptedBurnerKey) {
306
+ burnerAddress = recoveredBurnerAddress;
307
+ encryptedBurnerAddress = await (0, crypto_1.encryptWithPassword)(recoveredBurnerAddress, password);
308
+ encryptedBurnerKey = recoveredEncryptedBurnerKey;
309
+ restoredBurnerFromChain = true;
250
310
  }
251
311
  if (args.create_burner_wallet && !encryptedBurnerKey) {
252
312
  const { PrivateKey } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
@@ -282,11 +342,17 @@ class NullPayMcpService {
282
342
  });
283
343
  const usedEnvCredentials = Boolean(envMain.address && envMain.password && !args.address && !args.password);
284
344
  const lines = [
285
- usedEnvCredentials ? 'Used main-wallet address and password from MCP env.' : `Logged in as ${address}.`,
345
+ usedEnvCredentials ? 'Used main-wallet address and password from MCP env.' : 'Logged in as ' + address + '.',
286
346
  encryptedBurnerKey
287
- ? `Active wallet: ${preferredWallet}. Burner wallet is available${burnerAddress ? ` at ${burnerAddress}` : ''}.`
347
+ ? 'Active wallet: ' + preferredWallet + '. Burner wallet is available' + (burnerAddress ? ' at ' + burnerAddress : '') + '.'
288
348
  : 'Active wallet: main. No burner wallet is stored yet.',
289
349
  ];
350
+ if (usedRecoveredPassword) {
351
+ lines.push('Recovered your NullPay password from on-chain backup records using the main wallet private key (' + (recoveredBackupSource === 'full_burner' ? 'full burner backup' : 'password backup') + ').');
352
+ }
353
+ if (restoredBurnerFromChain) {
354
+ lines.push('Recovered your backed-up burner wallet from on-chain records and restored it into the MCP session.');
355
+ }
290
356
  if (mainPrivateKey) {
291
357
  lines.push('Main wallet private key is available for record-backed amount lookup and main-wallet payments. Active wallet is set to main by default, and you can switch to burner anytime by logging in again with wallet_preference set to burner. Invoice lookup will prefer the main wallet records even when you pay from burner.');
292
358
  }
@@ -306,6 +372,9 @@ class NullPayMcpService {
306
372
  has_main_private_key: Boolean(mainPrivateKey),
307
373
  main_private_key_from_env: Boolean(envMain.privateKey),
308
374
  used_env_credentials: usedEnvCredentials,
375
+ used_recovered_password: usedRecoveredPassword,
376
+ recovery_source: recoveredBackupSource,
377
+ restored_burner_from_chain: restoredBurnerFromChain,
309
378
  },
310
379
  };
311
380
  }
@@ -0,0 +1 @@
1
+ export declare function runSetupWizard(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runSetupWizard = runSetupWizard;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const promises_1 = __importDefault(require("readline/promises"));
11
+ const process_1 = require("process");
12
+ function getClaudeCodeConfigPath() {
13
+ return path_1.default.join(os_1.default.homedir(), '.claude.json');
14
+ }
15
+ function getClaudeDesktopConfigPath() {
16
+ if (process.platform === 'win32') {
17
+ const localAppData = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
18
+ const packagedClaudePath = path_1.default.join(localAppData, 'Packages', 'Claude_pzs8sxrjxfjjc', 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json');
19
+ if (fs_1.default.existsSync(packagedClaudePath) || fs_1.default.existsSync(path_1.default.dirname(packagedClaudePath))) {
20
+ return packagedClaudePath;
21
+ }
22
+ }
23
+ if (process.platform === 'win32') {
24
+ const appData = process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming');
25
+ return path_1.default.join(appData, 'Claude', 'claude_desktop_config.json');
26
+ }
27
+ if (process.platform === 'darwin') {
28
+ return path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
29
+ }
30
+ return path_1.default.join(os_1.default.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
31
+ }
32
+ function buildServerEntry(answers) {
33
+ const env = {
34
+ NULLPAY_MAIN_ADDRESS: answers.address,
35
+ NULLPAY_MAIN_PRIVATE_KEY: answers.privateKey,
36
+ NULLPAY_MAIN_PASSWORD: answers.password,
37
+ };
38
+ if (process.platform === 'win32') {
39
+ return {
40
+ command: 'cmd',
41
+ args: ['/c', 'npx', '-y', '@nullpay/mcp', 'server'],
42
+ env,
43
+ };
44
+ }
45
+ return {
46
+ command: 'npx',
47
+ args: ['-y', '@nullpay/mcp', 'server'],
48
+ env,
49
+ };
50
+ }
51
+ function readJsonConfig(configPath) {
52
+ if (!fs_1.default.existsSync(configPath)) {
53
+ return {};
54
+ }
55
+ const raw = fs_1.default.readFileSync(configPath, 'utf8').trim();
56
+ if (!raw) {
57
+ return {};
58
+ }
59
+ const parsed = JSON.parse(raw);
60
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
61
+ throw new Error(`Config at ${configPath} is not a JSON object.`);
62
+ }
63
+ return parsed;
64
+ }
65
+ function writeMcpConfig(configPath, serverName, entry) {
66
+ const current = readJsonConfig(configPath);
67
+ const currentMcpServers = current.mcpServers && typeof current.mcpServers === 'object' && !Array.isArray(current.mcpServers)
68
+ ? current.mcpServers
69
+ : {};
70
+ const next = {
71
+ ...current,
72
+ mcpServers: {
73
+ ...currentMcpServers,
74
+ [serverName]: entry,
75
+ },
76
+ };
77
+ fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
78
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
79
+ }
80
+ async function askChoice(rl) {
81
+ process_1.stdout.write('Where do you want to install NullPay MCP?\n');
82
+ process_1.stdout.write('1. Claude Code\n');
83
+ process_1.stdout.write('2. Claude Desktop\n');
84
+ process_1.stdout.write('3. Cancel\n');
85
+ while (true) {
86
+ const answer = (await rl.question('Choose 1, 2, or 3: ')).trim();
87
+ if (answer === '1')
88
+ return 'claude-code';
89
+ if (answer === '2')
90
+ return 'claude-desktop';
91
+ if (answer === '3')
92
+ return null;
93
+ process_1.stdout.write('Please enter 1, 2, or 3.\n');
94
+ }
95
+ }
96
+ async function askRequired(rl, label) {
97
+ while (true) {
98
+ const answer = (await rl.question(`${label}: `)).trim();
99
+ if (answer) {
100
+ return answer;
101
+ }
102
+ process_1.stdout.write(`${label} is required.\n`);
103
+ }
104
+ }
105
+ async function collectAnswers(rl) {
106
+ process_1.stdout.write('\nNullPay will configure Claude automatically. You only need to provide your wallet credentials here.\n\n');
107
+ const address = await askRequired(rl, 'Main wallet address');
108
+ const privateKey = await askRequired(rl, 'Main wallet private key');
109
+ const password = await askRequired(rl, 'NullPay password');
110
+ return {
111
+ address,
112
+ privateKey,
113
+ password,
114
+ };
115
+ }
116
+ async function runSetupWizard() {
117
+ const rl = promises_1.default.createInterface({ input: process_1.stdin, output: process_1.stdout });
118
+ try {
119
+ process_1.stdout.write('NullPay MCP setup\n\n');
120
+ const choice = await askChoice(rl);
121
+ if (!choice) {
122
+ process_1.stdout.write('Setup cancelled.\n');
123
+ return;
124
+ }
125
+ const answers = await collectAnswers(rl);
126
+ const entry = buildServerEntry(answers);
127
+ const configPath = choice === 'claude-code' ? getClaudeCodeConfigPath() : getClaudeDesktopConfigPath();
128
+ writeMcpConfig(configPath, 'nullpay', entry);
129
+ process_1.stdout.write(`\nNullPay MCP was added to ${configPath}\n`);
130
+ if (choice === 'claude-code') {
131
+ process_1.stdout.write('Next step: restart Claude Code or run `claude mcp list` to confirm the server is registered.\n');
132
+ }
133
+ else {
134
+ process_1.stdout.write('Next step: fully restart Claude Desktop so it reloads the new MCP config.\n');
135
+ }
136
+ }
137
+ finally {
138
+ rl.close();
139
+ }
140
+ }
@@ -1,3 +1,3 @@
1
- export declare function hashAddress(address: string): string;
2
- export declare function encryptWithPassword(text: string, password: string): Promise<string>;
3
- export declare function decryptWithPassword(payload: string, password: string): Promise<string>;
1
+ export declare function hashAddress(address: string): string;
2
+ export declare function encryptWithPassword(text: string, password: string): Promise<string>;
3
+ export declare function decryptWithPassword(payload: string, password: string): Promise<string>;
@@ -1,66 +1,66 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.hashAddress = hashAddress;
7
- exports.encryptWithPassword = encryptWithPassword;
8
- exports.decryptWithPassword = decryptWithPassword;
9
- const crypto_1 = __importDefault(require("crypto"));
10
- const ITERATIONS = 100000;
11
- const KEY_LENGTH = 32;
12
- const DIGEST = 'sha256';
13
- function toBase64(buffer) {
14
- return Buffer.from(buffer).toString('base64');
15
- }
16
- function fromBase64(value) {
17
- return Buffer.from(value, 'base64');
18
- }
19
- function hashAddress(address) {
20
- return crypto_1.default.createHash('sha256').update(address).digest('hex');
21
- }
22
- async function encryptWithPassword(text, password) {
23
- const salt = crypto_1.default.randomBytes(16);
24
- const iv = crypto_1.default.randomBytes(12);
25
- const key = await new Promise((resolve, reject) => {
26
- crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
27
- if (error)
28
- reject(error);
29
- else
30
- resolve(derivedKey);
31
- });
32
- });
33
- const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
34
- const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
35
- const tag = cipher.getAuthTag();
36
- return `${toBase64(salt)}:${toBase64(iv)}:${toBase64(Buffer.concat([ciphertext, tag]))}`;
37
- }
38
- async function decryptWithPassword(payload, password) {
39
- const parts = payload.split(':');
40
- if (parts.length !== 3) {
41
- throw new Error('Invalid encrypted payload format');
42
- }
43
- const [saltPart, ivPart, cipherPart] = parts;
44
- const salt = fromBase64(saltPart);
45
- const iv = fromBase64(ivPart);
46
- const cipherWithTag = fromBase64(cipherPart);
47
- const ciphertext = cipherWithTag.subarray(0, cipherWithTag.length - 16);
48
- const tag = cipherWithTag.subarray(cipherWithTag.length - 16);
49
- const key = await new Promise((resolve, reject) => {
50
- crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
51
- if (error)
52
- reject(error);
53
- else
54
- resolve(derivedKey);
55
- });
56
- });
57
- try {
58
- const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
59
- decipher.setAuthTag(tag);
60
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
61
- return plaintext.toString('utf8');
62
- }
63
- catch {
64
- throw new Error('Incorrect password or corrupted data');
65
- }
66
- }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hashAddress = hashAddress;
7
+ exports.encryptWithPassword = encryptWithPassword;
8
+ exports.decryptWithPassword = decryptWithPassword;
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const ITERATIONS = 100000;
11
+ const KEY_LENGTH = 32;
12
+ const DIGEST = 'sha256';
13
+ function toBase64(buffer) {
14
+ return Buffer.from(buffer).toString('base64');
15
+ }
16
+ function fromBase64(value) {
17
+ return Buffer.from(value, 'base64');
18
+ }
19
+ function hashAddress(address) {
20
+ return crypto_1.default.createHash('sha256').update(address).digest('hex');
21
+ }
22
+ async function encryptWithPassword(text, password) {
23
+ const salt = crypto_1.default.randomBytes(16);
24
+ const iv = crypto_1.default.randomBytes(12);
25
+ const key = await new Promise((resolve, reject) => {
26
+ crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
27
+ if (error)
28
+ reject(error);
29
+ else
30
+ resolve(derivedKey);
31
+ });
32
+ });
33
+ const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
34
+ const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
35
+ const tag = cipher.getAuthTag();
36
+ return `${toBase64(salt)}:${toBase64(iv)}:${toBase64(Buffer.concat([ciphertext, tag]))}`;
37
+ }
38
+ async function decryptWithPassword(payload, password) {
39
+ const parts = payload.split(':');
40
+ if (parts.length !== 3) {
41
+ throw new Error('Invalid encrypted payload format');
42
+ }
43
+ const [saltPart, ivPart, cipherPart] = parts;
44
+ const salt = fromBase64(saltPart);
45
+ const iv = fromBase64(ivPart);
46
+ const cipherWithTag = fromBase64(cipherPart);
47
+ const ciphertext = cipherWithTag.subarray(0, cipherWithTag.length - 16);
48
+ const tag = cipherWithTag.subarray(cipherWithTag.length - 16);
49
+ const key = await new Promise((resolve, reject) => {
50
+ crypto_1.default.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, DIGEST, (error, derivedKey) => {
51
+ if (error)
52
+ reject(error);
53
+ else
54
+ resolve(derivedKey);
55
+ });
56
+ });
57
+ try {
58
+ const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
59
+ decipher.setAuthTag(tag);
60
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
61
+ return plaintext.toString('utf8');
62
+ }
63
+ catch {
64
+ throw new Error('Incorrect password or corrupted data');
65
+ }
66
+ }
@@ -1,6 +1,6 @@
1
- export declare function readEnvTrimmed(name: string): string | undefined;
2
- export declare function getMainWalletEnv(): {
3
- address: string | undefined;
4
- password: string | undefined;
5
- privateKey: string | undefined;
6
- };
1
+ export declare function readEnvTrimmed(name: string): string | undefined;
2
+ export declare function getMainWalletEnv(): {
3
+ address: string | undefined;
4
+ password: string | undefined;
5
+ privateKey: string | undefined;
6
+ };
package/dist/utils/env.js CHANGED
@@ -1,15 +1,15 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readEnvTrimmed = readEnvTrimmed;
4
- exports.getMainWalletEnv = getMainWalletEnv;
5
- function readEnvTrimmed(name) {
6
- const value = process.env[name]?.trim();
7
- return value ? value : undefined;
8
- }
9
- function getMainWalletEnv() {
10
- return {
11
- address: readEnvTrimmed('NULLPAY_MAIN_ADDRESS'),
12
- password: readEnvTrimmed('NULLPAY_MAIN_PASSWORD'),
13
- privateKey: readEnvTrimmed('NULLPAY_MAIN_PRIVATE_KEY') || readEnvTrimmed('NULLPAY_MAIN_PVT_KEY')
14
- };
15
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readEnvTrimmed = readEnvTrimmed;
4
+ exports.getMainWalletEnv = getMainWalletEnv;
5
+ function readEnvTrimmed(name) {
6
+ const value = process.env[name]?.trim();
7
+ return value ? value : undefined;
8
+ }
9
+ function getMainWalletEnv() {
10
+ return {
11
+ address: readEnvTrimmed('NULLPAY_MAIN_ADDRESS'),
12
+ password: readEnvTrimmed('NULLPAY_MAIN_PASSWORD'),
13
+ privateKey: readEnvTrimmed('NULLPAY_MAIN_PRIVATE_KEY') || readEnvTrimmed('NULLPAY_MAIN_PVT_KEY')
14
+ };
15
+ }
@@ -1 +1 @@
1
- export declare function dynamicImport<T = any>(specifier: string): Promise<T>;
1
+ export declare function dynamicImport<T = any>(specifier: string): Promise<T>;
package/dist/utils/esm.js CHANGED
@@ -1,7 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dynamicImport = dynamicImport;
4
- async function dynamicImport(specifier) {
5
- const importer = new Function('s', 'return import(s);');
6
- return await importer(specifier);
7
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dynamicImport = dynamicImport;
4
+ async function dynamicImport(specifier) {
5
+ const importer = new Function('s', 'return import(s);');
6
+ return await importer(specifier);
7
+ }
@@ -1,14 +1,14 @@
1
- import { Currency, InvoiceRecord, InvoiceType } from '../types';
2
- export declare function normalizeCurrency(value?: string): Currency;
3
- export declare function normalizePaymentCurrency(value?: string): Exclude<Currency, 'ANY'> | undefined;
4
- export declare function normalizeInvoiceType(value?: string): InvoiceType;
5
- export declare function tokenTypeLabel(tokenType?: number | null): string;
6
- export declare function invoiceTypeLabel(invoiceType?: number | null): string;
7
- export declare function currencyToTokenType(currency?: Currency | null): number;
8
- export declare function linkTokenToCurrency(token?: string | null): Currency | undefined;
9
- export declare function linkTypeToInvoiceType(type?: string | null): InvoiceType;
10
- export declare function parseAmount(value?: string | number | null): number | undefined;
11
- export declare function shouldMarkInvoiceSettled(invoiceType?: number | null): boolean;
12
- export declare function formatInvoiceSummary(invoice: InvoiceRecord): string;
13
- export declare function getAmountSource(invoice: InvoiceRecord): 'record' | 'database' | 'missing';
14
- export declare function buildAmountLookupHint(invoice: InvoiceRecord, hasInvoiceLookupKey: boolean): string;
1
+ import { Currency, InvoiceRecord, InvoiceType } from '../types';
2
+ export declare function normalizeCurrency(value?: string): Currency;
3
+ export declare function normalizePaymentCurrency(value?: string): Exclude<Currency, 'ANY'> | undefined;
4
+ export declare function normalizeInvoiceType(value?: string): InvoiceType;
5
+ export declare function tokenTypeLabel(tokenType?: number | null): string;
6
+ export declare function invoiceTypeLabel(invoiceType?: number | null): string;
7
+ export declare function currencyToTokenType(currency?: Currency | null): number;
8
+ export declare function linkTokenToCurrency(token?: string | null): Currency | undefined;
9
+ export declare function linkTypeToInvoiceType(type?: string | null): InvoiceType;
10
+ export declare function parseAmount(value?: string | number | null): number | undefined;
11
+ export declare function shouldMarkInvoiceSettled(invoiceType?: number | null): boolean;
12
+ export declare function formatInvoiceSummary(invoice: InvoiceRecord): string;
13
+ export declare function getAmountSource(invoice: InvoiceRecord): 'record' | 'database' | 'missing';
14
+ export declare function buildAmountLookupHint(invoice: InvoiceRecord, hasInvoiceLookupKey: boolean): string;