Skip to content

Commit

Permalink
Track blocks and relevant txs + account history
Browse files Browse the repository at this point in the history
Closes #126
  • Loading branch information
mhluongo committed Aug 15, 2021
1 parent bbf5664 commit 6536a7f
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 72 deletions.
13 changes: 12 additions & 1 deletion api/index.ts
Expand Up @@ -4,6 +4,7 @@ import Accounts, { AccountsState } from "./accounts"
import { SmartContractFungibleAsset } from "./types"
import { apiStubs } from "./temp-stubs"
import { STATE_KEY } from "./constants"
import { ETHEREUM } from "./constants/networks"
import { DEFAULT_STATE } from "./constants/default-state"
import { migrate } from "./migrations"
import {
Expand Down Expand Up @@ -93,7 +94,17 @@ class Main {

async initializeServices() {
this.preferenceService = startPreferences()
this.chainService = startChain(this.preferenceService)
this.chainService = startChain(this.preferenceService).then(
async (service) => {
await service.addAccountToTrack({
// TODO uses Ethermine address for development - move this to startup
// state
account: "0xea674fdde714fd979de3edf0f56aa9716b898ec8",
network: ETHEREUM,
})
return service
}
)
this.indexingService = startIndexing(
this.preferenceService,
this.chainService
Expand Down
82 changes: 82 additions & 0 deletions api/lib/alchemy.ts
@@ -0,0 +1,82 @@
import {
AlchemyProvider,
AlchemyWebSocketProvider,
} from "@ethersproject/providers"
import { BigNumber, utils } from "ethers"

export interface AlchemyAssetTransfer {
hash: string
blockHeight: number
category: "token" | "internal" | "external"
from: string | null
to: string | null
rawContract?: {
address: string
decimals: number
value: BigInt
}
value: number
erc721TokenId: string | null
}

/*
* Use Alchemy's getAssetTransfers call to get historical transfers for an
* account.
*
* Note that pagination isn't supported, so any responses after 1k transfers
* will be dropped.
*
* More information https://docs.alchemy.com/alchemy/documentation/apis/enhanced-apis/transfers-api#alchemy_getassettransfers
* @param provider - an Alchemy ethers provider
* @param account - the account whose transfer history we're fetching
* @param fromBlock - the block height specifying how far in the past we want
* to look.
*/
export async function getAssetTransfers(
provider: AlchemyProvider | AlchemyWebSocketProvider,
account: string,
fromBlock: number
): Promise<AlchemyAssetTransfer[]> {
const params = {
fromBlock: utils.hexValue(fromBlock),
toBlock: "latest",
// excludeZeroValue: false,
}
const rpcResponses = await Promise.all([
provider.send("alchemy_getAssetTransfers", [
{
...params,
fromAddress: account,
},
]),
provider.send("alchemy_getAssetTransfers", [
{
...params,
toAddress: account,
},
]),
])

return rpcResponses[0].transfers
.concat(rpcResponses[1].transfers)
.map((json) => {
const formattedTransfer: AlchemyAssetTransfer = {
hash: json.hash,
blockHeight: BigNumber.from(json.blockNum).toNumber(),
category: json.category,
from: json.from,
to: json.to,
value: json.value,
erc721TokenId: json.erc721TokenId,
}
// TODO parse rawContract properly
if (json.rawContract) {
formattedTransfer.rawContract = {
address: json.rawContract.address,
value: BigNumber.from(json.rawContract.value).toBigInt(),
decimals: Number(json.rawContract.decimal),
}
}
return formattedTransfer
})
}
76 changes: 29 additions & 47 deletions api/services/chain/db.ts
Expand Up @@ -3,45 +3,20 @@ import Dexie from "dexie"
import {
AccountBalance,
AccountNetwork,
AnyEVMTransaction,
EIP1559Block,
FungibleAsset,
Network,
} from "../../types"

// TODO application data atop transactions (eg token balances)

export interface Transaction {
hash: string
from: string
to: string
gas: BigInt
gasPrice: BigInt
input: string
nonce: BigInt
value: BigInt
dataSource: "local" | "alchemy"
network: Network
}

export interface ConfirmedTransaction extends Transaction {
blockHash: string
blockNumber: string
}

export interface SignedTransaction extends Transaction {
r: string
s: string
v: string
}

export interface SignedConfirmedTransaction
extends SignedTransaction,
ConfirmedTransaction {}

export interface Migration {
interface Migration {
id: number
appliedAt: number
}

// TODO keep track of blocks invalidated by a reorg
// TODO keep track of transaction "first seen" time

export class ChainDatabase extends Dexie {
/*
* Accounts whose transaction and balances should be tracked on a particular
Expand All @@ -50,15 +25,14 @@ export class ChainDatabase extends Dexie {
accountsToTrack: Dexie.Table<AccountNetwork, number>

/*
*
* Partial block headers cached to track reorgs and network status.
*/
transactions: Dexie.Table<
| Transaction
| ConfirmedTransaction
| SignedTransaction
| SignedConfirmedTransaction,
number
>
blocks: Dexie.Table<EIP1559Block, number>

/*
* Historic and pending transactions relevant to tracked accounts.
*/
transactions: Dexie.Table<AnyEVMTransaction, number>

/*
* Historic account balances.
Expand All @@ -72,24 +46,28 @@ export class ChainDatabase extends Dexie {
this.version(1).stores({
migrations: "++id,appliedAt",
accountsToTrack:
"++id,account,network.family,network.chainID,network.name",
"&[account+network.name+network.chainID],account,network.family,network.chainID,network.name",
balances:
"++id,account,assetAmount.amount,assetAmount.asset.symbol,network.name,blockHeight,retrievedAt",
transactions:
"&[hash+network.name],hash,from,[from+network.name],to,[to+network.name],nonce,[nonce+from+network.name],blockHash,blockNumber,network.name",
blocks:
"&[hash+network.name],[network.name+timestamp],hash,network.name,timestamp,parentHash,blockHeight,[blockHeight+network.name]",
})
}

async getLatestBlock(network: Network): Promise<EIP1559Block> {
return this.blocks
.where("[network.name+timestamp]")
.above([network.name, Date.now() - 60 * 60 * 24])
.reverse()
.sortBy("timestamp")[0]
}

async getTransaction(
network: Network,
txHash: string
): Promise<
| Transaction
| ConfirmedTransaction
| SignedTransaction
| SignedConfirmedTransaction
| null
> {
): Promise<AnyEVMTransaction | null> {
return (
(
await this.transactions
Expand All @@ -100,6 +78,10 @@ export class ChainDatabase extends Dexie {
)
}

async addOrUpdateTransaction(tx: AnyEVMTransaction): Promise<void> {
await this.transactions.put(tx)
}

async getLatestAccountBalance(
account: string,
network: Network,
Expand Down
10 changes: 9 additions & 1 deletion api/services/chain/index.ts
@@ -1,11 +1,19 @@
import PreferenceService from "../preferences/service"
import ChainService from "./service"

export { default as ChainService } from "./service"

const SCHEDULES = {
queuedTransactions: {
delayInMinutes: 1,
periodInMinutes: 5,
},
}

export async function startService(
preferenceService: Promise<PreferenceService>
): Promise<ChainService> {
const service = new ChainService(preferenceService)
const service = new ChainService(SCHEDULES, preferenceService)
await service.startService()
return service
}

0 comments on commit 6536a7f

Please sign in to comment.