Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map Internal Transactions #97

Merged
merged 3 commits into from
Jun 29, 2023
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
46 changes: 31 additions & 15 deletions src/controller/httpsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import OpenApi from '../api/openapi'
import BitcoinRouter from '../service/bitcoin/BitcoinRouter'
import { fromApiToRtbcBalance } from '../rskExplorerApi/utils'
import { isMyTransaction } from '../service/transaction/utils'
import { IEvent } from '../rskExplorerApi/types'
import { IApiTransactions, IEvent, IInternalTransaction } from '../rskExplorerApi/types'

export class HttpsAPI {
private app: Application
Expand Down Expand Up @@ -68,24 +68,40 @@ export class HttpsAPI {
async ({ params: { address }, query: { limit, prev, next, chainId = '31', blockNumber = '0' } }: Request,
res: Response, nextFunction: NextFunction) => {
const dataSource = this.dataSourceMapping[chainId as string]
const events: IEvent[] = await dataSource.getEventsByAddress(address.toLowerCase())
.then(events => events.filter(
(event: IEvent) => isMyTransaction(event, address) && event.blockNumber >= +blockNumber)
/* A transaction has the following structure { to: string, from: string }
* and to or from params should be our address when we send or receive a cryptocurrency
* (such as RBTC).
*/
const transactions: {data: IApiTransactions[], prev: string, next: string} =
await dataSource.getTransactionsByAddress(address, limit as string,
prev as string, next as string, blockNumber as string)
.catch(nextFunction)
/* We query events to find transactions when we send or receive a token(ERC20)
* such as RIF,RDOC
* Additionally, we query internal transactions because we could send or receive a cryptocurrency
* invoking a smart contract.
* Finally, we filter by blocknumber and duplicates
*/
const hashes: string[] = await Promise.all([dataSource.getEventsByAddress(address, limit as string),
dataSource.getInternalTransactionByAddress(address, limit as string)])
.then((promises) =>
promises.flat()
.filter((value: IEvent | IInternalTransaction) =>
isMyTransaction(value, address) && value.blockNumber >= +blockNumber)
.filter((value: IEvent | IInternalTransaction) => !transactions.data.map(tx => tx.hash)
.includes(value.transactionHash))
.map((value: IEvent | IInternalTransaction) => value.transactionHash)
)
.then((hashes: string[]) => Array.from(new Set(hashes)))
.catch(() => [])
const result = await Promise.all(
events.map((event: IEvent) => dataSource.getTransaction(event.transactionHash))
hashes.map((hash: string) => dataSource.getTransaction(hash))
)
return await
dataSource.getTransactionsByAddress(address, limit as string,
prev as string, next as string, blockNumber as string)
.then(transactions => {
return {
prev: transactions.prev,
next: transactions.next,
data: [...transactions.data, ...result]
}
})
return Promise.resolve({
prev: transactions.prev,
next: transactions.next,
data: [...transactions.data, ...result]
})
.then(this.responseJsonOk(res))
.catch(nextFunction)
}
Expand Down
3 changes: 2 additions & 1 deletion src/repository/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export abstract class DataSource {
abstract getTokens();
abstract getTokensByAddress(address: string);
abstract getRbtcBalanceByAddress(address: string);
abstract getEventsByAddress(address: string);
abstract getEventsByAddress(address: string, limit?: string);
abstract getTransaction(hash: string);
abstract getInternalTransactionByAddress(address: string, limit?: string);
abstract getTransactionsByAddress(address:string,
limit?: string,
prev?: string,
Expand Down
19 changes: 16 additions & 3 deletions src/rskExplorerApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
TokensServerResponse,
IApiRbtcBalance,
RbtcBalancesServerResponse,
TransactionServerResponse
TransactionServerResponse,
InternalTransactionServerResponse
} from './types'
import { fromApiToRtbcBalance, fromApiToTEvents, fromApiToTokens, fromApiToTokenWithBalance } from './utils'

Expand All @@ -18,17 +19,29 @@ export class RSKExplorerAPI extends DataSource {
this.chainId = chainId
}

async getEventsByAddress (address:string) {
async getEventsByAddress (address:string, limit?: string) {
const params = {
module: 'events',
action: 'getAllEventsByAddress',
address: address.toLowerCase()
address: address.toLowerCase(),
limit
}

const response = await this.axios!.get<EventsServerResponse>(this.url, { params })
return response.data.data.map(ev => fromApiToTEvents(ev))
}

async getInternalTransactionByAddress (address: string, limit?: string) {
const params = {
module: 'internalTransactions',
action: 'getInternalTransactionsByAddress',
address,
limit
}
const response = await this.axios!.get<InternalTransactionServerResponse>(this.url, { params })
return response.data.data
}

async getTokens () {
const params = {
module: 'addresses',
Expand Down
34 changes: 34 additions & 0 deletions src/rskExplorerApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,37 @@ export interface ChannelServerResponse {
export interface TransactionServerResponse {
data: IApiTransactions
}

export interface IAction {
callType: string;
from: string;
to: string;
gas: string;
input: string;
value: string;
}

export interface IResult {
gasUsed: string;
output: string;
}

export interface IInternalTransaction {
_id: string;
action: IAction;
blockHash: string;
blockNumber: number;
transactionHash: string;
transactionPosition: number;
type: string;
subtraces: number;
traceAddress: number[];
result: IResult;
_index: number;
timestamp: number;
internalTxId: string;
}

export interface InternalTransactionServerResponse {
data: IInternalTransaction[]
}
19 changes: 12 additions & 7 deletions src/service/transaction/transactionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ export class TransactionProvider extends PollingProvider<Event> {
}

async getIncomingTransactions (address: string) {
const events = await this.dataSource.getEventsByAddress(this.address.toLowerCase())
.then(events => events.filter(event => isMyTransaction(event, address)))
const hashes: string[] = await Promise.all([
this.dataSource.getEventsByAddress(this.address.toLowerCase()),
this.dataSource.getInternalTransactionByAddress(this.address.toLowerCase())
])
.then(promises =>
promises.flat()
.filter(transaction => isMyTransaction(transaction, address))
.map(transaction => transaction.transactionHash)
)
.then((hashes: string[]) => Array.from(new Set(hashes)))
.catch(() => [])

const txs = events
.map(event => this.dataSource.getTransaction(event.transactionHash))

const result = await Promise.all(txs)
return result
return await Promise.all(hashes
.map(hash => this.dataSource.getTransaction(hash)))
}

async getTransactionsPaginated (address: string, limit?: string, prev?: string, next?: string) {
Expand Down
12 changes: 9 additions & 3 deletions src/service/transaction/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { IEvent } from '../../rskExplorerApi/types'
import { IEvent, IInternalTransaction } from '../../rskExplorerApi/types'

export function isMyTransaction (event: IEvent, address: string) {
return event.args.some((arg) => arg.toLowerCase() === address.toLowerCase())
export function isMyTransaction (event: IEvent | IInternalTransaction, address: string) {
if ('args' in event) {
return event.args.some((arg) => arg.toLowerCase() === address.toLowerCase())
}
if ('action' in event) {
return event.action.from.toLowerCase() === address.toLowerCase() ||
event.action.to.toLowerCase() === address.toLowerCase()
}
}
4 changes: 3 additions & 1 deletion test/address.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ const getEventsByAddressMock = jest.fn(() => Promise.resolve(eventResponse))
const getTransactionsByAddressMock = jest.fn(() => Promise.resolve(transactionResponse))
const getTokensByAddressMock = jest.fn(() => Promise.resolve(tokenResponse))
const getTransactionMock = jest.fn(() => Promise.resolve(transactionFromEventResponse))
const getInternalTransactionByAddressMock = jest.fn(() => Promise.resolve({ data: [] }))
const rskExplorerApiMock = {
getEventsByAddress: getEventsByAddressMock,
getTransactionsByAddress: getTransactionsByAddressMock,
getTokensByAddress: getTokensByAddressMock,
getTransaction: getTransactionMock
getTransaction: getTransactionMock,
getInternalTransactionByAddress: getInternalTransactionByAddressMock
} as any
const dataSourceMapping = {}
dataSourceMapping['31'] = rskExplorerApiMock
Expand Down