Skip to content
Open
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
6 changes: 3 additions & 3 deletions src/plugins/etherscan/ZenmoneyManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<id>etherscan</id>
<company>15936</company>
<description>
Плагин для синхронизации ETH и BSC с помощью etherscan V2.
Для синхронизации выберите блокчейн, укажите ApiKey etherscan V2 и адреса кошельков.
Plugin for syncing Ethereum, BSC and Arbitrum One via Etherscan V2 API.
Select blockchains, provide your Etherscan V2 API key and wallet addresses.
</description>
<version>1.0</version>
<build>4</build>
<build>5</build>
<modular>true</modular>
<api>public</api>
<files>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ describe('convertAccounts', () => {
],
[
{
id: '0xe61289f5dc092d685e6e918b6624e273b42b6730',
id: '1-0xe61289f5dc092d685e6e918b6624e273b42b6730',
type: AccountType.checking,
title: '0xe61289f5dc092d685e6e918b6624e273b42b6730',
instrument: 'μETH',
balance: 2000000,
syncIds: ['0xe61289f5dc092d685e6e918b6624e273b42b6730']
syncIds: ['1-0xe61289f5dc092d685e6e918b6624e273b42b6730']
},
{
id: '0xe61289f5dc092d685e6e918b6624e273b42b6740',
id: '1-0xe61289f5dc092d685e6e918b6624e273b42b6740',
type: AccountType.checking,
title: '0xe61289f5dc092d685e6e918b6624e273b42b6740',
instrument: 'μETH',
balance: 0,
syncIds: ['0xe61289f5dc092d685e6e918b6624e273b42b6740']
syncIds: ['1-0xe61289f5dc092d685e6e918b6624e273b42b6740']
}
]
]
Expand Down
21 changes: 11 additions & 10 deletions src/plugins/etherscan/__tests__/converters/ether-scrape.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('scrape', () => {

const result = await scrape(
{
chain: 1,
preferences: preferencesMock,
startBlock: 1,
endBlock: 99999999,
Expand All @@ -18,28 +19,28 @@ describe('scrape', () => {

expect(result.accounts).toEqual([
{
id: '1',
id: '1-1',
type: AccountType.checking,
title: '1',
instrument: 'μETH',
balance: 2000000,
syncIds: ['1']
syncIds: ['1-1']
},
{
id: '2',
id: '1-2',
type: AccountType.checking,
title: '2',
instrument: 'μETH',
balance: 10000000,
syncIds: ['2']
syncIds: ['1-2']
},
{
id: '3',
id: '1-3',
type: AccountType.checking,
title: '3',
instrument: 'μETH',
balance: 0,
syncIds: ['3']
syncIds: ['1-3']
}
])

Expand All @@ -49,7 +50,7 @@ describe('scrape', () => {
date: new Date('2015-07-30T15:26:28.000Z'),
movements: [{
id: '1',
account: { id: '1' },
account: { id: '1-1' },
invoice: null,
sum: -1000000,
fee: -323
Expand All @@ -66,7 +67,7 @@ describe('scrape', () => {
date: new Date('2015-07-30T15:26:28.000Z'),
movements: [{
id: '2',
account: { id: '1' },
account: { id: '1-1' },
invoice: null,
sum: 2000000,
fee: 0
Expand All @@ -84,13 +85,13 @@ describe('scrape', () => {
movements: [
{
id: '3',
account: { id: '1' },
account: { id: '1-1' },
invoice: null,
sum: -1000000,
fee: -323
}, {
id: '3',
account: { id: '2' },
account: { id: '1-2' },
invoice: null,
sum: 1000000,
fee: 0
Expand Down
26 changes: 23 additions & 3 deletions src/plugins/etherscan/common/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
export const ETHER_MAINNET = 1
export const BNB_MAINNET = 56
export const ARBITRUM_ONE = 42161

export const Instruments = {
export const Instruments: Record<number, string> = {
[ETHER_MAINNET]: 'μETH',
[BNB_MAINNET]: 'bnb'
} as const
[BNB_MAINNET]: 'bnb',
[ARBITRUM_ONE]: 'μETH'
}

export const ChainNames: Record<number, string> = {
[ETHER_MAINNET]: 'Ethereum',
[BNB_MAINNET]: 'BSC',
[ARBITRUM_ONE]: 'Arbitrum One'
}

export function chainAccountId (chain: number, address: string): string {
return `${chain}-${address}`
}

// Minimum timestamps (seconds) — network launch dates
// Requests before these dates return "No closest block found"
export const ChainMinTimestamp: Record<number, number> = {
[ETHER_MAINNET]: 1438269973, // 2015-07-30
[BNB_MAINNET]: 1598671449, // 2020-08-29
[ARBITRUM_ONE]: 1630425600 // 2021-08-31
}
7 changes: 7 additions & 0 deletions src/plugins/etherscan/common/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ function canBeMergedAsTransfer (left: Transaction, right: Transaction): boolean
return false
}

// Cannot merge into transfer if both movements reference the same account
const leftId = 'id' in leftMovement.account ? leftMovement.account.id : null
const rightId = 'id' in rightMovement.account ? rightMovement.account.id : null
if (leftId != null && rightId != null && leftId === rightId) {
return false
}

if (leftMovement.sum < 0) {
return rightMovement.sum > 0
}
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/etherscan/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { delay } from '../../../common/utils'
import { Preferences } from '../types'
import _ from 'lodash'

import type { BlockNoResponse, Response } from './types'
import type { BlockNoResponse, Chain, Response } from './types'
import { TemporaryError } from '../../../errors'

const baseUrl = 'https://api.etherscan.io/v2/api'
Expand Down Expand Up @@ -88,10 +88,11 @@ export async function fetch<T extends Response> (

export async function fetchBlockNoByTime (
preferences: Preferences,
chain: Chain,
{ timestamp }: { timestamp: number }
): Promise<number> {
const response = await fetch<BlockNoResponse>({
chainid: preferences.chain,
chainid: chain,
module: 'block',
action: 'getblocknobytime',
closest: 'before',
Expand Down
86 changes: 86 additions & 0 deletions src/plugins/etherscan/common/merchants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ETHER_MAINNET, BNB_MAINNET, ARBITRUM_ONE } from './config'
import type { Chain } from './types'

type ContractMap = Record<string, string>

const ETHEREUM_CONTRACTS: ContractMap = {
// DEX
'0x7a250d5630b4cf539739df2c5dacb4c659f2488d': 'Uniswap V2 Router',
'0xe592427a0aece92de3edee1f18e0157c05861564': 'Uniswap V3 Router',
'0xef1c6e67703c7bd7107eed8303fbe6ec2554bf6b': 'Uniswap Universal Router',
'0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f': 'SushiSwap Router',

// Bridges
'0x3ee18b2214aff97000d974cf647e7c347e8fa585': 'Wormhole Bridge',

// Lending
'0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9': 'Aave V2 Lending Pool',
'0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2': 'Aave V3 Pool',

// System
'0x0000000000000000000000000000000000000000': 'System'
}

const BSC_CONTRACTS: ContractMap = {
// DEX
'0x10ed43c718714eb63d5aa57b78b54704e256024e': 'PancakeSwap Router',
'0x1b02da8cb0d097eb8d57a175b88c7d8b47997506': 'SushiSwap Router',

// Bridges
'0x3c2269811836af69497e5f486a85d7316753cf62': 'LayerZero Endpoint',
'0x8731d54e9d02c286767d56ac03e8037c07e01e98': 'Stargate Router',

// Lending
'0xc11b1268c1a384e55c48c2391d8d480264a3a7f4': 'Venus Protocol',

// System
'0x0000000000000000000000000000000000000000': 'System'
}

const ARBITRUM_CONTRACTS: ContractMap = {
// DEX
'0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45': 'Uniswap V3 Router',
'0x1b02da8cb0d097eb8d57a175b88c7d8b47997506': 'SushiSwap Router',
'0x2df1adb252afb77fe762586fa6c116857f163df7': 'PancakeSwap Router',

// Bridges
'0x0000000000000000000000000000000000000064': 'Arbitrum Bridge',
'0x3c2269811836af69497e5f486a85d7316753cf62': 'LayerZero Endpoint',
'0x8731d54e9d02c286767d56ac03e8037c07e01e98': 'Stargate Router',

// Lending / Perps
'0x5f3b5dfeb7b28cdbd7faba78963ee202a494e2a2': 'Aave Lending Pool',
'0x32df62dc3aed2cd6224193052ce665dc18165841': 'Radiant Capital',
'0x489ee077994b6658eafa855c308275ead8097c4a': 'GMX Exchange',

// System
'0x0000000000000000000000000000000000000000': 'System'
}

const KNOWN_CONTRACTS: Record<number, ContractMap> = {
[ETHER_MAINNET]: ETHEREUM_CONTRACTS,
[BNB_MAINNET]: BSC_CONTRACTS,
[ARBITRUM_ONE]: ARBITRUM_CONTRACTS
}

export function normalizeMerchant (
addr: string,
userAddress: string,
chain: Chain
): string {
if (addr == null || addr === '') return 'Unknown'

const a = addr.toLowerCase()
const u = userAddress.toLowerCase()

if (a === u) return 'Self'

const contracts = KNOWN_CONTRACTS[chain] ?? {}
if (contracts[a] != null) return contracts[a]

if (a.startsWith('0x') && a.length === 42) {
return `Contract ${addr.slice(0, 6)}…${addr.slice(-4)}`
}

return addr
}
4 changes: 2 additions & 2 deletions src/plugins/etherscan/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ETHER_MAINNET, BNB_MAINNET } from './config'
import { ETHER_MAINNET, BNB_MAINNET, ARBITRUM_ONE } from './config'

export interface Response {
status: string
Expand All @@ -9,4 +9,4 @@ export interface BlockNoResponse extends Response {
result: string
}

export type Chain = typeof ETHER_MAINNET | typeof BNB_MAINNET
export type Chain = typeof ETHER_MAINNET | typeof BNB_MAINNET | typeof ARBITRUM_ONE
11 changes: 7 additions & 4 deletions src/plugins/etherscan/ether/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fetch } from '../common'
import { Preferences } from '../types'
import type { Chain } from '../common/types'
import {
AccountResponse,
EthereumAccount,
Expand All @@ -8,10 +9,11 @@ import {
} from './types'

export async function fetchAccounts (
preferences: Preferences
preferences: Preferences,
chain: Chain
): Promise<EthereumAccount[]> {
const response = await fetch<AccountResponse>({
chainid: preferences.chain,
chainid: chain,
module: 'account',
action: 'balancemulti',
address: preferences.account,
Expand All @@ -33,13 +35,14 @@ const PAGE_SIZE = 100

export async function fetchAccountTransactions (
preferences: Preferences,
chain: Chain,
options: AccountTransactionsOptions
): Promise<EthereumTransaction[]> {
const { account, startBlock, endBlock, page = 1 } = options

try {
const response = await fetch<TransactionResponse>({
chainid: preferences.chain,
chainid: chain,
module: 'account',
action: 'txlist',
address: account,
Expand All @@ -56,7 +59,7 @@ export async function fetchAccountTransactions (
if (response.result.length === PAGE_SIZE) {
return [
...transactions,
...(await fetchAccountTransactions(preferences, {
...(await fetchAccountTransactions(preferences, chain, {
...options,
page: page + 1
}))
Expand Down
Loading
Loading