From ec4deae3b56f09aef9bdd81173fa270222cc156f Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:06:53 -0300 Subject: [PATCH] fix(sdk): update account service constructor args (#74) --- packages/sdk/src/core/account.service.ts | 146 +++++++++++++---------- packages/sdk/test/unit/account.spec.ts | 2 +- 2 files changed, 87 insertions(+), 61 deletions(-) diff --git a/packages/sdk/src/core/account.service.ts b/packages/sdk/src/core/account.service.ts index b28c100..51e8fa9 100644 --- a/packages/sdk/src/core/account.service.ts +++ b/packages/sdk/src/core/account.service.ts @@ -9,15 +9,27 @@ import { PoolInfo, PrivacyPoolAccount, } from "../types/account.js"; -import { DepositEvent, RagequitEvent, WithdrawalEvent } from "../types/events.js"; +import { + DepositEvent, + RagequitEvent, + WithdrawalEvent, +} from "../types/events.js"; import { Logger } from "../utils/logger.js"; import { AccountError } from "../errors/account.error.js"; import { ErrorCode } from "../errors/base.error.js"; +type AccountServiceConfig = + | { + mnemonic: string; + } + | { + account: PrivacyPoolAccount; + }; + /** * Service responsible for managing privacy pool accounts and their associated commitments. * Handles account initialization, deposit/withdrawal tracking, and history synchronization. - * + * * @remarks * This service maintains the state of all pool accounts and their commitments across different * chains and scopes. It uses deterministic key generation to recover account state from a mnemonic. @@ -28,34 +40,38 @@ export class AccountService { /** * Creates a new AccountService instance. - * + * * @param dataService - Service for fetching on-chain events - * @param account - Optional existing account to initialize with - * @param mnemonic - Optional mnemonic for deterministic key generation - * + * @param config - Configuration for the account service (either mnemonic or existing account) + * @param config.mnemonic - Optional mnemonic for deterministic key generation + * @param config.account - Optional existing account to initialize with + * * @throws {AccountError} If account initialization fails */ constructor( private readonly dataService: DataService, - mnemonic: string, - account?: PrivacyPoolAccount, + config: AccountServiceConfig ) { this.logger = new Logger({ prefix: "Account" }); - this.account = account || this._initializeAccount(mnemonic); + if("mnemonic" in config) { + this.account = this._initializeAccount(config.mnemonic); + } else { + this.account = config.account; + } } /** * Initializes a new account from a mnemonic phrase. - * + * * @param mnemonic - The mnemonic phrase to derive keys from * @returns A new PrivacyPoolAccount with derived master keys - * + * * @remarks * This method derives two master keys from the mnemonic: * 1. A master nullifier key from account index 0 * 2. A master secret key from account index 1 * These keys are used to deterministically generate nullifiers and secrets for deposits and withdrawals. - * + * * @throws {AccountError} If account initialization fails * @private */ @@ -64,11 +80,11 @@ export class AccountService { this.logger.debug("Initializing account with mnemonic"); const masterNullifierSeed = bytesToNumber( - mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey!, + mnemonicToAccount(mnemonic, { accountIndex: 0 }).getHdKey().privateKey! ); const masterSecretSeed = bytesToNumber( - mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey!, + mnemonicToAccount(mnemonic, { accountIndex: 1 }).getHdKey().privateKey! ); const masterNullifier = poseidon([BigInt(masterNullifierSeed)]) as Secret; @@ -78,7 +94,7 @@ export class AccountService { masterKeys: [masterNullifier, masterSecret], poolAccounts: new Map(), creationTimestamp: 0n, - lastUpdateTimestamp: 0n + lastUpdateTimestamp: 0n, }; } catch (error) { throw AccountError.accountInitializationFailed( @@ -89,7 +105,7 @@ export class AccountService { /** * Generates a deterministic nullifier for a deposit. - * + * * @param scope - The scope of the pool * @param index - The index of the deposit * @returns A deterministic nullifier for the deposit @@ -102,7 +118,7 @@ export class AccountService { /** * Generates a deterministic secret for a deposit. - * + * * @param scope - The scope of the pool * @param index - The index of the deposit * @returns A deterministic secret for the deposit @@ -115,7 +131,7 @@ export class AccountService { /** * Generates a deterministic nullifier for a withdrawal. - * + * * @param label - The label of the commitment * @param index - The index of the withdrawal * @returns A deterministic nullifier for the withdrawal @@ -128,7 +144,7 @@ export class AccountService { /** * Generates a deterministic secret for a withdrawal. - * + * * @param label - The label of the commitment * @param index - The index of the withdrawal * @returns A deterministic secret for the withdrawal @@ -141,7 +157,7 @@ export class AccountService { /** * Hashes a commitment using the Poseidon hash function. - * + * * @param value - The value of the commitment * @param label - The label of the commitment * @param precommitment - The precommitment hash @@ -151,14 +167,14 @@ export class AccountService { private _hashCommitment( value: bigint, label: Hash, - precommitment: Hash, + precommitment: Hash ): Hash { return poseidon([value, label, precommitment]) as Hash; } /** * Hashes a precommitment using the Poseidon hash function. - * + * * @param nullifier - The nullifier for the commitment * @param secret - The secret for the commitment * @returns The precommitment hash @@ -170,9 +186,9 @@ export class AccountService { /** * Gets all spendable commitments across all pools. - * + * * @returns A map of scope to array of spendable commitments - * + * * @remarks * A commitment is considered spendable if: * 1. It has a non-zero value @@ -209,18 +225,18 @@ export class AccountService { /** * Creates nullifier and secret for a new deposit - * + * * @param scope - The scope of the pool to deposit into * @param index - Optional index for deterministic generation * @returns The nullifier, secret, and precommitment for the deposit - * + * * @remarks * If no index is provided, it uses the current number of accounts for the scope. * The precommitment is a hash of the nullifier and secret, used in the deposit process. */ public createDepositSecrets( scope: Hash, - index?: bigint, + index?: bigint ): { nullifier: Secret; secret: Secret; @@ -242,15 +258,15 @@ export class AccountService { /** * Creates nullifier and secret for spending a commitment - * + * * @param commitment - The commitment to spend * @returns The nullifier and secret for the new commitment - * + * * @remarks * The index used for generating the withdrawal nullifier and secret is based on * the number of children the account already has, ensuring each withdrawal has * a unique nullifier. - * + * * @throws {AccountError} If no account is found for the commitment */ public createWithdrawalSecrets(commitment: AccountCommitment): { @@ -279,7 +295,7 @@ export class AccountService { /** * Adds a new pool account after depositing - * + * * @param scope - The scope of the pool * @param value - The deposit value * @param nullifier - The nullifier used for the deposit @@ -288,7 +304,7 @@ export class AccountService { * @param blockNumber - The block number of the deposit * @param txHash - The transaction hash of the deposit * @returns The new pool account - * + * * @remarks * This method creates a new account with the deposit commitment and adds it to the * pool accounts map under the specified scope. The commitment hash is calculated @@ -301,7 +317,7 @@ export class AccountService { secret: Secret, label: Hash, blockNumber: bigint, - txHash: Hex, + txHash: Hex ): PoolAccount { const precommitment = this._hashPrecommitment(nullifier, secret); const commitment = this._hashCommitment(value, label, precommitment); @@ -327,7 +343,7 @@ export class AccountService { this.account.poolAccounts.get(scope)!.push(newAccount); this.logger.info( - `Added new pool account with value ${value} and label ${label}`, + `Added new pool account with value ${value} and label ${label}` ); return newAccount; @@ -335,7 +351,7 @@ export class AccountService { /** * Adds a new commitment to the account after spending - * + * * @param parentCommitment - The commitment that was spent * @param value - The remaining value after spending * @param nullifier - The nullifier used for spending @@ -343,12 +359,12 @@ export class AccountService { * @param blockNumber - The block number of the withdrawal * @param txHash - The transaction hash of the withdrawal * @returns The new commitment - * + * * @remarks * This method finds the account containing the parent commitment, creates a new * commitment with the provided parameters, and adds it to the account's children. * The new commitment inherits the label from the parent commitment. - * + * * @throws {AccountError} If no account is found for the commitment */ public addWithdrawalCommitment( @@ -357,7 +373,7 @@ export class AccountService { nullifier: Secret, secret: Secret, blockNumber: bigint, - txHash: Hex, + txHash: Hex ): AccountCommitment { let foundAccount: PoolAccount | undefined; let foundScope: bigint | undefined; @@ -366,7 +382,7 @@ export class AccountService { foundAccount = accounts.find((account) => { if (account.deposit.hash === parentCommitment.hash) return true; return account.children.some( - (child) => child.hash === parentCommitment.hash, + (child) => child.hash === parentCommitment.hash ); }); @@ -394,7 +410,7 @@ export class AccountService { foundAccount.children.push(newCommitment); this.logger.info( - `Added new commitment with value ${value} to account with label ${parentCommitment.label}`, + `Added new commitment with value ${value} to account with label ${parentCommitment.label}` ); return newCommitment; @@ -402,16 +418,16 @@ export class AccountService { /** * Adds a ragequit event to an existing pool account - * + * * @param label - The label of the account to add the ragequit to * @param ragequit - The ragequit event to add * @returns The updated pool account - * + * * @remarks * When an account has a ragequit event, it can no longer be spent. * This method finds the account with the matching label and attaches * the ragequit event to it. - * + * * @throws {AccountError} If no account is found with the given label */ public addRagequitToAccount( @@ -449,18 +465,18 @@ export class AccountService { /** * Retrieves the history of deposits and withdrawals for the given pools. - * + * * @param pools - Array of pool configurations to sync history for - * + * * @remarks * This method performs the following steps: * 1. Initializes pool accounts for each pool if they don't exist * 2. For each pool, fetches deposit events and reconstructs accounts * 3. Processes withdrawals and ragequits to update account state - * + * * The account reconstruction is deterministic based on the master keys, * allowing the full state to be recovered from on-chain events. - * + * * @throws {DataError} If event fetching fails * @throws {AccountError} If account state reconstruction fails */ @@ -480,14 +496,14 @@ export class AccountService { pools.map(async (pool) => { // Log which pool is being processed this.logger.info( - `Processing pool ${pool.address} on chain ${pool.chainId} from block ${pool.deploymentBlock}`, + `Processing pool ${pool.address} on chain ${pool.chainId} from block ${pool.deploymentBlock}` ); // Fetch all deposit events for this pool const deposits = await this.dataService.getDeposits(pool); this.logger.info( - `Found ${deposits.length} deposits for pool ${pool.address}`, + `Found ${deposits.length} deposits for pool ${pool.address}` ); // Create a map of deposits by precommitment for efficient lookup @@ -533,7 +549,7 @@ export class AccountService { secret, deposit.label, deposit.blockNumber, - deposit.transactionHash, + deposit.transactionHash ); // Track the found deposit @@ -546,13 +562,13 @@ export class AccountService { // If no accounts were found for this scope, log and skip further processing if (this.account.poolAccounts.get(pool.scope)!.length === 0) { this.logger.info( - `No Pool Accounts were found for scope ${pool.scope}`, + `No Pool Accounts were found for scope ${pool.scope}` ); return; } this.logger.info( - `Found ${foundDeposits.length} deposits for pool ${pool.address}`, + `Found ${foundDeposits.length} deposits for pool ${pool.address}` ); }) ); @@ -564,9 +580,9 @@ export class AccountService { /** * Processes withdrawal events for all pools and updates account state. - * + * * @param pools - Array of pool configurations to process withdrawals for - * + * * @remarks * This method performs the following steps for each pool: * 1. Identifies the earliest deposit block for each scope @@ -577,7 +593,7 @@ export class AccountService { * - Matching them against on-chain events * - Adding matched withdrawals to the account state * 5. Adds ragequit events to accounts if found - * + * * @throws {DataError} If event fetching fails * @private */ @@ -605,8 +621,14 @@ export class AccountService { } // Fetch withdrawal and ragequit events from the first deposit block - const withdrawals = await this.dataService.getWithdrawals(pool, firstDepositBlock); - const ragequits = await this.dataService.getRagequits(pool, firstDepositBlock); + const withdrawals = await this.dataService.getWithdrawals( + pool, + firstDepositBlock + ); + const ragequits = await this.dataService.getRagequits( + pool, + firstDepositBlock + ); this.logger.info( `Found ${withdrawals.length} withdrawals for pool ${pool.address}` @@ -636,7 +658,9 @@ export class AccountService { // Continue processing withdrawals until no more are found while (true) { // Generate nullifier for this withdrawal - const nullifierHash = poseidon([currentCommitment.nullifier]) as Hash; + const nullifierHash = poseidon([ + currentCommitment.nullifier, + ]) as Hash; // Look for a withdrawal event with this nullifier const withdrawal = withdrawalMap.get(nullifierHash); @@ -645,7 +669,10 @@ export class AccountService { } // Generate secret for this withdrawal - const nullifier = this._genWithdrawalNullifier(account.label, index); + const nullifier = this._genWithdrawalNullifier( + account.label, + index + ); const secret = this._genWithdrawalSecret(account.label, index); // Add the withdrawal commitment to the account @@ -673,5 +700,4 @@ export class AccountService { }) ); } - } diff --git a/packages/sdk/test/unit/account.spec.ts b/packages/sdk/test/unit/account.spec.ts index f5c7ad9..22b5d4f 100644 --- a/packages/sdk/test/unit/account.spec.ts +++ b/packages/sdk/test/unit/account.spec.ts @@ -55,7 +55,7 @@ describe("AccountService", () => { } as unknown as DataService; // Initialize account service with mocked data service - accountService = new AccountService(dataService, testMnemonic); + accountService = new AccountService(dataService, {mnemonic: testMnemonic}); masterKeys = accountService.account.masterKeys; // Generate test data