import Arweave from 'arweave' import Transaction from 'arweave/node/lib/transaction' import _ from 'lodash' import { ArTxDataResult, ArTxsResult, InnerDataResult, OuterDataResult } from './types' import { AR_DECIMAL, GQLResultInterface, GQLTransactionsResultInterface, convertTimestampToDateFormat, convertTimestampToTimeFormat, } from './utils' export interface ReqVariables { ownersFilter: Array first: number after?: string } export default class ArweaveTxClient { ownerHasNextPage: boolean | undefined recipientHasNextPage: boolean | undefined ownerCursor: string recipientCursor: string constructor() { this.ownerHasNextPage = undefined this.recipientHasNextPage = undefined this.ownerCursor = '' this.recipientCursor = '' } async getTxData(arweave: Arweave, hash: string): Promise { const tx: Transaction = await arweave.transactions.get(hash) if (!tx) throw Error(`Failed to get transaction data (tx-hash: ${hash})`) const fromAddress = await arweave.wallets.ownerToAddress(tx.owner) const resultData: ArTxDataResult = { transaction_hash: tx.id, from: fromAddress, to: tx.target, value: Number(tx.quantity) / Math.pow(10, AR_DECIMAL), gas_price: (Number(tx.reward) / Math.pow(10, AR_DECIMAL)).toFixed(AR_DECIMAL), signature: tx.signature, } return resultData } async getTxsHistory(arweave: Arweave, owner: string, limit?: number) { let ownerVariables: ReqVariables if (this.ownerCursor !== '') { ownerVariables = { ownersFilter: [owner], first: limit ? limit : 100, after: this.ownerCursor, } } else { ownerVariables = { ownersFilter: [owner], first: limit ? limit : 100, } } let recipientVariables: ReqVariables if (this.recipientCursor !== '') { recipientVariables = { ownersFilter: [owner], first: limit ? limit : 100, after: this.recipientCursor, } } else { recipientVariables = { ownersFilter: [owner], first: limit ? limit : 100, } } let txs: GQLTransactionsResultInterface const outerTxsData: OuterDataResult[] = [] const innerTxsData: InnerDataResult[] = [] const txsDataFinal: ArTxsResult = { outer: outerTxsData, inner: innerTxsData, } if (this.ownerHasNextPage === true || this.ownerHasNextPage === undefined) { txs = await this.getOwnersTxsQueryResult(arweave, ownerVariables) this.ownerHasNextPage = txs.pageInfo.hasNextPage if (txs.edges.length > 0) { this.ownerCursor = txs.edges[txs.edges.length - 1].cursor txs.edges.map((res) => { const outerResult: OuterDataResult = { timestamp: res.node.block ? `${res.node.block.timestamp}` : '-', transaction_hash: res.node.id, block: res.node.block ? `${res.node.block.height}` : '-', from: res.node.owner.address, to: res.node.recipient, value: res.node.quantity.ar, gas_price: res.node.fee.ar, date: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToDateFormat(res.node.block.timestamp) : '-', time: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToTimeFormat(res.node.block.timestamp) : '-', } outerTxsData.push(outerResult) const innerResult: InnerDataResult = { timestamp: res.node.block ? `${res.node.block.timestamp}` : '-', transaction_hash: res.node.id, block: res.node.block ? `${res.node.block.height}` : '-', from: res.node.owner.address, to: res.node.recipient, value: res.node.quantity.ar, gas_price: res.node.fee.ar, date: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToDateFormat(res.node.block.timestamp) : '-', time: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToTimeFormat(res.node.block.timestamp) : '-', signature: res.node.signature, block_hash: res.node.block ? res.node.block.id : '-', } innerTxsData.push(innerResult) }) } } if (this.recipientHasNextPage === true || this.recipientHasNextPage === undefined) { txs = await this.getRecipientsTxsQueryResult(arweave, recipientVariables) this.recipientHasNextPage = txs.pageInfo.hasNextPage if (txs.edges.length > 0) { this.recipientCursor = txs.edges[txs.edges.length - 1].cursor txs.edges.map((res) => { const outerResult: OuterDataResult = { timestamp: res.node.block ? `${res.node.block.timestamp}` : '-', transaction_hash: res.node.id, block: res.node.block ? `${res.node.block.height}` : '-', from: res.node.owner.address, to: res.node.recipient, value: res.node.quantity.ar, gas_price: res.node.fee.ar, date: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToDateFormat(res.node.block.timestamp) : '-', time: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToTimeFormat(res.node.block.timestamp) : '-', } outerTxsData.push(outerResult) const innerResult: InnerDataResult = { timestamp: res.node.block ? `${res.node.block.timestamp}` : '-', transaction_hash: res.node.id, block: res.node.block ? `${res.node.block.height}` : '-', from: res.node.owner.address, to: res.node.recipient, value: res.node.quantity.ar, gas_price: res.node.fee.ar, date: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToDateFormat(res.node.block.timestamp) : '-', time: res.node.block && res.node.block.timestamp !== (null || undefined) ? convertTimestampToTimeFormat(res.node.block.timestamp) : '-', signature: res.node.signature, block_hash: res.node.block ? res.node.block.id : '-', } innerTxsData.push(innerResult) }) } } if (outerTxsData.length !== 0) { const outerLodash: OuterDataResult[] = _.orderBy(outerTxsData, 'timestamp', 'desc') txsDataFinal.outer = outerLodash } if (innerTxsData.length !== 0) { const innerLodash: InnerDataResult[] = _.orderBy(innerTxsData, 'timestamp', 'desc') txsDataFinal.inner = innerLodash } return txsDataFinal } async getOwnersTxsQueryResult(arweave: Arweave, variables: ReqVariables): Promise { const query = `query Transactions($ownersFilter: [String!], $first: Int!, $after: String) { transactions(owners: $ownersFilter, first: $first, sort: HEIGHT_ASC, after: $after) { pageInfo { hasNextPage } edges { node { id owner { address } recipient block { height id timestamp } fee { ar winston } quantity { ar winston } parent { id } signature } cursor } } }` const response = await arweave.api.post('graphql', { query, variables, }) if (response.status !== 200) { throw new Error(`Unable to retrieve transactions. Arweave gateway responded with status ${response.status}.`) } const data: GQLResultInterface = response.data const txs = data.data.transactions return txs } async getRecipientsTxsQueryResult( arweave: Arweave, variables: ReqVariables, ): Promise { const query = `query Transactions($ownersFilter: [String!], $first: Int!, $after: String) { transactions(recipients: $ownersFilter, first: $first, sort: HEIGHT_ASC, after: $after) { pageInfo { hasNextPage } edges { node { id owner { address } recipient tags { name value } block { height id timestamp } fee { ar winston } quantity { ar winston } parent { id } signature } cursor } } }` const response = await arweave.api.post('graphql', { query, variables, }) if (response.status !== 200) { throw new Error(`Unable to retrieve transactions. Arweave gateway responded with status ${response.status}.`) } const data: GQLResultInterface = response.data const txs: GQLTransactionsResultInterface = data.data.transactions return txs } }