diff --git a/package.json b/package.json index d927b29bc..6c13c7217 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "bitcoinjs-lib": "^6.0.2", "bs58": "^5.0.0", "chart.js": "^3.8.0", - "chronik-client-cashtokens": "^0.28.2-alpha", + "chronik-client-cashtokens": "^3.1.1-rc0", "cors": "^2.8.5", "cross-env": "^7.0.2", "dotenv-cli": "^5.1.0", diff --git a/pages/api/address/balance/[address].ts b/pages/api/address/balance/[address].ts index d6848eaec..6bc0b3ca5 100644 --- a/pages/api/address/balance/[address].ts +++ b/pages/api/address/balance/[address].ts @@ -4,7 +4,6 @@ import { parseAddress } from 'utils/validators' import Cors from 'cors' import { runMiddleware, satoshisToUnit } from 'utils/index' import xecaddr from 'xecaddrjs' -import { Prisma } from '@prisma/client' import { multiBlockchainClient } from 'services/chronikService' const { ADDRESS_NOT_PROVIDED_400 } = RESPONSE_MESSAGES @@ -18,7 +17,7 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise try { const address = parseAddress(req.query.address as string) const response = await multiBlockchainClient.getBalance(address) - const balance = await satoshisToUnit(new Prisma.Decimal(response), xecaddr.detectAddressFormat(address)) + const balance = await satoshisToUnit(response, xecaddr.detectAddressFormat(address)) res.status(200).send(balance) } catch (err: any) { switch (err.message) { diff --git a/services/chronikService.ts b/services/chronikService.ts index 66406c4ed..894019ac0 100644 --- a/services/chronikService.ts +++ b/services/chronikService.ts @@ -1,7 +1,7 @@ -import { BlockInfo_InNode, ChronikClientNode, ScriptType_InNode, ScriptUtxo_InNode, Tx_InNode, WsConfig_InNode, WsEndpoint_InNode, WsMsgClient, WsSubScriptClient } from 'chronik-client-cashtokens' +import { BlockInfo, ChronikClient, ScriptType, ScriptUtxo, Tx, WsConfig, WsEndpoint, WsMsgClient, WsSubScriptClient } from 'chronik-client-cashtokens' import { encode, decode } from 'ecashaddrjs' import bs58 from 'bs58' -import { AddressWithTransaction, BlockchainInfo, BlockInfo, TransactionDetails, ProcessedMessages, SubbedAddressesLog, SyncAndSubscriptionReturn, SubscriptionReturn } from 'types/chronikTypes' +import { AddressWithTransaction, BlockchainInfo, TransactionDetails, ProcessedMessages, SubbedAddressesLog, SyncAndSubscriptionReturn, SubscriptionReturn, SimpleBlockInfo } from 'types/chronikTypes' import { CHRONIK_MESSAGE_CACHE_DELAY, RESPONSE_MESSAGES, XEC_TIMESTAMP_THRESHOLD, XEC_NETWORK_ID, BCH_NETWORK_ID, BCH_TIMESTAMP_THRESHOLD, FETCH_DELAY, FETCH_N, KeyValueT, NETWORK_IDS_FROM_SLUGS, SOCKET_MESSAGES, NETWORK_IDS, NETWORK_TICKERS, MainNetworkSlugsType, MAX_MEMPOOL_TXS_TO_PROCESS_AT_A_TIME, MEMPOOL_PROCESS_DELAY, CHRONIK_INITIALIZATION_DELAY } from 'constants/index' import { productionAddresses } from 'prisma/seeds/addresses' import { @@ -107,10 +107,10 @@ export function getNullDataScriptData (outputScript: string): OpReturnData | nul } export class ChronikBlockchainClient { - chronik: ChronikClientNode + chronik: ChronikClient networkId: number networkSlug: string - chronikWSEndpoint: WsEndpoint_InNode + chronikWSEndpoint: WsEndpoint confirmedTxsHashesFromLastBlock: string[] wsEndpoint: Socket CHRONIK_MSG_PREFIX: string @@ -127,7 +127,7 @@ export class ChronikBlockchainClient { this.mempoolTxsBeingProcessed = 0 this.networkSlug = networkSlug this.networkId = NETWORK_IDS_FROM_SLUGS[networkSlug] - this.chronik = new ChronikClientNode([config.networkBlockchainURLs[networkSlug]]) + this.chronik = new ChronikClient([config.networkBlockchainURLs[networkSlug]]) this.chronikWSEndpoint = this.chronik.ws(this.getWsConfig()) this.confirmedTxsHashesFromLastBlock = [] void this.chronikWSEndpoint.waitForOpen() @@ -197,14 +197,14 @@ export class ChronikBlockchainClient { return { height: blockchainInfo.tipHeight, hash: blockchainInfo.tipHash } } - async getBlockInfo (networkSlug: string, height: number): Promise { + async getBlockInfo (networkSlug: string, height: number): Promise { this.validateNetwork(networkSlug) - const blockInfo: BlockInfo_InNode = (await this.chronik.block(height)).blockInfo + const blockInfo: BlockInfo = (await this.chronik.block(height)).blockInfo return { hash: blockInfo.hash, height: blockInfo.height, timestamp: blockInfo.timestamp } } private txThesholdFilter (address: Address) { - return (t: Tx_InNode, _index: number, _array: Tx_InNode[]): boolean => { + return (t: Tx, _index: number, _array: Tx[]): boolean => { return ( t.block === undefined || (t.block?.timestamp >= XEC_TIMESTAMP_THRESHOLD && address.networkId === XEC_NETWORK_ID) || @@ -213,16 +213,16 @@ export class ChronikBlockchainClient { } } - private async getTransactionAmountAndData (transaction: Tx_InNode, addressString: string): Promise<{amount: Prisma.Decimal, opReturn: string}> { - let totalOutput = 0 - let totalInput = 0 + private async getTransactionAmountAndData (transaction: Tx, addressString: string): Promise<{amount: Prisma.Decimal, opReturn: string}> { + let totalOutput = 0n + let totalInput = 0n const addressFormat = xecaddr.detectAddressFormat(addressString) const script = toHash160(addressString).hash160 let opReturn = '' for (const output of transaction.outputs) { if (output.outputScript.includes(script)) { - totalOutput += output.value + totalOutput += output.sats } if (opReturn === '') { const nullScriptData = getNullDataScriptData(output.outputScript) @@ -235,17 +235,17 @@ export class ChronikBlockchainClient { } for (const input of transaction.inputs) { if (input?.outputScript?.includes(script) === true) { - totalInput += input.value + totalInput += input.sats } } - const satoshis = new Prisma.Decimal(totalOutput).minus(totalInput) + const satoshis = totalOutput - totalInput return { amount: await satoshisToUnit(satoshis, addressFormat), opReturn } } - private async getTransactionFromChronikTransaction (transaction: Tx_InNode, address: Address): Promise { + private async getTransactionFromChronikTransaction (transaction: Tx, address: Address): Promise { const { amount, opReturn } = await this.getTransactionAmountAndData(transaction, address.address) return { hash: transaction.txid, @@ -257,7 +257,7 @@ export class ChronikBlockchainClient { } } - public async getPaginatedTxs (addressString: string, page: number, pageSize: number): Promise { + public async getPaginatedTxs (addressString: string, page: number, pageSize: number): Promise { const { type, hash160 } = toHash160(addressString) return (await this.chronik.script(type, hash160).history(page, pageSize)).txs } @@ -314,15 +314,15 @@ export class ChronikBlockchainClient { await updateLastSynced(addressString) } - private async getUtxos (address: string): Promise { + private async getUtxos (address: string): Promise { const { type, hash160 } = toHash160(address) const scriptsUtxos = await this.chronik.script(type, hash160).utxos() return scriptsUtxos.utxos } - public async getBalance (address: string): Promise { + public async getBalance (address: string): Promise { const utxos = await this.getUtxos(address) - return utxos.reduce((acc, utxo) => acc + utxo.value, 0) + return utxos.reduce((acc, utxo) => acc + utxo.sats, 0n) } async getTransactionDetails (hash: string): Promise { @@ -341,20 +341,20 @@ export class ChronikBlockchainClient { } for (const input of tx.inputs) { details.inputs.push({ - value: new Prisma.Decimal(input.value), + value: input.sats, address: outputScriptToAddress(this.networkSlug, input.outputScript) }) } for (const output of tx.outputs) { details.outputs.push({ - value: new Prisma.Decimal(output.value), + value: output.sats, address: outputScriptToAddress(this.networkSlug, output.outputScript) }) } return details } - private getWsConfig (): WsConfig_InNode { + private getWsConfig (): WsConfig { return { onMessage: (msg: WsMsgClient) => { void this.processWsMessage(msg) }, onError: (e: ws.ErrorEvent) => { console.log(`${this.CHRONIK_MSG_PREFIX}: Chronik webSocket error, type: ${e.type} | message: ${e.message} | error: ${e.error as string}`) }, @@ -365,19 +365,19 @@ export class ChronikBlockchainClient { } } - private getSortedInputAddresses (transaction: Tx_InNode): string[] { - const addressValueMap = new Map() + private getSortedInputAddresses (transaction: Tx): string[] { + const addressSatsMap = new Map() transaction.inputs.forEach((inp) => { const address = outputScriptToAddress(this.networkSlug, inp.outputScript) if (address !== undefined && address !== '') { - const currentValue = addressValueMap.get(address) ?? 0 - addressValueMap.set(address, currentValue + inp.value) + const currentValue = addressSatsMap.get(address) ?? 0n + addressSatsMap.set(address, currentValue + inp.sats) } }) - const sortedInputAddresses = Array.from(addressValueMap.entries()) - .sort(([, valueA], [, valueB]) => valueB - valueA) + const sortedInputAddresses = Array.from(addressSatsMap.entries()) + .sort(([, valueA], [, valueB]) => Number(valueB - valueA)) .map(([address]) => address) return sortedInputAddresses @@ -458,7 +458,7 @@ export class ChronikBlockchainClient { let page = 0 const pageSize = 200 let blockPageTxs = (await this.chronik.blockTxs(blockHash, page, pageSize)).txs - let blockTxsToSync: Tx_InNode[] = [] + let blockTxsToSync: Tx[] = [] while (blockPageTxs.length > 0 && blockTxsToSync.length !== this.confirmedTxsHashesFromLastBlock.length) { const thisBlockTxsToSync = blockPageTxs.filter(tx => this.confirmedTxsHashesFromLastBlock.includes(tx.txid)) blockTxsToSync = [...blockTxsToSync, ...thisBlockTxsToSync] @@ -481,13 +481,13 @@ export class ChronikBlockchainClient { } } - private getRelatedAddressesForTransaction (transaction: Tx_InNode): string[] { + private getRelatedAddressesForTransaction (transaction: Tx): string[] { const inputAddresses = transaction.inputs.map(inp => outputScriptToAddress(this.networkSlug, inp.outputScript)) const outputAddresses = transaction.outputs.map(out => outputScriptToAddress(this.networkSlug, out.outputScript)) return [...inputAddresses, ...outputAddresses].filter(a => a !== undefined) } - private async getAddressesForTransaction (transaction: Tx_InNode): Promise { + private async getAddressesForTransaction (transaction: Tx): Promise { const relatedAddresses = this.getRelatedAddressesForTransaction(transaction) const addressesFromStringArray = await fetchAddressesArray(relatedAddresses) const addressesWithTransactions: AddressWithTransaction[] = await Promise.all(addressesFromStringArray.map( @@ -628,14 +628,14 @@ export function fromHash160 (networkSlug: string, type: string, hash160: string) ) } -export function toHash160 (address: string): {type: ScriptType_InNode, hash160: string} { +export function toHash160 (address: string): {type: ScriptType, hash160: string} { try { const { type, hash } = decode(address) const legacyAdress = bs58.encode(hash) const addrHash160 = Buffer.from(bs58.decode(legacyAdress)).toString( 'hex' ) - return { type: type.toLowerCase() as ScriptType_InNode, hash160: addrHash160 } + return { type: type.toLowerCase() as ScriptType, hash160: addrHash160 } } catch (err) { console.log('[CHRONIK]: Error converting address to hash160') throw err @@ -781,7 +781,7 @@ class MultiBlockchainClient { return await this.clients[networkSlug as MainNetworkSlugsType].getLastBlockTimestamp() } - public async getBalance (address: string): Promise { + public async getBalance (address: string): Promise { const networkSlug = getAddressPrefix(address) return await this.clients[networkSlug as MainNetworkSlugsType].getBalance(address) } diff --git a/types/chronikTypes.ts b/types/chronikTypes.ts index 87baed2a7..82e47b866 100644 --- a/types/chronikTypes.ts +++ b/types/chronikTypes.ts @@ -2,7 +2,7 @@ import { Address, Prisma } from '@prisma/client' import { KeyValueT } from 'constants/index' interface InputOutput { - value: Prisma.Decimal + value: bigint address?: string } @@ -18,7 +18,7 @@ export interface BlockchainInfo { hash: Uint8Array | string } -export interface BlockInfo extends BlockchainInfo { +export interface SimpleBlockInfo extends BlockchainInfo { timestamp: number } diff --git a/utils/index.ts b/utils/index.ts index 64fd68e54..0ceee8170 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -40,11 +40,12 @@ export const getAddressPrefixed = function (addressString: string): string { return `${getAddressPrefix(addressString)}:${removeAddressPrefix(addressString)}` } -export async function satoshisToUnit (satoshis: Prisma.Decimal, networkFormat: string): Promise { +export async function satoshisToUnit (satoshis: bigint, networkFormat: string): Promise { + const decimal = new Prisma.Decimal(satoshis.toString()) if (networkFormat === xecaddr.Format.Xecaddr) { - return satoshis.dividedBy(1e2) + return decimal.dividedBy(1e2) } else if (networkFormat === xecaddr.Format.Cashaddr) { - return satoshis.dividedBy(1e8) + return decimal.dividedBy(1e8) } throw new Error(RESPONSE_MESSAGES.INVALID_ADDRESS_400.message) } diff --git a/yarn.lock b/yarn.lock index c86ed22a0..d188e07db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2807,14 +2807,14 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -chronik-client-cashtokens@^0.28.2-alpha: - version "0.28.2-alpha" - resolved "https://registry.yarnpkg.com/chronik-client-cashtokens/-/chronik-client-cashtokens-0.28.2-alpha.tgz#656a76202e821e68016a51ba039b1901fba91427" - integrity sha512-BPcXoKhm/wFIMge/NVbic7rgFCOudEqKOeW1IbNiKbtdqgLV+KmWTDOUjxDcJW9lEfclqwqOo/9qWL108vn6uQ== +chronik-client-cashtokens@^3.1.1-rc0: + version "3.1.1-rc0" + resolved "https://registry.yarnpkg.com/chronik-client-cashtokens/-/chronik-client-cashtokens-3.1.1-rc0.tgz#e5e4a3538e8010b70623974a731bf2712506c5e3" + integrity sha512-jnFnog+hVd9u8bik+sf2SRVwr4ETN8Gnq6HpFRdPkM5UtMu7boCjQVrKHOXKb9AyaCFKUnreK9mBEL/l9dAjMg== dependencies: "@types/ws" "^8.2.1" axios "^1.6.3" - ecashaddrjs "file:../.cache/yarn/v6/npm-chronik-client-cashtokens-0.28.2-alpha-656a76202e821e68016a51ba039b1901fba91427-integrity/node_modules/ecashaddrjs" + ecashaddrjs "file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" isomorphic-ws "^4.0.1" protobufjs "^6.8.8" ws "^8.3.0" @@ -3281,7 +3281,7 @@ ecashaddrjs@^1.0.7: dependencies: big-integer "1.6.36" -ecashaddrjs@^1.5.8, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-0.28.2-alpha-656a76202e821e68016a51ba039b1901fba91427-integrity/node_modules/ecashaddrjs": +ecashaddrjs@^1.5.8, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": version "1.5.8" resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-1.5.8.tgz#fff684d14a944cfd4bca56dd8dc3adbfce8016f9" integrity sha512-EBL052a1Hd1+wwaBQr3Q+jXY4XfH0jXR1tK360P8GQ9YJUZ5zLG+0I7yee8/QgHY04zdoCzDOY0fRlwqtqDhRg==