chore: add timestamps, make mnemonic required, export services (#36)

This commit is contained in:
moebius
2025-03-05 09:10:55 -07:00
committed by GitHub
7 changed files with 128 additions and 34 deletions

View File

@@ -17,6 +17,15 @@ const typescriptConfig = {
outputToFilesystem: false,
}
// External dependencies that should not be bundled
const external = [
'@envio-dev/hypersync-client',
'viem',
'viem/accounts',
'viem/chains',
'maci-crypto',
];
export default [
{
@@ -29,6 +38,7 @@ export default [
entryFileNames: "[name].mjs"
},
],
external,
plugins: [
nodeResolve({
exportConditions: ["umd"],
@@ -55,6 +65,7 @@ export default [
entryFileNames: "[name].mjs"
},
],
external,
plugins: [
nodeResolve({
exportConditions: ["node"],
@@ -83,6 +94,7 @@ export default [
sourcemap: false,
},
],
external,
plugins: [
nodeResolve({
exportConditions: ["node"],
@@ -103,6 +115,7 @@ export default [
{
input: path.join(rootOutDir, "types", "index.d.ts"),
output: [{ file: path.join(rootOutDir, "index.d.mts"), format: "esm" }],
external,
plugins: [dts()],
}

View File

@@ -4,7 +4,7 @@ import { Hex, bytesToNumber } from "viem";
import { english, generateMnemonic, mnemonicToAccount } from "viem/accounts";
import { DataService } from "./data.service.js";
import {
Commitment,
AccountCommitment,
PoolAccount,
PoolInfo,
PrivacyPoolAccount,
@@ -35,16 +35,15 @@ export class AccountService {
*/
constructor(
private readonly dataService: DataService,
mnemonic: string,
account?: PrivacyPoolAccount,
mnemonic?: string,
) {
this.logger = new Logger({ prefix: "Account" });
this.account = account || this._initializeAccount(mnemonic);
}
private _initializeAccount(mnemonic?: string): PrivacyPoolAccount {
private _initializeAccount(mnemonic: string): PrivacyPoolAccount {
try {
mnemonic = mnemonic || generateMnemonic(english, 128);
this.logger.debug("Initializing account with mnemonic");
let key1 = bytesToNumber(
@@ -59,9 +58,10 @@ export class AccountService {
let masterKey2 = poseidon([BigInt(key2)]) as Secret;
return {
mnemonic,
masterKeys: [masterKey1, masterKey2],
poolAccounts: new Map(),
creationTimestamp: 0n,
lastUpdateTimestamp: 0n
};
} catch (error) {
throw AccountError.accountInitializationFailed(
@@ -103,11 +103,11 @@ export class AccountService {
*
* @returns A map of scope to array of spendable commitments
*/
public getSpendableCommitments(): Map<bigint, Commitment[]> {
const result = new Map<bigint, Commitment[]>();
public getSpendableCommitments(): Map<bigint, AccountCommitment[]> {
const result = new Map<bigint, AccountCommitment[]>();
for (const [scope, accounts] of this.account.poolAccounts.entries()) {
const nonZeroCommitments: Commitment[] = [];
const nonZeroCommitments: AccountCommitment[] = [];
for (const account of accounts) {
const lastCommitment =
@@ -159,7 +159,7 @@ export class AccountService {
* @returns The nullifier and secret for the new commitment
* @throws {AccountError} If no account is found for the commitment
*/
public createWithdrawalSecrets(commitment: Commitment): {
public createWithdrawalSecrets(commitment: AccountCommitment): {
nullifier: Secret;
secret: Secret;
} {
@@ -192,6 +192,7 @@ export class AccountService {
* @param secret - The secret used for the deposit
* @param label - The label for the commitment
* @param blockNumber - The block number of the deposit
* @param timestamp - The timestamp of the deposit
* @param txHash - The transaction hash of the deposit
* @returns The new pool account
*/
@@ -202,11 +203,20 @@ export class AccountService {
secret: Secret,
label: Hash,
blockNumber: bigint,
timestamp: bigint,
txHash: Hash,
): PoolAccount {
const precommitment = this._hashPrecommitment(nullifier, secret);
const commitment = this._hashCommitment(value, label, precommitment);
// Update account timestamps
if (this.account.creationTimestamp === 0n || timestamp < this.account.creationTimestamp) {
this.account.creationTimestamp = timestamp;
}
if (timestamp > this.account.lastUpdateTimestamp) {
this.account.lastUpdateTimestamp = timestamp;
}
const newAccount: PoolAccount = {
label,
deposit: {
@@ -216,6 +226,7 @@ export class AccountService {
nullifier,
secret,
blockNumber,
timestamp,
txHash,
},
children: [],
@@ -242,18 +253,25 @@ export class AccountService {
* @param nullifier - The nullifier used for spending
* @param secret - The secret used for spending
* @param blockNumber - The block number of the withdrawal
* @param timestamp - The timestamp of the withdrawal
* @param txHash - The transaction hash of the withdrawal
* @returns The new commitment
* @throws {AccountError} If no account is found for the commitment
*/
public addWithdrawalCommitment(
parentCommitment: Commitment,
parentCommitment: AccountCommitment,
value: bigint,
nullifier: Secret,
secret: Secret,
blockNumber: bigint,
timestamp: bigint,
txHash: Hash,
): Commitment {
): AccountCommitment {
// Update last update timestamp
if (timestamp > this.account.lastUpdateTimestamp) {
this.account.lastUpdateTimestamp = timestamp;
}
let foundAccount: PoolAccount | undefined;
let foundScope: bigint | undefined;
@@ -276,13 +294,14 @@ export class AccountService {
}
const precommitment = this._hashPrecommitment(nullifier, secret);
const newCommitment: Commitment = {
const newCommitment: AccountCommitment = {
hash: this._hashCommitment(value, parentCommitment.label, precommitment),
value,
label: parentCommitment.label,
nullifier,
secret,
blockNumber,
timestamp,
txHash,
};
@@ -313,13 +332,13 @@ export class AccountService {
for (const withdrawal of withdrawals) {
for (const account of foundAccounts.values()) {
const isParentCommitment =
const isParentCommitment =
BigInt(account.deposit.nullifier) === BigInt(withdrawal.spentNullifier) ||
account.children.some(child => BigInt(child.nullifier) === BigInt(withdrawal.spentNullifier));
if (isParentCommitment) {
const parentCommitment = account.children.length > 0
? account.children[account.children.length - 1]
const parentCommitment = account.children.length > 0
? account.children[account.children.length - 1]
: account.deposit;
if (!parentCommitment) {
@@ -333,6 +352,7 @@ export class AccountService {
withdrawal.spentNullifier as unknown as Secret,
parentCommitment.secret,
withdrawal.blockNumber,
withdrawal.timestamp,
withdrawal.transactionHash,
);
break;
@@ -429,6 +449,7 @@ export class AccountService {
secret,
deposit.label,
deposit.blockNumber,
deposit.timestamp,
deposit.transactionHash,
);
});

View File

@@ -2,6 +2,8 @@ import {
HypersyncClient,
presetQueryLogsOfEvent,
Query,
QueryResponse,
Log
} from "@envio-dev/hypersync-client";
import {
ChainConfig,
@@ -73,8 +75,7 @@ export class DataService {
const toBlock = options.toBlock ?? undefined;
this.logger.debug(
`Fetching deposits for chain ${chainId} from block ${fromBlock}${
toBlock ? ` to ${toBlock}` : ""
`Fetching deposits for chain ${chainId} from block ${fromBlock}${toBlock ? ` to ${toBlock}` : ""
}`,
);
@@ -101,7 +102,16 @@ export class DataService {
const res = await client.get(query);
// Create a map of block numbers to timestamps
const blockTimestamps = new Map(
res.data.blocks.map(block => [
block.number,
block.timestamp ? BigInt(block.timestamp) : 0n
])
);
return res.data.logs.map((log) => {
let a: Log = log;
if (!log.topics || log.topics.length < 2) {
throw DataError.invalidLog("deposit", "missing topics");
}
@@ -137,13 +147,20 @@ export class DataService {
throw DataError.invalidLog("deposit", "missing required fields");
}
const blockNumber = BigInt(log.blockNumber);
const timestamp = blockTimestamps.get(Number(blockNumber));
if (!timestamp) {
throw DataError.invalidLog("deposit", "missing block timestamp");
}
return {
depositor: `0x${depositor.toString(16).padStart(40, "0")}`,
commitment: bigintToHash(commitment),
label: bigintToHash(label),
value,
precommitment: bigintToHash(precommitment),
blockNumber: BigInt(log.blockNumber),
blockNumber,
timestamp,
transactionHash: log.transactionHash as unknown as Hash,
};
});
@@ -173,8 +190,7 @@ export class DataService {
const toBlock = options.toBlock ?? undefined;
this.logger.debug(
`Fetching withdrawals for chain ${chainId} from block ${fromBlock}${
toBlock ? ` to ${toBlock}` : ""
`Fetching withdrawals for chain ${chainId} from block ${fromBlock}${toBlock ? ` to ${toBlock}` : ""
}`,
);
@@ -188,6 +204,14 @@ export class DataService {
const res = await client.get(query);
// Create a map of block numbers to timestamps
const blockTimestamps = new Map(
res.data.blocks.map(block => [
block.number,
block.timestamp ? BigInt(block.timestamp) : 0n
])
);
return res.data.logs.map((log) => {
if (!log.topics || log.topics.length < 2) {
throw DataError.invalidLog("withdrawal", "missing topics");
@@ -222,11 +246,18 @@ export class DataService {
throw DataError.invalidLog("withdrawal", "missing required fields");
}
const blockNumber = BigInt(log.blockNumber);
const timestamp = blockTimestamps.get(Number(blockNumber));
if (!timestamp) {
throw DataError.invalidLog("withdrawal", "missing block timestamp");
}
return {
withdrawn: value,
spentNullifier: bigintToHash(spentNullifier),
newCommitment: bigintToHash(newCommitment),
blockNumber: BigInt(log.blockNumber),
blockNumber,
timestamp,
transactionHash: log.transactionHash as unknown as Hash,
};
});
@@ -256,8 +287,7 @@ export class DataService {
const toBlock = options.toBlock ?? undefined;
this.logger.debug(
`Fetching ragequits for chain ${chainId} from block ${fromBlock}${
toBlock ? ` to ${toBlock}` : ""
`Fetching ragequits for chain ${chainId} from block ${fromBlock}${toBlock ? ` to ${toBlock}` : ""
}`,
);
@@ -271,6 +301,14 @@ export class DataService {
const res = await client.get(query);
// Create a map of block numbers to timestamps
const blockTimestamps = new Map(
res.data.blocks.map(block => [
block.number,
block.timestamp ? BigInt(block.timestamp) : 0n
])
);
return res.data.logs.map((log) => {
if (!log.topics || log.topics.length < 2) {
throw DataError.invalidLog("ragequit", "missing topics");
@@ -306,12 +344,19 @@ export class DataService {
throw DataError.invalidLog("ragequit", "missing required fields");
}
const blockNumber = BigInt(log.blockNumber);
const timestamp = blockTimestamps.get(Number(blockNumber));
if (!timestamp) {
throw DataError.invalidLog("ragequit", "missing block timestamp");
}
return {
ragequitter: `0x${ragequitter.toString(16).padStart(40, "0")}`,
commitment: bigintToHash(commitment),
label: bigintToHash(label),
value,
blockNumber: BigInt(log.blockNumber),
blockNumber,
timestamp,
transactionHash: log.transactionHash as unknown as Hash,
};
});

View File

@@ -3,12 +3,13 @@ export * from "./crypto.js";
export * from "./external.js";
export { PrivacyPoolSDK } from "./core/sdk.js";
// Types
export * from "./types/commitment.js";
export * from "./types/withdrawal.js";
// Additional Types (not included in types/index.js)
export * from "./types/account.js";
export * from "./types/events.js";
// Errors
export * from "./errors/base.error.js";
export * from "./errors/account.error.js";
// Interfaces
export * from "./interfaces/circuits.interface.js";
@@ -16,3 +17,5 @@ export * from "./interfaces/circuits.interface.js";
// Services (exported for advanced usage)
export { CommitmentService } from "./core/commitment.service.js";
export { WithdrawalService } from "./core/withdrawal.service.js";
export { AccountService } from "./core/account.service.js";
export { DataService } from "./core/data.service.js";

View File

@@ -3,24 +3,26 @@ import { Address } from "viem";
export interface PoolAccount {
label: Hash;
deposit: Commitment;
children: Commitment[];
deposit: AccountCommitment;
children: AccountCommitment[];
}
export interface Commitment {
export interface AccountCommitment {
hash: Hash;
value: bigint;
label: Hash;
nullifier: Secret;
secret: Secret;
blockNumber: bigint;
timestamp: bigint;
txHash: Hash;
}
export interface PrivacyPoolAccount {
mnemonic: string;
masterKeys: [Secret, Secret];
poolAccounts: Map<bigint, PoolAccount[]>;
creationTimestamp: bigint;
lastUpdateTimestamp: bigint;
}
export interface PoolInfo {

View File

@@ -11,6 +11,7 @@ export interface DepositEvent {
value: bigint;
precommitment: Hash;
blockNumber: bigint;
timestamp: bigint;
transactionHash: Hash;
}
@@ -22,6 +23,7 @@ export interface WithdrawalEvent {
spentNullifier: Hash;
newCommitment: Hash;
blockNumber: bigint;
timestamp: bigint;
transactionHash: Hash;
}
@@ -34,6 +36,7 @@ export interface RagequitEvent {
label: Hash;
value: bigint;
blockNumber: bigint;
timestamp: bigint;
transactionHash: Hash;
}

View File

@@ -3,9 +3,10 @@ import { AccountService } from "../../src/core/account.service.js";
import { DataService } from "../../src/core/data.service.js";
import { Hash, Secret } from "../../src/types/commitment.js";
import { DepositEvent, WithdrawalEvent } from "../../src/types/events.js";
import { PoolInfo, Commitment } from "../../src/types/account.js";
import { PoolInfo, AccountCommitment } from "../../src/types/account.js";
import { poseidon } from "maci-crypto/build/ts/hashing.js";
import { Address } from "viem";
import { english, generateMnemonic } from "viem/accounts";
function randomBigInt(): bigint {
return BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
@@ -29,6 +30,7 @@ describe("AccountService", () => {
let masterKeys: [Secret, Secret];
let depositEvents: DepositEvent[] = [];
let withdrawalEvents: WithdrawalEvent[] = [];
const testMnemonic = generateMnemonic(english);
beforeEach(() => {
// Reset test data arrays
@@ -46,7 +48,7 @@ describe("AccountService", () => {
} as unknown as DataService;
// Initialize account service with mocked data service
accountService = new AccountService(dataService);
accountService = new AccountService(dataService, testMnemonic);
masterKeys = accountService.account.masterKeys;
// Generate test data
@@ -67,6 +69,7 @@ describe("AccountService", () => {
const precommitment = poseidon([nullifier, secret]) as Hash;
const commitment = poseidon([value, label, precommitment]) as Hash;
const timestamp = BigInt(Math.floor(Date.now() / 1000));
const deposit: DepositEvent = {
depositor: POOL.address,
@@ -75,6 +78,7 @@ describe("AccountService", () => {
value,
precommitment,
blockNumber: POOL.deploymentBlock + BigInt(i * 100),
timestamp,
transactionHash: BigInt(i + 1) as Hash,
};
@@ -88,6 +92,7 @@ describe("AccountService", () => {
nullifier,
secret,
blockNumber: deposit.blockNumber,
timestamp,
txHash: deposit.transactionHash,
};
let remainingValue = value;
@@ -125,6 +130,7 @@ describe("AccountService", () => {
spentNullifier: poseidon([withdrawalNullifier]) as Hash,
newCommitment,
blockNumber: currentCommitment.blockNumber + BigInt((j + 1) * 100),
timestamp: currentCommitment.timestamp + BigInt((j + 1) * 60), // Add 1 minute per withdrawal
transactionHash: BigInt(i * 100 + j + 2) as Hash,
};
@@ -138,6 +144,7 @@ describe("AccountService", () => {
nullifier: withdrawalNullifier,
secret: withdrawalSecret,
blockNumber: withdrawal.blockNumber,
timestamp: withdrawal.timestamp,
txHash: withdrawal.transactionHash,
};
}
@@ -160,7 +167,7 @@ describe("AccountService", () => {
const spendable = accountService
.getSpendableCommitments()
.get(POOL.scope) as Commitment[];
.get(POOL.scope) as AccountCommitment[];
if (spendable) {
console.log("Spendable:", spendable);