@nullpay/mcp 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/aleo.js CHANGED
@@ -24,7 +24,8 @@ const crypto_1 = __importDefault(require("crypto"));
24
24
  const esm_1 = require("./esm");
25
25
  const env_1 = require("./env");
26
26
  exports.PROGRAM_ID = 'zk_pay_proofs_privacy_v22.aleo';
27
- const FREEZELIST_PROGRAM_ID = 'test_usdcx_freezelist.aleo';
27
+ const USDCX_FREEZELIST_PROGRAM_ID = 'test_usdcx_freezelist.aleo';
28
+ const USAD_FREEZELIST_PROGRAM_ID = 'test_usad_freezelist.aleo';
28
29
  const EXPLORER_BASE = 'https://api.explorer.provable.com/v1';
29
30
  const MAPPING_BASE = 'https://api.provable.com/v2/testnet/program';
30
31
  const SCANNER_BASE = 'https://api.provable.com/scanner/testnet';
@@ -548,39 +549,54 @@ async function findSpendableRecord(session, programFilter, recordName, amountReq
548
549
  }
549
550
  return null;
550
551
  }
551
- async function getFreezeListIndex(index) {
552
+ function getFreezeListProgramId(tokenProgram) {
553
+ if (tokenProgram === 'test_usdcx_stablecoin.aleo') {
554
+ return USDCX_FREEZELIST_PROGRAM_ID;
555
+ }
556
+ if (tokenProgram === 'test_usad_stablecoin.aleo') {
557
+ return USAD_FREEZELIST_PROGRAM_ID;
558
+ }
559
+ return null;
560
+ }
561
+ async function getFreezeListIndex(programId, index) {
552
562
  const { AleoNetworkClient } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
553
563
  const client = new AleoNetworkClient(EXPLORER_BASE);
554
- const value = await client.getProgramMappingValue(FREEZELIST_PROGRAM_ID, 'freeze_list_index', `${index}u32`);
564
+ const value = await client.getProgramMappingValue(programId, 'freeze_list_index', `${index}u32`);
555
565
  return value ? value.replace(/"/g, '') : null;
556
566
  }
557
- async function generateFreezeListProof(targetIndex = 1, occupiedLeafValue) {
558
- const { Field, Poseidon4 } = await (0, esm_1.dynamicImport)('@provablehq/wasm');
559
- const hasher = new Poseidon4();
560
- const emptyHashes = [];
561
- let currentEmpty = '0field';
562
- for (let level = 0; level < 16; level += 1) {
563
- emptyHashes.push(currentEmpty);
564
- const field = Field.fromString(currentEmpty);
565
- currentEmpty = hasher.hash([field, field]).toString();
566
- }
567
- let currentHash = '0field';
568
- let currentIndex = targetIndex;
569
- const proofSiblings = [];
570
- for (let level = 0; level < 16; level += 1) {
571
- const isLeft = currentIndex % 2 === 0;
572
- const siblingIndex = isLeft ? currentIndex + 1 : currentIndex - 1;
573
- let siblingHash = emptyHashes[level];
574
- if (level === 0 && siblingIndex === 0 && occupiedLeafValue) {
575
- siblingHash = occupiedLeafValue;
567
+ async function getFreezeListCount(programId) {
568
+ const response = await fetch(`${MAPPING_BASE}/${programId}/mapping/freeze_list_last_index/true`);
569
+ if (!response.ok) {
570
+ return 0;
571
+ }
572
+ const value = await response.json();
573
+ if (!value) {
574
+ return 0;
575
+ }
576
+ const parsed = Number.parseInt(String(value).replace(/u32|["']/g, ''), 10);
577
+ return Number.isFinite(parsed) ? parsed + 1 : 0;
578
+ }
579
+ async function generateFreezeListProof(ownerAddress, tokenProgram) {
580
+ const freezeListProgramId = getFreezeListProgramId(tokenProgram);
581
+ if (!freezeListProgramId) {
582
+ throw new Error(`Unsupported freeze list program for ${tokenProgram}`);
583
+ }
584
+ const { SealanceMerkleTree } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
585
+ const count = Math.max(1, await getFreezeListCount(freezeListProgramId));
586
+ const addresses = [];
587
+ for (let index = 0; index < count; index += 1) {
588
+ const address = await getFreezeListIndex(freezeListProgramId, index);
589
+ if (address) {
590
+ addresses.push(address);
576
591
  }
577
- proofSiblings.push(siblingHash);
578
- const left = Field.fromString(isLeft ? currentHash : siblingHash);
579
- const right = Field.fromString(isLeft ? siblingHash : currentHash);
580
- currentHash = hasher.hash([left, right]).toString();
581
- currentIndex = Math.floor(currentIndex / 2);
582
592
  }
583
- return `[${proofSiblings.join(', ')}]`;
593
+ const sealance = new SealanceMerkleTree();
594
+ const leaves = sealance.generateLeaves(addresses, 16);
595
+ const tree = sealance.buildTree(leaves);
596
+ const [leftIdx, rightIdx] = sealance.getLeafIndices(tree, ownerAddress);
597
+ const proofLeft = sealance.getSiblingPath(tree, leftIdx, 16);
598
+ const proofRight = sealance.getSiblingPath(tree, rightIdx, 16);
599
+ return sealance.formatMerkleProof([proofLeft, proofRight]);
584
600
  }
585
601
  async function createSponsoredPaymentAuthorization(args) {
586
602
  const session = await getScannerSession(args.walletPrivateKey);
@@ -598,14 +614,12 @@ async function createSponsoredPaymentAuthorization(args) {
598
614
  }
599
615
  let proofsInput;
600
616
  if (paymentMode.tokenProgram !== 'credits.aleo') {
601
- const firstIndex = await getFreezeListIndex(0);
602
- let index0Field;
603
- if (firstIndex) {
604
- const { Address } = await (0, esm_1.dynamicImport)('@provablehq/wasm');
605
- index0Field = Address.from_string(firstIndex).toGroup().toXCoordinate().toString();
617
+ const ownerMatch = record.match(/owner\s*:\s*([a-z0-9]+)/i);
618
+ const ownerAddress = ownerMatch?.[1];
619
+ if (!ownerAddress || !ownerAddress.startsWith('aleo')) {
620
+ throw new Error(`Failed to read token record owner for ${paymentMode.tokenProgram}.`);
606
621
  }
607
- const proof = await generateFreezeListProof(1, index0Field);
608
- proofsInput = `[${proof}, ${proof}]`;
622
+ proofsInput = await generateFreezeListProof(ownerAddress, paymentMode.tokenProgram);
609
623
  }
610
624
  const { AleoKeyProvider, AleoNetworkClient, NetworkRecordProvider, ProgramManager } = await (0, esm_1.dynamicImport)('@provablehq/sdk');
611
625
  const keyProvider = new AleoKeyProvider();
package/dist/service.js CHANGED
@@ -235,18 +235,27 @@ class NullPayMcpService {
235
235
  let recoveredEncryptedBurnerKey = null;
236
236
  let usedRecoveredPassword = false;
237
237
  let restoredBurnerFromChain = false;
238
- const attemptRecovery = async () => {
239
- if (!mainPrivateKey) {
240
- return false;
238
+ let chainBackupLoaded = false;
239
+ const loadChainBackup = async () => {
240
+ if (chainBackupLoaded || !mainPrivateKey) {
241
+ return null;
241
242
  }
243
+ chainBackupLoaded = true;
242
244
  const recovered = await (0, aleo_1.recoverOnChainWalletBackup)(mainPrivateKey, address);
243
- if (!recovered?.password) {
244
- return false;
245
+ if (!recovered) {
246
+ return null;
245
247
  }
246
- password = recovered.password;
247
248
  recoveredBackupSource = recovered.source;
248
249
  recoveredBurnerAddress = recovered.burnerAddress || null;
249
250
  recoveredEncryptedBurnerKey = recovered.encryptedBurnerKey || null;
251
+ return recovered;
252
+ };
253
+ const attemptRecovery = async () => {
254
+ const recovered = await loadChainBackup();
255
+ if (!recovered?.password) {
256
+ return false;
257
+ }
258
+ password = recovered.password;
250
259
  usedRecoveredPassword = true;
251
260
  return true;
252
261
  };
@@ -302,6 +311,9 @@ class NullPayMcpService {
302
311
  burnerAddress = null;
303
312
  }
304
313
  }
314
+ if (!encryptedBurnerKey || !burnerAddress || !encryptedBurnerAddress) {
315
+ await loadChainBackup();
316
+ }
305
317
  if ((!encryptedBurnerAddress || !encryptedBurnerKey || !burnerAddress) && recoveredBurnerAddress && recoveredEncryptedBurnerKey) {
306
318
  burnerAddress = recoveredBurnerAddress;
307
319
  encryptedBurnerAddress = await (0, crypto_1.encryptWithPassword)(recoveredBurnerAddress, password);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nullpay/mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "NullPay MCP server and Claude setup wizard for conversational payment flows",
5
5
  "type": "commonjs",
6
6
  "main": "dist/server.js",