Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
147 changes: 19 additions & 128 deletions packages/bitcore-node/src/modules/moralis/api/csp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os from 'os';
import { Web3 } from '@bitpay-labs/crypto-wallet-core';
import { LRUCache } from 'lru-cache';
import request from 'request';
import config from '../../../config';
Expand All @@ -11,7 +10,14 @@ import { WalletAddressStorage } from '../../../models/walletAddress';
import { BaseEVMStateProvider, BuildWalletTxsStreamParams } from '../../../providers/chain-state/evm/api/csp';
import { EVMBlockStorage } from '../../../providers/chain-state/evm/models/block';
import { EVMTransactionStorage } from '../../../providers/chain-state/evm/models/transaction';
import { EVMTransactionJSON, GethTraceCall, IEVMBlock, IEVMTransactionTransformed, Transaction } from '../../../providers/chain-state/evm/types';
import { EVMTransactionJSON, IEVMBlock, IEVMTransactionTransformed } from '../../../providers/chain-state/evm/types';
import {
buildMoralisQueryString,
formatMoralisChainId,
transformMoralisQueryParams,
transformMoralisTokenTransfer,
transformMoralisTransaction
} from '../../../providers/chain-state/external/adapters/moralis-utils';
import { ExternalApiStream } from '../../../providers/chain-state/external/streams/apiStream';
import { IBlock } from '../../../types/Block';
import { ChainId, ChainNetwork } from '../../../types/ChainNetwork';
Expand Down Expand Up @@ -242,8 +248,8 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
throw new Error('Invalid chainId');
}

const query = this._transformQueryParams({ chainId, args: { date } });
const queryStr = this._buildQueryString(query);
const query = transformMoralisQueryParams({ chainId, args: { date } });
const queryStr = buildMoralisQueryString(query);

