diff --git a/src/client.ts b/src/client.ts index 3668531c..5bd5a8ed 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3,7 +3,6 @@ import { keccak256 } from '@ethersproject/keccak256'; import { Wallet } from '@ethersproject/wallet'; import { Buffer } from 'buffer'; import invariant from 'tiny-invariant'; -import { AccountAPI } from './api'; import { AccountCore } from './core/account'; import { ElectionCore } from './core/election'; import { VoteCore } from './core/vote'; @@ -26,6 +25,8 @@ import { import { delay } from './util/common'; import { API_URL, EXPLORER_URL, FAUCET_AUTH_TOKEN, FAUCET_URL, TX_WAIT_OPTIONS } from './util/constants'; import { + AccountData, + AccountService, AnonymousService, CensusProof, CensusService, @@ -41,24 +42,6 @@ import { ZkProof, } from './services'; -/** - * @typedef AccountData - * @property {string} address - * @property {number} balance - * @property {number} nonce - * @property {number} electionIndex - * @property {string | null} infoURL - * @property {Account} account - */ -export type AccountData = { - address: string; - balance: number; - nonce: number; - electionIndex: number; - infoURL?: string; - account: Account; -}; - export enum EnvOptions { DEV = 'dev', STG = 'stg', @@ -113,6 +96,7 @@ export class VocdoniSDKClient { public voteService: VoteService; public fileService: FileService; public faucetService: FaucetService; + public accountService: AccountService; public url: string; public wallet: Wallet | Signer | null; @@ -157,6 +141,10 @@ export class VocdoniSDKClient { chainService: this.chainService, }); this.cspService = new CspService({}); + this.accountService = new AccountService({ + url: this.url, + chainService: this.chainService, + }); } /** @@ -175,27 +163,15 @@ export class VocdoniSDKClient { * @returns {Promise} */ async fetchAccountInfo(address?: string): Promise { - let accountData; if (!this.wallet && !address) { throw Error('No account set'); } else if (address) { - accountData = await AccountAPI.info(this.url, address); + this.accountData = await this.accountService.fetchAccountInfo(address); } else { - accountData = await this.wallet.getAddress().then((address) => AccountAPI.info(this.url, address)); + this.accountData = await this.wallet + .getAddress() + .then((address) => this.accountService.fetchAccountInfo(address)); } - - this.accountData = accountData; - this.accountData.account = Account.build({ - languages: accountData.metadata?.languages, - name: accountData.metadata?.name, - description: accountData.metadata?.description, - feed: accountData.metadata?.newsFeed, - header: accountData.metadata?.media?.header, - avatar: accountData.metadata?.media?.avatar, - logo: accountData.metadata?.media?.logo, - meta: Object.entries(accountData.metadata?.meta ?? []).map(([key, value]) => ({ key, value })), - }); - return this.accountData; } @@ -257,10 +233,10 @@ export class VocdoniSDKClient { ): Promise { return wallet .getAddress() - .then((address) => Promise.all([AnonymousService.calcSik(address, sik, password), this.fetchChainId()])) - .then(([calculatedSIK, chainId]) => { + .then((address) => AnonymousService.calcSik(address, sik, password)) + .then((calculatedSIK) => { const registerSIKTx = AccountCore.generateRegisterSIKTransaction(electionId, calculatedSIK, censusProof); - return AccountCore.signTransaction(registerSIKTx, chainId, wallet); + return this.accountService.signTransaction(registerSIKTx, wallet); }) .then((signedTx) => this.chainService.submitTx(signedTx)) .then((hash) => this.waitForTransaction(hash)); @@ -370,12 +346,12 @@ export class VocdoniSDKClient { */ private setAccountInfo(promAccountData: Promise<{ tx: Uint8Array; metadata: string }>): Promise { const accountTx = promAccountData.then((setAccountInfoTx) => - AccountCore.signTransaction(setAccountInfoTx.tx, this.chainService.chainData.chainId, this.wallet) + this.accountService.signTransaction(setAccountInfoTx.tx, this.wallet) ); return Promise.all([promAccountData, accountTx]) - .then((accountInfo) => AccountAPI.setInfo(this.url, accountInfo[1], accountInfo[0].metadata)) - .then((txData) => this.waitForTransaction(txData.txHash)) + .then((accountInfo) => this.accountService.setInfo(accountInfo[1], accountInfo[0].metadata)) + .then((txHash) => this.waitForTransaction(txHash)) .then(() => this.fetchAccountInfo()); } @@ -430,11 +406,11 @@ export class VocdoniSDKClient { const faucet = faucetPackage ? Promise.resolve(faucetPackage) : this.wallet.getAddress().then((address) => this.faucetService.fetchPayload(address)); - return Promise.all([this.fetchAccountInfo(), faucet, this.fetchChainId()]) - .then(([account, faucet, chainId]) => { + return Promise.all([this.fetchAccountInfo(), faucet]) + .then(([account, faucet]) => { const faucetPackage = this.faucetService.parseFaucetPackage(faucet); const collectFaucetTx = AccountCore.generateCollectFaucetTransaction(account, faucetPackage); - return AccountCore.signTransaction(collectFaucetTx, chainId, this.wallet); + return this.accountService.signTransaction(collectFaucetTx, this.wallet); }) .then((signedTx) => this.chainService.submitTx(signedTx)) .then((hash) => this.waitForTransaction(hash)) diff --git a/src/core/account.ts b/src/core/account.ts index 9a5ca811..b9f8c774 100644 --- a/src/core/account.ts +++ b/src/core/account.ts @@ -10,11 +10,10 @@ import { TxType, } from '@vocdoni/proto/vochain'; import { Buffer } from 'buffer'; -import { AccountData } from '../client'; import { Account, AccountMetadata } from '../types'; import { TransactionCore } from './transaction'; import { strip0x } from '../util/common'; -import { CensusProof, FaucetPackage } from '../services'; +import { AccountData, CensusProof, FaucetPackage } from '../services'; export abstract class AccountCore extends TransactionCore { /** diff --git a/src/core/election.ts b/src/core/election.ts index d8cb84f1..08e2f0bd 100644 --- a/src/core/election.ts +++ b/src/core/election.ts @@ -1,4 +1,3 @@ -import { AccountData } from '../client'; import { CensusOrigin, NewProcessTx, @@ -12,7 +11,7 @@ import { AllElectionStatus, CensusType, ElectionStatus, UnpublishedElection } fr import { TransactionCore } from './transaction'; import { Buffer } from 'buffer'; import { strip0x } from '../util/common'; -import { ChainCosts, ChainData } from '../services'; +import { AccountData, ChainCosts, ChainData } from '../services'; export abstract class ElectionCore extends TransactionCore { private static readonly VOCHAIN_BLOCK_TIME_IN_SECONDS = 12; diff --git a/src/services/account.ts b/src/services/account.ts new file mode 100644 index 00000000..e20bcf3a --- /dev/null +++ b/src/services/account.ts @@ -0,0 +1,90 @@ +import { Service, ServiceProperties } from './service'; +import { ChainService } from './chain'; +import { Account } from '../types'; +import { AccountAPI } from '../api'; +import invariant from 'tiny-invariant'; +import { Wallet } from '@ethersproject/wallet'; +import { Signer } from '@ethersproject/abstract-signer'; +import { AccountCore } from '../core/account'; + +interface AccountServiceProperties { + chainService: ChainService; +} + +type AccountServiceParameters = ServiceProperties & AccountServiceProperties; + +/** + * @typedef AccountData + * @property {string} address + * @property {number} balance + * @property {number} nonce + * @property {number} electionIndex + * @property {string | null} infoURL + * @property {Account} account + */ +export type AccountData = { + address: string; + balance: number; + nonce: number; + electionIndex: number; + infoURL?: string; + account: Account; +}; + +export class AccountService extends Service implements AccountServiceProperties { + public chainService: ChainService; + + /** + * Instantiate the election service. + * + * @param {Partial} params The service parameters + */ + constructor(params: Partial) { + super(); + Object.assign(this, params); + } + + /** + * Fetches account information. + * + * @param {string} address The account address to fetch the information + * @returns {Promise} + */ + async fetchAccountInfo(address: string): Promise { + invariant(this.url, 'No URL set'); + let accountData; + accountData = await AccountAPI.info(this.url, address); + + accountData.account = Account.build({ + languages: accountData.metadata?.languages, + name: accountData.metadata?.name, + description: accountData.metadata?.description, + feed: accountData.metadata?.newsFeed, + header: accountData.metadata?.media?.header, + avatar: accountData.metadata?.media?.avatar, + logo: accountData.metadata?.media?.logo, + meta: Object.entries(accountData.metadata?.meta ?? []).map(([key, value]) => ({ key, value })), + }); + + return accountData; + } + + /** + * Updates an account with information + * + * @param {string} tx The transaction for setting the account + * @param {string} metadata The account metadata + * @returns {Promise} The transaction hash + */ + setInfo(tx: string, metadata: string): Promise { + invariant(this.url, 'No URL set'); + return AccountAPI.setInfo(this.url, tx, metadata).then((response) => response.txHash); + } + + async signTransaction(tx: Uint8Array, walletOrSigner: Wallet | Signer): Promise { + invariant(this.chainService, 'No chain service set'); + return this.chainService + .fetchChainData() + .then((chainData) => AccountCore.signTransaction(tx, chainData.chainId, walletOrSigner)); + } +} diff --git a/src/services/index.ts b/src/services/index.ts index 07b1a6a3..54fec00f 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,3 +1,4 @@ +export * from './account'; export * from './census'; export * from './chain'; export * from './csp';