bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
220 lines (197 loc) • 8.12 kB
text/typescript
import { Readable } from 'stream';
import { Transaction } from 'web3-eth';
import { AbiItem } from 'web3-utils';
import { ChainStateProvider } from '../../';
import { Config } from '../../../../services/config';
import { IEVMNetworkConfig } from '../../../../types/Config';
import { StreamWalletTransactionsParams } from '../../../../types/namespaces/ChainStateProvider';
import { MultisigAbi } from '../abi/multisig';
import { MultisigRelatedFilterTransform } from '../api/multisigTransform';
import { PopulateEffectsTransform } from '../api/populateEffectsTransform';
import { PopulateReceiptTransform } from '../api/populateReceiptTransform';
import { EVMListTransactionsStream } from '../api/transform';
import { EVMBlockStorage } from '../models/block';
import { EVMTransactionStorage } from '../models/transaction';
import { EventLog } from '../types';
import { BaseEVMStateProvider } from './csp';
interface MULTISIGInstantiation
extends EventLog<{
[key: string]: string;
}> {}
interface MULTISIGTxInfo
extends EventLog<{
[key: string]: string;
}> {}
function getCSP(chain: string, network: string) {
return ChainStateProvider.get({ chain, network }) as BaseEVMStateProvider;
}
export class GnosisApi {
public gnosisFactories = {
'ETH': {
testnet: '0x2C992817e0152A65937527B774c7A99a84603045',
mainnet: '0x6e95C8E8557AbC08b46F3c347bA06F8dC012763f'
},
'MATIC': {
mainnet: '0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2'
}
};
private MULTISIG_TX_PROPOSAL_EXPIRE_TIME = 48 * 3600 * 1000;
async multisigFor(chain: string,network: string, address: string) {
const { web3 } = await getCSP(chain, network).getWeb3(network);
const contract = new web3.eth.Contract(MultisigAbi as AbiItem[], address);
return contract;
}
async getMultisigContractInstantiationInfo(
chain: string,
network: string,
sender: string,
txId: string
): Promise<Partial<Transaction>[]> {
const { web3 } = await getCSP(chain, network).getWeb3(network);
const networkConfig: IEVMNetworkConfig = Config.chainConfig({ chain: 'ETH', network });
const { gnosisFactory = this.gnosisFactories[chain][network] } = networkConfig;
let query = { chain, network, txid: txId };
const found = await EVMTransactionStorage.collection.findOne(query);
const blockHeight = found && found.blockHeight ? found.blockHeight : null;
if (!blockHeight || blockHeight < 0) return Promise.resolve([]);
const contract = await this.multisigFor(chain, network, gnosisFactory);
const contractInfo = await contract.getPastEvents('ContractInstantiation', {
fromBlock: web3.utils.toHex(blockHeight),
toBlock: web3.utils.toHex(blockHeight)
});
return this.convertMultisigContractInstantiationInfo(
contractInfo.filter(info => info.returnValues.sender.toLowerCase() === sender.toLowerCase())
);
}
convertMultisigContractInstantiationInfo(contractInstantiationInfo: Array<MULTISIGInstantiation>) {
return contractInstantiationInfo.map(this.convertContractInstantiationInfo);
}
convertContractInstantiationInfo(transfer: MULTISIGInstantiation) {
const { blockHash, blockNumber, transactionHash, returnValues, transactionIndex } = transfer;
return {
blockHash,
blockNumber,
transactionHash,
transactionIndex,
hash: transactionHash,
sender: returnValues['sender'],
instantiation: returnValues['instantiation']
} as Partial<Transaction>;
}
async getMultisigTxpsInfo(chain: string, network: string, multisigContractAddress: string): Promise<Partial<Transaction>[]> {
const contract = await this.multisigFor(chain, network, multisigContractAddress);
const time = Math.floor(Date.now()) - this.MULTISIG_TX_PROPOSAL_EXPIRE_TIME;
const [block] = await EVMBlockStorage.collection
.find({
chain,
network,
timeNormalized: { $gte: new Date(time) }
})
.limit(1)
.toArray();
const blockHeight = block!.height;
const [confirmationInfo, revocationInfo, executionInfo, executionFailure] = await Promise.all([
contract.getPastEvents('Confirmation', {
fromBlock: blockHeight,
toBlock: 'latest'
}),
contract.getPastEvents('Revocation', {
fromBlock: blockHeight,
toBlock: 'latest'
}),
contract.getPastEvents('Execution', {
fromBlock: blockHeight,
toBlock: 'latest'
}),
contract.getPastEvents('ExecutionFailure', {
fromBlock: blockHeight,
toBlock: 'latest'
})
]);
const executionTransactionIdArray = executionInfo.map(i => i.returnValues.transactionId);
const contractTransactionsInfo = [...confirmationInfo, ...revocationInfo, ...executionFailure];
const multisigTxpsInfo = contractTransactionsInfo.filter(
i => !executionTransactionIdArray.includes(i.returnValues.transactionId)
);
return this.convertMultisigTxpsInfo(multisigTxpsInfo);
}
convertMultisigTxpsInfo(multisigTxpsInfo: Array<MULTISIGTxInfo>) {
return multisigTxpsInfo.map(this.convertTxpsInfo);
}
convertTxpsInfo(transfer: MULTISIGTxInfo) {
const { blockHash, blockNumber, transactionHash, returnValues, transactionIndex, event } = transfer;
return {
blockHash,
blockNumber,
transactionHash,
transactionIndex,
hash: transactionHash,
sender: returnValues['sender'],
transactionId: returnValues['transactionId'],
event
} as Partial<Transaction>;
}
async getMultisigInfo(chain: string, network: string, multisigContractAddress: string) {
const contract: any = await this.multisigFor(chain, network, multisigContractAddress);
const owners = await contract.methods.getOwners().call();
const required = await contract.methods.required().call();
return {
owners,
required
};
}
async streamGnosisWalletTransactions(params: { multisigContractAddress: string } & StreamWalletTransactionsParams) {
const { chain, network, multisigContractAddress, res, args } = params;
const transactionQuery = getCSP(chain, network).getWalletTransactionQuery(params);
delete transactionQuery.wallets;
delete transactionQuery['wallets.0'];
let query;
if (args.tokenAddress) {
query = {
$or: [
{
...transactionQuery,
to: args.tokenAddress,
'abiType.params.0.value': multisigContractAddress.toLowerCase()
},
{
...transactionQuery,
'internal.action.to': args.tokenAddress.toLowerCase(),
'internal.action.from': multisigContractAddress.toLowerCase()
},
{
...transactionQuery,
'effects.contractAddress': args.tokenAddress,
'effects.from': multisigContractAddress
}
]
};
} else {
query = {
$or: [
{ ...transactionQuery, to: multisigContractAddress },
{ ...transactionQuery, 'internal.action.to': multisigContractAddress.toLowerCase() },
{ ...transactionQuery, 'effects.to': multisigContractAddress }
]
};
}
let transactionStream = new Readable({ objectMode: true });
const ethTransactionTransform = new EVMListTransactionsStream([multisigContractAddress, args.tokenAddress]);
const populateReceipt = new PopulateReceiptTransform();
const populateEffects = new PopulateEffectsTransform();
transactionStream = EVMTransactionStorage.collection
.find(query)
.sort({ blockTimeNormalized: 1 })
.addCursorFlag('noCursorTimeout', true);
transactionStream = transactionStream.pipe(populateEffects); // For old db entires
if (multisigContractAddress) {
const multisigTransform = new MultisigRelatedFilterTransform(multisigContractAddress, args.tokenAddress);
transactionStream = transactionStream.pipe(multisigTransform);
}
transactionStream
.pipe(populateReceipt)
.pipe(ethTransactionTransform)
.pipe(res);
}
}
export const Gnosis = new GnosisApi();