Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions pages/api/address/balance/[address].ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,7 +17,7 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise<void>
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) {
Expand Down
68 changes: 34 additions & 34 deletions services/chronikService.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -197,14 +197,14 @@ export class ChronikBlockchainClient {
return { height: blockchainInfo.tipHeight, hash: blockchainInfo.tipHash }
}

async getBlockInfo (networkSlug: string, height: number): Promise<BlockInfo> {
async getBlockInfo (networkSlug: string, height: number): Promise<SimpleBlockInfo> {
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) ||
Expand All @@ -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)
Expand All @@ -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<Prisma.TransactionUncheckedCreateInput> {
private async getTransactionFromChronikTransaction (transaction: Tx, address: Address): Promise<Prisma.TransactionUncheckedCreateInput> {
const { amount, opReturn } = await this.getTransactionAmountAndData(transaction, address.address)
return {
hash: transaction.txid,
Expand All @@ -257,7 +257,7 @@ export class ChronikBlockchainClient {
}
}

public async getPaginatedTxs (addressString: string, page: number, pageSize: number): Promise<Tx_InNode[]> {
public async getPaginatedTxs (addressString: string, page: number, pageSize: number): Promise<Tx[]> {
const { type, hash160 } = toHash160(addressString)
return (await this.chronik.script(type, hash160).history(page, pageSize)).txs
}
Expand Down Expand Up @@ -314,15 +314,15 @@ export class ChronikBlockchainClient {
await updateLastSynced(addressString)
}

private async getUtxos (address: string): Promise<ScriptUtxo_InNode[]> {
private async getUtxos (address: string): Promise<ScriptUtxo[]> {
const { type, hash160 } = toHash160(address)
const scriptsUtxos = await this.chronik.script(type, hash160).utxos()
return scriptsUtxos.utxos
}

public async getBalance (address: string): Promise<number> {
public async getBalance (address: string): Promise<bigint> {
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<TransactionDetails> {
Expand All @@ -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}`) },
Expand All @@ -365,19 +365,19 @@ export class ChronikBlockchainClient {
}
}

private getSortedInputAddresses (transaction: Tx_InNode): string[] {
const addressValueMap = new Map<string, number>()
private getSortedInputAddresses (transaction: Tx): string[] {
const addressSatsMap = new Map<string, bigint>()

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
Expand Down Expand Up @@ -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]
Expand All @@ -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<AddressWithTransaction[]> {
private async getAddressesForTransaction (transaction: Tx): Promise<AddressWithTransaction[]> {
const relatedAddresses = this.getRelatedAddressesForTransaction(transaction)
const addressesFromStringArray = await fetchAddressesArray(relatedAddresses)
const addressesWithTransactions: AddressWithTransaction[] = await Promise.all(addressesFromStringArray.map(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -781,7 +781,7 @@ class MultiBlockchainClient {
return await this.clients[networkSlug as MainNetworkSlugsType].getLastBlockTimestamp()
}

public async getBalance (address: string): Promise<number> {
public async getBalance (address: string): Promise<bigint> {
const networkSlug = getAddressPrefix(address)
return await this.clients[networkSlug as MainNetworkSlugsType].getBalance(address)
}
Expand Down
4 changes: 2 additions & 2 deletions types/chronikTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Address, Prisma } from '@prisma/client'
import { KeyValueT } from 'constants/index'

interface InputOutput {
value: Prisma.Decimal
value: bigint
address?: string
}

Expand All @@ -18,7 +18,7 @@ export interface BlockchainInfo {
hash: Uint8Array | string
}

export interface BlockInfo extends BlockchainInfo {
export interface SimpleBlockInfo extends BlockchainInfo {
timestamp: number
}

Expand Down
7 changes: 4 additions & 3 deletions utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Prisma.Decimal> {
export async function satoshisToUnit (satoshis: bigint, networkFormat: string): Promise<Prisma.Decimal> {
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)
}
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down