mirror of
https://github.com/0xbow-io/privacy-pools-core.git
synced 2026-01-09 01:17:58 -05:00
fix(sdk): update account service constructor args (#74)
This commit is contained in:
@@ -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 {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user