return new Promise<number>((resolve, reject) => {
request({
Expand All @@ -267,7 +273,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
async _getTransactionFromMoralis(params: StreamTransactionParams & ChainId) {
const { chain, network, chainId, txId } = params;

const query = this._buildQueryString({ chain: chainId, include: 'internal_transactions' });
const query = buildMoralisQueryString({ chain: formatMoralisChainId(chainId), include: 'internal_transactions' });

return new Promise<IEVMTransactionTransformed | null>((resolve, reject) => {
request({
Expand All @@ -286,7 +292,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
if (tx.message === 'No transaction found') {
return resolve(null);
}
return resolve(this._transformTransaction({ chain, network, ...tx }));
return resolve(transformMoralisTransaction({ chain, network, ...tx }));
});
});
}
Expand All @@ -304,15 +310,15 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
throw new Error('Invalid chainId');
}

const query = this._transformQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = this._buildQueryString({
const query = transformMoralisQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = buildMoralisQueryString({
...query,
order: args.order || 'DESC', // default to descending order
Comment thread
leolambo marked this conversation as resolved.
Outdated
limit: args.pageSize || 10, // limit per request/page. total limit (args.limit) is checked in apiStream._read()
include: 'internal_transactions'
});
args.transform = (tx) => {
const _tx: any = this._transformTransaction({ chain, network, ...tx });
const _tx: any = transformMoralisTransaction({ chain, network, ...tx });
const confirmations = this._calculateConfirmations(tx, args.tipHeight);
return EVMTransactionStorage._apiTransform({ ..._tx, confirmations }, { object: true }) as EVMTransactionJSON;
Comment thread
leolambo marked this conversation as resolved.
};
Expand All @@ -335,15 +341,15 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
throw new Error('Invalid chainId');
}

const queryTransform = this._transformQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = this._buildQueryString({
const queryTransform = transformMoralisQueryParams({ chainId, args }); // throws if no chain or network
const queryStr = buildMoralisQueryString({
...queryTransform,
order: args.order || 'DESC', // default to descending order
limit: args.pageSize || 10, // limit per request/page. total limit (args.limit) is checked in apiStream._read()
contract_addresses: [tokenAddress],
Comment thread
leolambo marked this conversation as resolved.
});
args.transform = (tx) => {
const _tx: any = this._transformTokenTransfer({ chain, network, ...tx });
const _tx: any = transformMoralisTokenTransfer({ chain, network, ...tx });
const confirmations = this._calculateConfirmations(tx, args.tipHeight);
Comment thread
leolambo marked this conversation as resolved.
Outdated
return EVMTransactionStorage._apiTransform({ ..._tx, confirmations }, { object: true }) as EVMTransactionJSON;
};
Expand All @@ -355,96 +361,6 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
);
}

private _transformTransaction(tx) {
const txid = tx.hash || tx.transaction_hash; // erc20 transfer txs have transaction_hash
try {
const transformed = {
chain: tx.chain,
network: tx.network,
txid,
blockHeight: Number(tx.block_number ?? tx.blockNumber),
blockHash: tx.block_hash ?? tx.blockHash,
blockTime: new Date(tx.block_timestamp ?? tx.blockTimestamp),
blockTimeNormalized: new Date(tx.block_timestamp ?? tx.blockTimestamp),
value: tx.value,
gasLimit: tx.gas ?? 0,
gasPrice: tx.gas_price ?? tx.gasPrice ?? 0,
fee: Number(tx.receipt_gas_used ?? tx.receiptGasUsed ?? 0) * Number(tx.gas_price ?? tx.gasPrice ?? 0),
nonce: tx.nonce,
to: Web3.utils.toChecksumAddress(tx.to_address ?? tx.toAddress),
from: Web3.utils.toChecksumAddress(tx.from_address ?? tx.fromAddress),
data: tx.input,
internal: [],
calls: tx?.internal_transactions?.map(t => this._transformInternalTransaction(t)) || [],
effects: [],
category: tx.category,
wallets: [],
transactionIndex: tx.transaction_index ?? tx.transactionIndex
} as IEVMTransactionTransformed;
EVMTransactionStorage.addEffectsToTxs([transformed]);
return transformed;
} catch (e: any) {
logger.error('Error transforming transaction from Moralis: %o -- %o', txid || tx, e.stack || e.message || e);
throw e;
}
}

private _transformInternalTransaction(tx) {
return {
from: Web3.utils.toChecksumAddress(tx.from),
to: Web3.utils.toChecksumAddress(tx.to),
gas: tx.gas,
gasUsed: tx.gas_used,
input: tx.input,
output: tx.output,
type: tx.type,
value: tx.value,
abiType: EVMTransactionStorage.abiDecode(tx.input)
} as GethTraceCall;
}

private _transformTokenTransfer(transfer) {
const _transfer = this._transformTransaction(transfer);
return {
..._transfer,
transactionHash: transfer.transaction_hash,
transactionIndex: transfer.transaction_index,
contractAddress: transfer.contract_address ?? transfer.address,
name: transfer.token_name
} as Partial<Transaction> | any;
}

private _transformQueryParams(params) {
const { chainId, args } = params;
const query = {
chain: this._formatChainId(chainId),
} as any;
if (args) {
if (args.startBlock || args.endBlock) {
if (args.startBlock) {
query.from_block = Number(args.startBlock);
}
if (args.endBlock) {
query.to_block = Number(args.endBlock);
}
} else {
if (args.startDate) {
query.from_date = args.startDate;
}
if (args.endDate) {
query.to_date = args.endDate;
}
}
if (args.direction) {
query.order = Number(args.direction) > 0 ? 'ASC' : 'DESC';
}
if (args.date) {
query.date = new Date(args.date).getTime();
}
}
return query;
}

private _calculateConfirmations(tx, tip) {
let confirmations = 0;
if (tx.blockHeight && tx.blockHeight >= 0) {
Expand All @@ -453,31 +369,6 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
return confirmations;
}

private _buildQueryString(params: Record<string, any>): string {
const query: string[] = [];

if (params.chain) {
params.chain = this._formatChainId(params.chain);
}

for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
// add array values in the form of key[i]=value
if (value[i] != null) query.push(`${key}%5B${i}%5D=${value[i]}`);
}
} else if (value != null) {
query.push(`${key}=${value}`);
}
}

return query.length ? `?${query.join('&')}` : '';
}

private _formatChainId(chainId) {
return '0x' + parseInt(chainId).toString(16);
}

/**
* Request wrapper for moralis Streams (subscriptions)
* @param method
Expand Down Expand Up @@ -509,7 +400,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {

async createAddressSubscription(params: ChainNetwork & ChainId) {
const { chain, network, chainId } = params;
const _chainId = this._formatChainId(chainId);
const _chainId = formatMoralisChainId(chainId);

const result: any = await this._subsRequest('PUT', this.baseStreamUrl, {
description: `Bitcore ${_chainId} - ${os.hostname()} - addresses`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AlchemyAdapter } from './alchemy';
import { MoralisAdapter } from './moralis';
import type { IIndexedAPIAdapter } from './IIndexedAPIAdapter';
import type { IMultiProviderConfig } from '../../../../types/Config';

export class AdapterFactory {
private static registry: Record<string, new (config: IMultiProviderConfig) => IIndexedAPIAdapter> = {
alchemy: AlchemyAdapter
alchemy: AlchemyAdapter,
moralis: MoralisAdapter
};

static createAdapter(providerConfig: IMultiProviderConfig): IIndexedAPIAdapter {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Web3 } from '@bitpay-labs/crypto-wallet-core';
import { EVMTransactionStorage } from '../../evm/models/transaction';
import type { IEVMTransactionTransformed } from '../../evm/types';

/**
* Shared Moralis transformation and query-building utilities.
* Used by both MoralisAdapter (multi-provider) and MoralisStateProvider (standalone CSP).
*/

export function transformMoralisTransaction(tx: any): IEVMTransactionTransformed {
const txid = tx.hash || tx.transaction_hash;
const transformed = {
chain: tx.chain,
network: tx.network,
txid,
blockHeight: Number(tx.block_number ?? tx.blockNumber),
blockHash: tx.block_hash ?? tx.blockHash,
blockTime: new Date(tx.block_timestamp ?? tx.blockTimestamp),
blockTimeNormalized: new Date(tx.block_timestamp ?? tx.blockTimestamp),
value: tx.value,
gasLimit: tx.gas ?? 0,
gasPrice: tx.gas_price ?? tx.gasPrice ?? 0,
fee: Number(tx.receipt_gas_used ?? tx.receiptGasUsed ?? 0) * Number(tx.gas_price ?? tx.gasPrice ?? 0),
nonce: tx.nonce,
to: (tx.to_address ?? tx.toAddress)
? Web3.utils.toChecksumAddress(tx.to_address ?? tx.toAddress)
: '',
from: Web3.utils.toChecksumAddress(tx.from_address ?? tx.fromAddress),
data: tx.input,
internal: [],
calls: tx?.internal_transactions?.map((t: any) => transformMoralisInternalTx(t)) || [],
effects: [],
category: tx.category,
wallets: [],
transactionIndex: tx.transaction_index ?? tx.transactionIndex
} as IEVMTransactionTransformed;
EVMTransactionStorage.addEffectsToTxs([transformed]);
return transformed;
}

export function transformMoralisInternalTx(tx: any) {
return {
from: Web3.utils.toChecksumAddress(tx.from),
to: Web3.utils.toChecksumAddress(tx.to),
gas: tx.gas,
gasUsed: tx.gas_used,
input: tx.input,
output: tx.output,
type: tx.type,
value: tx.value,
abiType: EVMTransactionStorage.abiDecode(tx.input)
};
}

export function transformMoralisTokenTransfer(transfer: any) {
const base = transformMoralisTransaction(transfer);
return {
...base,
transactionHash: transfer.transaction_hash,
transactionIndex: transfer.transaction_index,
contractAddress: transfer.contract_address ?? transfer.address,
name: transfer.token_name
};
}

export function transformMoralisQueryParams(params: { chainId: string | bigint; args: any }) {
const { chainId, args } = params;
const query: any = { chain: formatMoralisChainId(chainId) };
if (args) {
if (args.startBlock || args.endBlock) {
if (args.startBlock) query.from_block = Number(args.startBlock);
if (args.endBlock) query.to_block = Number(args.endBlock);
} else {
if (args.startDate) query.from_date = args.startDate;
if (args.endDate) query.to_date = args.endDate;
}
if (args.direction) {
query.order = Number(args.direction) > 0 ? 'ASC' : 'DESC';
}
if (args.date) {
query.date = new Date(args.date).getTime();
}
}
return query;
}

export function buildMoralisQueryString(params: Record<string, any>): string {
const query: string[] = [];
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
if (value[i] != null) query.push(`${key}%5B${i}%5D=${value[i]}`);
}
} else if (value != null) {
query.push(`${key}=${value}`);
}
}
return query.length ? `?${query.join('&')}` : '';
}

export function formatMoralisChainId(chainId: string | bigint): string {
const str = String(chainId);
if (str.startsWith('0x')) return str;
return '0x' + BigInt(str).toString(16);
}
Loading