@0xtorch/evm 0.0.126 → 0.0.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.DS_Store +0 -0
- package/_cjs/chain/definitions/ethereum.js +2 -2
- package/_cjs/chain/definitions/ethereum.js.map +1 -1
- package/_cjs/explorer/etherscanV2/client.js +94 -0
- package/_cjs/explorer/etherscanV2/client.js.map +1 -0
- package/_cjs/explorer/etherscanV2/create.js +196 -0
- package/_cjs/explorer/etherscanV2/create.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getBlockNumberByTimestamp.js +21 -0
- package/_cjs/explorer/etherscanV2/getBlockNumberByTimestamp.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js +34 -0
- package/_cjs/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js +36 -0
- package/_cjs/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js +48 -0
- package/_cjs/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js +64 -0
- package/_cjs/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js +51 -0
- package/_cjs/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js +69 -0
- package/_cjs/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js +47 -0
- package/_cjs/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js +74 -0
- package/_cjs/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js.map +1 -0
- package/_cjs/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js +51 -0
- package/_cjs/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js.map +1 -0
- package/_cjs/explorer/index.js +9 -7
- package/_cjs/explorer/index.js.map +1 -1
- package/_cjs/index.js +2 -1
- package/_cjs/index.js.map +1 -1
- package/_esm/chain/definitions/ethereum.js +3 -3
- package/_esm/chain/definitions/ethereum.js.map +1 -1
- package/_esm/explorer/etherscanV2/client.js +95 -0
- package/_esm/explorer/etherscanV2/client.js.map +1 -0
- package/_esm/explorer/etherscanV2/create.js +195 -0
- package/_esm/explorer/etherscanV2/create.js.map +1 -0
- package/_esm/explorer/etherscanV2/getBlockNumberByTimestamp.js +18 -0
- package/_esm/explorer/etherscanV2/getBlockNumberByTimestamp.js.map +1 -0
- package/_esm/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js +31 -0
- package/_esm/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js.map +1 -0
- package/_esm/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js +33 -0
- package/_esm/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js.map +1 -0
- package/_esm/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js +45 -0
- package/_esm/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js.map +1 -0
- package/_esm/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js +61 -0
- package/_esm/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js.map +1 -0
- package/_esm/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js +48 -0
- package/_esm/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js.map +1 -0
- package/_esm/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js +66 -0
- package/_esm/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js.map +1 -0
- package/_esm/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js +44 -0
- package/_esm/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js.map +1 -0
- package/_esm/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js +71 -0
- package/_esm/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js.map +1 -0
- package/_esm/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js +48 -0
- package/_esm/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js.map +1 -0
- package/_esm/explorer/index.js +1 -0
- package/_esm/explorer/index.js.map +1 -1
- package/_esm/index.js +1 -1
- package/_esm/index.js.map +1 -1
- package/_types/explorer/etherscanV2/client.d.ts +17 -0
- package/_types/explorer/etherscanV2/client.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/create.d.ts +12 -0
- package/_types/explorer/etherscanV2/create.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getBlockNumberByTimestamp.d.ts +12 -0
- package/_types/explorer/etherscanV2/getBlockNumberByTimestamp.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getContractCreatorAndCreationTxHash.d.ts +12 -0
- package/_types/explorer/etherscanV2/getContractCreatorAndCreationTxHash.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.d.ts +12 -0
- package/_types/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.d.ts +16 -0
- package/_types/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getInternalTransactionsByTransactionHash.d.ts +12 -0
- package/_types/explorer/etherscanV2/getInternalTransactionsByTransactionHash.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.d.ts +16 -0
- package/_types/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.d.ts +20 -0
- package/_types/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.d.ts +16 -0
- package/_types/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getListOfInternalTransactionsByAddress.d.ts +17 -0
- package/_types/explorer/etherscanV2/getListOfInternalTransactionsByAddress.d.ts.map +1 -0
- package/_types/explorer/etherscanV2/getListOfNormalTransactionsByAddress.d.ts +17 -0
- package/_types/explorer/etherscanV2/getListOfNormalTransactionsByAddress.d.ts.map +1 -0
- package/_types/explorer/index.d.ts +1 -0
- package/_types/explorer/index.d.ts.map +1 -1
- package/_types/index.d.ts +1 -1
- package/_types/index.d.ts.map +1 -1
- package/chain/definitions/ethereum.ts +3 -3
- package/explorer/etherscanV2/client.ts +154 -0
- package/explorer/etherscanV2/create.ts +251 -0
- package/explorer/etherscanV2/getBlockNumberByTimestamp.ts +33 -0
- package/explorer/etherscanV2/getContractCreatorAndCreationTxHash.ts +46 -0
- package/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.ts +54 -0
- package/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.ts +67 -0
- package/explorer/etherscanV2/getInternalTransactionsByTransactionHash.ts +76 -0
- package/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.ts +74 -0
- package/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.ts +92 -0
- package/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.ts +68 -0
- package/explorer/etherscanV2/getListOfInternalTransactionsByAddress.ts +100 -0
- package/explorer/etherscanV2/getListOfNormalTransactionsByAddress.ts +73 -0
- package/explorer/index.ts +1 -0
- package/index.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { rest } from '@0xtorch/core'
|
|
2
|
+
import { type ZodType, type ZodTypeDef, z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export type EtherscanV2Client = {
|
|
5
|
+
get: <
|
|
6
|
+
Output,
|
|
7
|
+
Def extends ZodTypeDef = ZodTypeDef,
|
|
8
|
+
Input = Output,
|
|
9
|
+
>(parameters: {
|
|
10
|
+
path: string
|
|
11
|
+
query: Record<string, string>
|
|
12
|
+
resultSchema: ZodType<Output, Def, Input>
|
|
13
|
+
headers?: Record<string, string>
|
|
14
|
+
}) => Promise<Output>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const baseUrl = 'https://api.etherscan.io/api'
|
|
18
|
+
|
|
19
|
+
type CreateEtherscanV2ClientParameters = {
|
|
20
|
+
chainId: number
|
|
21
|
+
apiKey?: string
|
|
22
|
+
proxyUrl?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const createEtherscanV2Client = ({
|
|
26
|
+
chainId,
|
|
27
|
+
apiKey,
|
|
28
|
+
proxyUrl,
|
|
29
|
+
}: CreateEtherscanV2ClientParameters): EtherscanV2Client => {
|
|
30
|
+
// apiKey = undefined かつ proxyUrl = undefined の場合はエラー
|
|
31
|
+
if (apiKey === undefined && proxyUrl === undefined) {
|
|
32
|
+
throw new Error('apiKey or proxyUrl must be provided')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rateLimitPerSecond = 5
|
|
36
|
+
const requestTimestamps: number[] = []
|
|
37
|
+
|
|
38
|
+
const get = async <
|
|
39
|
+
Output,
|
|
40
|
+
Def extends ZodTypeDef = ZodTypeDef,
|
|
41
|
+
Input = Output,
|
|
42
|
+
>(
|
|
43
|
+
{
|
|
44
|
+
path,
|
|
45
|
+
query,
|
|
46
|
+
resultSchema,
|
|
47
|
+
headers,
|
|
48
|
+
}: {
|
|
49
|
+
path: string
|
|
50
|
+
query: Record<string, string>
|
|
51
|
+
resultSchema: ZodType<Output, Def, Input>
|
|
52
|
+
headers?: Record<string, string>
|
|
53
|
+
},
|
|
54
|
+
repeat = 0,
|
|
55
|
+
): Promise<Output> => {
|
|
56
|
+
// rate limit を超えていたら 1 秒待つ
|
|
57
|
+
const time1SecondAgo = Date.now() - 1000
|
|
58
|
+
if (
|
|
59
|
+
requestTimestamps.filter((t) => t >= time1SecondAgo).length >=
|
|
60
|
+
rateLimitPerSecond
|
|
61
|
+
) {
|
|
62
|
+
await new Promise<void>((resolve) => {
|
|
63
|
+
const intervalId = setInterval(() => {
|
|
64
|
+
const time1SecondAgo = Date.now() - 1000
|
|
65
|
+
if (
|
|
66
|
+
requestTimestamps.filter((t) => t >= time1SecondAgo).length <
|
|
67
|
+
rateLimitPerSecond
|
|
68
|
+
) {
|
|
69
|
+
clearInterval(intervalId)
|
|
70
|
+
resolve()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
}, 1000)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// requestTimestamps に追加
|
|
78
|
+
requestTimestamps.push(Date.now())
|
|
79
|
+
|
|
80
|
+
// define the schema for the response
|
|
81
|
+
const responseSchema = z.object({
|
|
82
|
+
status: z.string(),
|
|
83
|
+
message: z.string(),
|
|
84
|
+
result: z.union([resultSchema, z.string()]).nullable(),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// request
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(path, baseUrl)
|
|
90
|
+
url.searchParams.set('chainid', chainId.toString())
|
|
91
|
+
for (const [key, value] of Object.entries(query)) {
|
|
92
|
+
url.searchParams.set(key, value)
|
|
93
|
+
}
|
|
94
|
+
if (apiKey !== undefined) {
|
|
95
|
+
url.searchParams.set('apikey', apiKey)
|
|
96
|
+
}
|
|
97
|
+
console.debug(`[GET] ${url.toString()}`)
|
|
98
|
+
const { status, message, result } = await rest(
|
|
99
|
+
proxyUrl === undefined
|
|
100
|
+
? url.toString()
|
|
101
|
+
: `${proxyUrl}${encodeURIComponent(url.toString())}`,
|
|
102
|
+
{
|
|
103
|
+
schema: responseSchema,
|
|
104
|
+
fetchOptions: {
|
|
105
|
+
headers: {
|
|
106
|
+
...headers,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
if (status !== '1') {
|
|
112
|
+
throw typeof result === 'string'
|
|
113
|
+
? new Error(result)
|
|
114
|
+
: new Error(message)
|
|
115
|
+
}
|
|
116
|
+
if (result === null || result === undefined) {
|
|
117
|
+
throw new Error(message)
|
|
118
|
+
}
|
|
119
|
+
if (typeof result === 'string') {
|
|
120
|
+
throw new TypeError(result)
|
|
121
|
+
}
|
|
122
|
+
return result as Output
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (repeat < 3 && error instanceof Error) {
|
|
125
|
+
if (
|
|
126
|
+
error.message === 'Max rate limit reached' ||
|
|
127
|
+
error.message === 'Max calls per sec rate limit reached' ||
|
|
128
|
+
error.message === 'Max calls per sec rate limit reached (5/sec)' ||
|
|
129
|
+
error.message ===
|
|
130
|
+
'Max rate limit reached, please use API Key for higher rate limit'
|
|
131
|
+
) {
|
|
132
|
+
console.debug(error)
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
134
|
+
return get({ path, query, resultSchema }, repeat + 1)
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
error.message.includes('429') ||
|
|
138
|
+
error.message.includes(
|
|
139
|
+
'Unexpected error, timeout or server too busy. Please try again later',
|
|
140
|
+
)
|
|
141
|
+
) {
|
|
142
|
+
console.debug(error)
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, 10_000))
|
|
144
|
+
return await get({ path, query, resultSchema }, repeat + 1)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw error
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
get,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { Erc20Transfer, LowerHex } from '../../types'
|
|
2
|
+
import type { InternalTransactionWithIndex } from '../../types/internalTransaction'
|
|
3
|
+
import type { TransactionIndex } from '../../types/transactionIndex'
|
|
4
|
+
import { createInternalTransactionId } from '../../utils/createInternalTransactionId'
|
|
5
|
+
import type { Explorer, LogWithBlockNumber } from '../types'
|
|
6
|
+
import { createEtherscanV2Client } from './client'
|
|
7
|
+
import { getBlockNumberByTimestamp } from './getBlockNumberByTimestamp'
|
|
8
|
+
import { getContractCreatorAndCreationTxHash } from './getContractCreatorAndCreationTxHash'
|
|
9
|
+
import { getContractSourceCodeForVerifiedContract } from './getContractSourceCodeForVerifiedContract'
|
|
10
|
+
import { getEventLogsByAddressFilteredByTopics } from './getEventLogsByAddressFilteredByTopics'
|
|
11
|
+
import { getInternalTransactionsByTransactionHash } from './getInternalTransactionsByTransactionHash'
|
|
12
|
+
import { getListOfErc20TokenTransferEventsByAddress } from './getListOfErc20TokenTransferEventsByAddress'
|
|
13
|
+
import { getListOfErc721TokenTransferEventsByAddress } from './getListOfErc721TokenTransferEventsByAddress'
|
|
14
|
+
import { getListOfErc1155TokenTransferEventsByAddress } from './getListOfErc1155TokenTransferEventsByAddress'
|
|
15
|
+
import { getListOfInternalTransactionsByAddress } from './getListOfInternalTransactionsByAddress'
|
|
16
|
+
import { getListOfNormalTransactionsByAddress } from './getListOfNormalTransactionsByAddress'
|
|
17
|
+
|
|
18
|
+
type CreateEtherscanV2Parameters = {
|
|
19
|
+
name: string
|
|
20
|
+
baseUrl: string
|
|
21
|
+
chainId: number
|
|
22
|
+
apiKey?: string
|
|
23
|
+
proxyUrl?: string
|
|
24
|
+
pageSize?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const createEtherscanV2 = ({
|
|
28
|
+
name,
|
|
29
|
+
baseUrl,
|
|
30
|
+
chainId,
|
|
31
|
+
apiKey,
|
|
32
|
+
proxyUrl,
|
|
33
|
+
pageSize = 10_000,
|
|
34
|
+
}: CreateEtherscanV2Parameters): Explorer => {
|
|
35
|
+
const client = createEtherscanV2Client({
|
|
36
|
+
chainId,
|
|
37
|
+
apiKey,
|
|
38
|
+
proxyUrl,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
baseUrl,
|
|
44
|
+
getAddressInternalTransactions: async ({
|
|
45
|
+
address,
|
|
46
|
+
fromBlock,
|
|
47
|
+
toBlock,
|
|
48
|
+
headers,
|
|
49
|
+
}) => {
|
|
50
|
+
const internalTransactions = new Map<
|
|
51
|
+
string,
|
|
52
|
+
InternalTransactionWithIndex
|
|
53
|
+
>()
|
|
54
|
+
let startblock = fromBlock
|
|
55
|
+
const endblock = toBlock
|
|
56
|
+
while (true) {
|
|
57
|
+
const result = await getListOfInternalTransactionsByAddress({
|
|
58
|
+
client,
|
|
59
|
+
address,
|
|
60
|
+
startblock,
|
|
61
|
+
endblock,
|
|
62
|
+
offset: pageSize,
|
|
63
|
+
headers,
|
|
64
|
+
})
|
|
65
|
+
for (const transaction of result) {
|
|
66
|
+
const id = createInternalTransactionId(transaction)
|
|
67
|
+
if (!internalTransactions.has(id)) {
|
|
68
|
+
internalTransactions.set(id, transaction)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (result.length < pageSize) {
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
const maxBlockNumber = Math.max(...result.map((x) => x.blockNumber))
|
|
75
|
+
startblock = maxBlockNumber
|
|
76
|
+
}
|
|
77
|
+
return [...internalTransactions.values()]
|
|
78
|
+
},
|
|
79
|
+
getAddressTransactionIndexes: async ({
|
|
80
|
+
address,
|
|
81
|
+
fromBlock,
|
|
82
|
+
toBlock,
|
|
83
|
+
headers,
|
|
84
|
+
}) => {
|
|
85
|
+
const indexes = new Map<LowerHex, TransactionIndex>()
|
|
86
|
+
let startblock = fromBlock
|
|
87
|
+
const endblock = toBlock
|
|
88
|
+
while (true) {
|
|
89
|
+
const result = await getListOfNormalTransactionsByAddress({
|
|
90
|
+
client,
|
|
91
|
+
address,
|
|
92
|
+
startblock,
|
|
93
|
+
endblock,
|
|
94
|
+
offset: pageSize,
|
|
95
|
+
headers,
|
|
96
|
+
})
|
|
97
|
+
for (const index of result) {
|
|
98
|
+
if (!indexes.has(index.hash)) {
|
|
99
|
+
indexes.set(index.hash, index)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (result.length < pageSize) {
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
const maxBlockNumber = Math.max(...result.map((x) => x.blockNumber))
|
|
106
|
+
startblock = maxBlockNumber
|
|
107
|
+
}
|
|
108
|
+
return [...indexes.values()]
|
|
109
|
+
},
|
|
110
|
+
getAddressTokenTransferIndexes: async ({
|
|
111
|
+
address,
|
|
112
|
+
fromBlock,
|
|
113
|
+
toBlock,
|
|
114
|
+
headers,
|
|
115
|
+
}) => {
|
|
116
|
+
const indexes = new Map<LowerHex, TransactionIndex>()
|
|
117
|
+
const erc20Transfers: Erc20Transfer[] = []
|
|
118
|
+
const endblock = toBlock
|
|
119
|
+
|
|
120
|
+
// ERC20
|
|
121
|
+
let startblock = fromBlock
|
|
122
|
+
while (true) {
|
|
123
|
+
const { indexes: resultIndexes, erc20Transfers: resultErc20Transfers } =
|
|
124
|
+
await getListOfErc20TokenTransferEventsByAddress({
|
|
125
|
+
client,
|
|
126
|
+
address,
|
|
127
|
+
startblock,
|
|
128
|
+
endblock,
|
|
129
|
+
offset: pageSize,
|
|
130
|
+
headers,
|
|
131
|
+
})
|
|
132
|
+
for (const index of resultIndexes) {
|
|
133
|
+
if (!indexes.has(index.hash)) {
|
|
134
|
+
indexes.set(index.hash, index)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
erc20Transfers.push(...resultErc20Transfers)
|
|
138
|
+
if (resultIndexes.length < pageSize) {
|
|
139
|
+
break
|
|
140
|
+
}
|
|
141
|
+
const maxBlockNumber = Math.max(
|
|
142
|
+
...resultIndexes.map((x) => x.blockNumber),
|
|
143
|
+
)
|
|
144
|
+
startblock = maxBlockNumber
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ERC721
|
|
148
|
+
startblock = fromBlock
|
|
149
|
+
while (true) {
|
|
150
|
+
const result = await getListOfErc721TokenTransferEventsByAddress({
|
|
151
|
+
client,
|
|
152
|
+
address,
|
|
153
|
+
startblock,
|
|
154
|
+
endblock,
|
|
155
|
+
offset: pageSize,
|
|
156
|
+
headers,
|
|
157
|
+
})
|
|
158
|
+
for (const index of result) {
|
|
159
|
+
if (!indexes.has(index.hash)) {
|
|
160
|
+
indexes.set(index.hash, index)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (result.length < pageSize) {
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
const maxBlockNumber = Math.max(...result.map((x) => x.blockNumber))
|
|
167
|
+
startblock = maxBlockNumber
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ERC1155
|
|
171
|
+
startblock = fromBlock
|
|
172
|
+
while (true) {
|
|
173
|
+
const result = await getListOfErc1155TokenTransferEventsByAddress({
|
|
174
|
+
client,
|
|
175
|
+
address,
|
|
176
|
+
startblock,
|
|
177
|
+
endblock,
|
|
178
|
+
offset: pageSize,
|
|
179
|
+
headers,
|
|
180
|
+
})
|
|
181
|
+
for (const index of result) {
|
|
182
|
+
if (!indexes.has(index.hash)) {
|
|
183
|
+
indexes.set(index.hash, index)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (result.length < pageSize) {
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
const maxBlockNumber = Math.max(...result.map((x) => x.blockNumber))
|
|
190
|
+
startblock = maxBlockNumber
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
indexes: [...indexes.values()],
|
|
195
|
+
erc20Transfers,
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
getBlockNumberOfTimestamp: ({ timestamp, headers }): Promise<number> =>
|
|
199
|
+
getBlockNumberByTimestamp({
|
|
200
|
+
client,
|
|
201
|
+
timestamp: Math.floor(timestamp / 1000),
|
|
202
|
+
headers,
|
|
203
|
+
}),
|
|
204
|
+
getContract: ({ address, headers }) =>
|
|
205
|
+
getContractSourceCodeForVerifiedContract({
|
|
206
|
+
client,
|
|
207
|
+
address,
|
|
208
|
+
headers,
|
|
209
|
+
}),
|
|
210
|
+
getContractCreations: ({ addresses, headers }) =>
|
|
211
|
+
getContractCreatorAndCreationTxHash({
|
|
212
|
+
client,
|
|
213
|
+
contractaddresses: addresses,
|
|
214
|
+
headers,
|
|
215
|
+
}),
|
|
216
|
+
getEventLogs: async ({
|
|
217
|
+
address,
|
|
218
|
+
topic0,
|
|
219
|
+
fromBlock: fromBlockParam,
|
|
220
|
+
toBlock,
|
|
221
|
+
headers,
|
|
222
|
+
}) => {
|
|
223
|
+
const logs: LogWithBlockNumber[] = []
|
|
224
|
+
let fromBlock = fromBlockParam ?? 0
|
|
225
|
+
const maxPageSize = 10_000
|
|
226
|
+
while (true) {
|
|
227
|
+
const result = await getEventLogsByAddressFilteredByTopics({
|
|
228
|
+
client,
|
|
229
|
+
address,
|
|
230
|
+
topic0,
|
|
231
|
+
fromBlock,
|
|
232
|
+
toBlock,
|
|
233
|
+
headers,
|
|
234
|
+
})
|
|
235
|
+
logs.push(...result)
|
|
236
|
+
if (result.length < maxPageSize) {
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
const maxBlockNumber = Math.max(...result.map((x) => x.blockNumber))
|
|
240
|
+
fromBlock = maxBlockNumber
|
|
241
|
+
}
|
|
242
|
+
return logs
|
|
243
|
+
},
|
|
244
|
+
getInternalTransactionOfTransaction: ({ hash, headers }) =>
|
|
245
|
+
getInternalTransactionsByTransactionHash({
|
|
246
|
+
client,
|
|
247
|
+
txhash: hash,
|
|
248
|
+
headers,
|
|
249
|
+
}),
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { EtherscanV2Client } from './client'
|
|
3
|
+
|
|
4
|
+
type GetBlockNumberByTimestampParameters = {
|
|
5
|
+
client: EtherscanV2Client
|
|
6
|
+
/** Unix timestamp in seconds */
|
|
7
|
+
timestamp: number
|
|
8
|
+
closest?: 'before' | 'after'
|
|
9
|
+
headers?: Record<string, string>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const resultSchema = z.string().regex(/^\d+$/).transform(Number)
|
|
13
|
+
|
|
14
|
+
/** {@link https://docs.etherscan.io/etherscan-v2/api-endpoints/blocks#get-block-number-by-timestamp} */
|
|
15
|
+
export const getBlockNumberByTimestamp = async ({
|
|
16
|
+
client,
|
|
17
|
+
timestamp,
|
|
18
|
+
closest = 'before',
|
|
19
|
+
headers,
|
|
20
|
+
}: GetBlockNumberByTimestampParameters) => {
|
|
21
|
+
const result = await client.get({
|
|
22
|
+
path: '',
|
|
23
|
+
query: {
|
|
24
|
+
module: 'block',
|
|
25
|
+
action: 'getblocknobytime',
|
|
26
|
+
timestamp: timestamp.toString(),
|
|
27
|
+
closest,
|
|
28
|
+
},
|
|
29
|
+
resultSchema,
|
|
30
|
+
headers,
|
|
31
|
+
})
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { Hex } from '../../types'
|
|
3
|
+
import { addressPrimitiveSchema, lowerHexSchema } from '../../types/primitive'
|
|
4
|
+
import type { ContractCreation } from '../types'
|
|
5
|
+
import type { EtherscanV2Client } from './client'
|
|
6
|
+
|
|
7
|
+
type GetContractCreatorAndCreationTxHashParameters = {
|
|
8
|
+
client: EtherscanV2Client
|
|
9
|
+
contractaddresses: Hex[]
|
|
10
|
+
headers?: Record<string, string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const contractCreationSchema = z.object({
|
|
14
|
+
contractAddress: addressPrimitiveSchema,
|
|
15
|
+
contractCreator: addressPrimitiveSchema,
|
|
16
|
+
txHash: lowerHexSchema,
|
|
17
|
+
})
|
|
18
|
+
const resultSchema = z.array(contractCreationSchema)
|
|
19
|
+
|
|
20
|
+
/** {@link https://docs.etherscan.io/etherscan-v2/api-endpoints/contracts#get-contract-creator-and-creation-tx-hash} */
|
|
21
|
+
export const getContractCreatorAndCreationTxHash = async ({
|
|
22
|
+
client,
|
|
23
|
+
contractaddresses,
|
|
24
|
+
headers,
|
|
25
|
+
}: GetContractCreatorAndCreationTxHashParameters): Promise<
|
|
26
|
+
ContractCreation[]
|
|
27
|
+
> => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await client.get({
|
|
30
|
+
path: '',
|
|
31
|
+
query: {
|
|
32
|
+
module: 'contract',
|
|
33
|
+
action: 'getcontractcreation',
|
|
34
|
+
contractaddresses: contractaddresses.join(','),
|
|
35
|
+
},
|
|
36
|
+
resultSchema,
|
|
37
|
+
headers,
|
|
38
|
+
})
|
|
39
|
+
return result
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error instanceof Error && error.message.includes('No data found')) {
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { Hex } from '../../types'
|
|
3
|
+
import type { Contract } from '../types'
|
|
4
|
+
import type { EtherscanV2Client } from './client'
|
|
5
|
+
|
|
6
|
+
type GetContractSourceCodeForVerifiedContractParameters = {
|
|
7
|
+
client: EtherscanV2Client
|
|
8
|
+
address: Hex
|
|
9
|
+
headers?: Record<string, string>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const resultSchema = z.array(
|
|
13
|
+
z.object({
|
|
14
|
+
ABI: z.string().optional(),
|
|
15
|
+
ContractName: z.string().optional(),
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
/** {@link https://docs.etherscan.io/etherscan-v2/api-endpoints/contracts#get-contract-source-code-for-verified-contract-source-codes} */
|
|
20
|
+
export const getContractSourceCodeForVerifiedContract = async ({
|
|
21
|
+
client,
|
|
22
|
+
address,
|
|
23
|
+
headers,
|
|
24
|
+
}: GetContractSourceCodeForVerifiedContractParameters): Promise<
|
|
25
|
+
Contract | undefined
|
|
26
|
+
> => {
|
|
27
|
+
const result = await client.get({
|
|
28
|
+
path: '',
|
|
29
|
+
query: {
|
|
30
|
+
module: 'contract',
|
|
31
|
+
action: 'getsourcecode',
|
|
32
|
+
address,
|
|
33
|
+
},
|
|
34
|
+
resultSchema,
|
|
35
|
+
headers,
|
|
36
|
+
})
|
|
37
|
+
if (result.length === 0) {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
const contract = result[0]
|
|
41
|
+
if (
|
|
42
|
+
contract.ABI === undefined ||
|
|
43
|
+
contract.ABI.length === 0 ||
|
|
44
|
+
contract.ContractName === undefined ||
|
|
45
|
+
contract.ContractName.length === 0
|
|
46
|
+
) {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
abi: contract.ABI,
|
|
52
|
+
name: contract.ContractName,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Hex } from 'viem'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { LowerHex } from '../../types'
|
|
4
|
+
import { addressPrimitiveSchema, lowerHexSchema } from '../../types/primitive'
|
|
5
|
+
import type { LogWithBlockNumber } from '../types'
|
|
6
|
+
import type { EtherscanV2Client } from './client'
|
|
7
|
+
|
|
8
|
+
type GetEventLogsByAddressFilteredByTopicsParameters = {
|
|
9
|
+
client: EtherscanV2Client
|
|
10
|
+
address: Hex
|
|
11
|
+
topic0: LowerHex
|
|
12
|
+
fromBlock?: number
|
|
13
|
+
toBlock?: number
|
|
14
|
+
headers?: Record<string, string>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const logSchema = z.object({
|
|
18
|
+
address: addressPrimitiveSchema,
|
|
19
|
+
blockNumber: z
|
|
20
|
+
.string()
|
|
21
|
+
.regex(/^0x[\da-f]*$/)
|
|
22
|
+
.transform((x) => (x === '0x' ? 0 : Number(x))),
|
|
23
|
+
data: lowerHexSchema,
|
|
24
|
+
logIndex: z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(/^0x[\da-f]*$/)
|
|
27
|
+
.transform((x) => (x === '0x' ? 0 : Number(x))),
|
|
28
|
+
topics: z
|
|
29
|
+
.array(lowerHexSchema.nullable())
|
|
30
|
+
.transform((x) => x.filter((v): v is LowerHex => v !== null)),
|
|
31
|
+
transactionHash: lowerHexSchema,
|
|
32
|
+
})
|
|
33
|
+
const resultSchema = z.array(logSchema)
|
|
34
|
+
|
|
35
|
+
/** {@link https://docs.etherscan.io/etherscan-v2/api-endpoints/logs#get-event-logs-by-address-filtered-by-topics} */
|
|
36
|
+
export const getEventLogsByAddressFilteredByTopics = async ({
|
|
37
|
+
client,
|
|
38
|
+
address,
|
|
39
|
+
topic0,
|
|
40
|
+
fromBlock,
|
|
41
|
+
toBlock,
|
|
42
|
+
headers,
|
|
43
|
+
}: GetEventLogsByAddressFilteredByTopicsParameters): Promise<
|
|
44
|
+
LogWithBlockNumber[]
|
|
45
|
+
> => {
|
|
46
|
+
try {
|
|
47
|
+
const result = await client.get({
|
|
48
|
+
path: '',
|
|
49
|
+
query: {
|
|
50
|
+
module: 'logs',
|
|
51
|
+
action: 'getLogs',
|
|
52
|
+
address,
|
|
53
|
+
topic0,
|
|
54
|
+
...(fromBlock === undefined ? {} : { fromBlock: fromBlock.toString() }),
|
|
55
|
+
...(toBlock === undefined ? {} : { toBlock: toBlock.toString() }),
|
|
56
|
+
},
|
|
57
|
+
resultSchema,
|
|
58
|
+
headers,
|
|
59
|
+
})
|
|
60
|
+
return result
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof Error && error.message.includes('No records found')) {
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
65
|
+
throw error
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { LowerHex } from '../../types'
|
|
3
|
+
import type { InternalTransactionWithIndex } from '../../types/internalTransaction'
|
|
4
|
+
import { addressPrimitiveSchema, bigintTextSchema } from '../../types/primitive'
|
|
5
|
+
import type { EtherscanV2Client } from './client'
|
|
6
|
+
|
|
7
|
+
type GetInternalTransactionsByTransactionHashParameters = {
|
|
8
|
+
client: EtherscanV2Client
|
|
9
|
+
txhash: LowerHex
|
|
10
|
+
headers?: Record<string, string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const internalTransactionSchema = z.object({
|
|
14
|
+
blockNumber: z.string().regex(/^\d+$/).transform(Number),
|
|
15
|
+
contractAddress: z.union([
|
|
16
|
+
addressPrimitiveSchema,
|
|
17
|
+
z
|
|
18
|
+
.literal('')
|
|
19
|
+
.nullable()
|
|
20
|
+
.transform(() => undefined),
|
|
21
|
+
]),
|
|
22
|
+
from: addressPrimitiveSchema,
|
|
23
|
+
gas: bigintTextSchema,
|
|
24
|
+
isError: z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(/^\d+$/)
|
|
27
|
+
.transform((x) => x === '1'),
|
|
28
|
+
timeStamp: z.string().regex(/^\d+$/).transform(Number),
|
|
29
|
+
to: z.union([
|
|
30
|
+
addressPrimitiveSchema,
|
|
31
|
+
z
|
|
32
|
+
.literal('')
|
|
33
|
+
.nullable()
|
|
34
|
+
.transform(() => undefined),
|
|
35
|
+
]),
|
|
36
|
+
value: bigintTextSchema,
|
|
37
|
+
})
|
|
38
|
+
const resultSchema = z.array(internalTransactionSchema)
|
|
39
|
+
|
|
40
|
+
/** {@link https://docs.etherscan.io/etherscan-v2/api-endpoints/accounts#get-internal-transactions-by-transaction-hash} */
|
|
41
|
+
export const getInternalTransactionsByTransactionHash = async ({
|
|
42
|
+
client,
|
|
43
|
+
txhash,
|
|
44
|
+
headers,
|
|
45
|
+
}: GetInternalTransactionsByTransactionHashParameters): Promise<
|
|
46
|
+
InternalTransactionWithIndex[]
|
|
47
|
+
> => {
|
|
48
|
+
try {
|
|
49
|
+
const result = await client.get({
|
|
50
|
+
path: '',
|
|
51
|
+
query: {
|
|
52
|
+
module: 'account',
|
|
53
|
+
action: 'txlistinternal',
|
|
54
|
+
txhash,
|
|
55
|
+
},
|
|
56
|
+
resultSchema,
|
|
57
|
+
headers,
|
|
58
|
+
})
|
|
59
|
+
return result.map((v) => ({
|
|
60
|
+
blockNumber: v.blockNumber,
|
|
61
|
+
contractAddress: v.contractAddress,
|
|
62
|
+
from: v.from,
|
|
63
|
+
gas: v.gas,
|
|
64
|
+
isError: v.isError,
|
|
65
|
+
timestamp: v.timeStamp * 1000,
|
|
66
|
+
to: v.to,
|
|
67
|
+
txHash: txhash,
|
|
68
|
+
value: v.value,
|
|
69
|
+
}))
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof Error && error.message === 'No transactions found') {
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|