@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.
Files changed (105) hide show
  1. package/.DS_Store +0 -0
  2. package/_cjs/chain/definitions/ethereum.js +2 -2
  3. package/_cjs/chain/definitions/ethereum.js.map +1 -1
  4. package/_cjs/explorer/etherscanV2/client.js +94 -0
  5. package/_cjs/explorer/etherscanV2/client.js.map +1 -0
  6. package/_cjs/explorer/etherscanV2/create.js +196 -0
  7. package/_cjs/explorer/etherscanV2/create.js.map +1 -0
  8. package/_cjs/explorer/etherscanV2/getBlockNumberByTimestamp.js +21 -0
  9. package/_cjs/explorer/etherscanV2/getBlockNumberByTimestamp.js.map +1 -0
  10. package/_cjs/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js +34 -0
  11. package/_cjs/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js.map +1 -0
  12. package/_cjs/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js +36 -0
  13. package/_cjs/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js.map +1 -0
  14. package/_cjs/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js +48 -0
  15. package/_cjs/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js.map +1 -0
  16. package/_cjs/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js +64 -0
  17. package/_cjs/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js.map +1 -0
  18. package/_cjs/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js +51 -0
  19. package/_cjs/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js.map +1 -0
  20. package/_cjs/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js +69 -0
  21. package/_cjs/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js.map +1 -0
  22. package/_cjs/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js +47 -0
  23. package/_cjs/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js.map +1 -0
  24. package/_cjs/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js +74 -0
  25. package/_cjs/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js.map +1 -0
  26. package/_cjs/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js +51 -0
  27. package/_cjs/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js.map +1 -0
  28. package/_cjs/explorer/index.js +9 -7
  29. package/_cjs/explorer/index.js.map +1 -1
  30. package/_cjs/index.js +2 -1
  31. package/_cjs/index.js.map +1 -1
  32. package/_esm/chain/definitions/ethereum.js +3 -3
  33. package/_esm/chain/definitions/ethereum.js.map +1 -1
  34. package/_esm/explorer/etherscanV2/client.js +95 -0
  35. package/_esm/explorer/etherscanV2/client.js.map +1 -0
  36. package/_esm/explorer/etherscanV2/create.js +195 -0
  37. package/_esm/explorer/etherscanV2/create.js.map +1 -0
  38. package/_esm/explorer/etherscanV2/getBlockNumberByTimestamp.js +18 -0
  39. package/_esm/explorer/etherscanV2/getBlockNumberByTimestamp.js.map +1 -0
  40. package/_esm/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js +31 -0
  41. package/_esm/explorer/etherscanV2/getContractCreatorAndCreationTxHash.js.map +1 -0
  42. package/_esm/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js +33 -0
  43. package/_esm/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.js.map +1 -0
  44. package/_esm/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js +45 -0
  45. package/_esm/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.js.map +1 -0
  46. package/_esm/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js +61 -0
  47. package/_esm/explorer/etherscanV2/getInternalTransactionsByTransactionHash.js.map +1 -0
  48. package/_esm/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js +48 -0
  49. package/_esm/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.js.map +1 -0
  50. package/_esm/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js +66 -0
  51. package/_esm/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.js.map +1 -0
  52. package/_esm/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js +44 -0
  53. package/_esm/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.js.map +1 -0
  54. package/_esm/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js +71 -0
  55. package/_esm/explorer/etherscanV2/getListOfInternalTransactionsByAddress.js.map +1 -0
  56. package/_esm/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js +48 -0
  57. package/_esm/explorer/etherscanV2/getListOfNormalTransactionsByAddress.js.map +1 -0
  58. package/_esm/explorer/index.js +1 -0
  59. package/_esm/explorer/index.js.map +1 -1
  60. package/_esm/index.js +1 -1
  61. package/_esm/index.js.map +1 -1
  62. package/_types/explorer/etherscanV2/client.d.ts +17 -0
  63. package/_types/explorer/etherscanV2/client.d.ts.map +1 -0
  64. package/_types/explorer/etherscanV2/create.d.ts +12 -0
  65. package/_types/explorer/etherscanV2/create.d.ts.map +1 -0
  66. package/_types/explorer/etherscanV2/getBlockNumberByTimestamp.d.ts +12 -0
  67. package/_types/explorer/etherscanV2/getBlockNumberByTimestamp.d.ts.map +1 -0
  68. package/_types/explorer/etherscanV2/getContractCreatorAndCreationTxHash.d.ts +12 -0
  69. package/_types/explorer/etherscanV2/getContractCreatorAndCreationTxHash.d.ts.map +1 -0
  70. package/_types/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.d.ts +12 -0
  71. package/_types/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.d.ts.map +1 -0
  72. package/_types/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.d.ts +16 -0
  73. package/_types/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.d.ts.map +1 -0
  74. package/_types/explorer/etherscanV2/getInternalTransactionsByTransactionHash.d.ts +12 -0
  75. package/_types/explorer/etherscanV2/getInternalTransactionsByTransactionHash.d.ts.map +1 -0
  76. package/_types/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.d.ts +16 -0
  77. package/_types/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.d.ts.map +1 -0
  78. package/_types/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.d.ts +20 -0
  79. package/_types/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.d.ts.map +1 -0
  80. package/_types/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.d.ts +16 -0
  81. package/_types/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.d.ts.map +1 -0
  82. package/_types/explorer/etherscanV2/getListOfInternalTransactionsByAddress.d.ts +17 -0
  83. package/_types/explorer/etherscanV2/getListOfInternalTransactionsByAddress.d.ts.map +1 -0
  84. package/_types/explorer/etherscanV2/getListOfNormalTransactionsByAddress.d.ts +17 -0
  85. package/_types/explorer/etherscanV2/getListOfNormalTransactionsByAddress.d.ts.map +1 -0
  86. package/_types/explorer/index.d.ts +1 -0
  87. package/_types/explorer/index.d.ts.map +1 -1
  88. package/_types/index.d.ts +1 -1
  89. package/_types/index.d.ts.map +1 -1
  90. package/chain/definitions/ethereum.ts +3 -3
  91. package/explorer/etherscanV2/client.ts +154 -0
  92. package/explorer/etherscanV2/create.ts +251 -0
  93. package/explorer/etherscanV2/getBlockNumberByTimestamp.ts +33 -0
  94. package/explorer/etherscanV2/getContractCreatorAndCreationTxHash.ts +46 -0
  95. package/explorer/etherscanV2/getContractSourceCodeForVerifiedContract.ts +54 -0
  96. package/explorer/etherscanV2/getEventLogsByAddressFilteredByTopics.ts +67 -0
  97. package/explorer/etherscanV2/getInternalTransactionsByTransactionHash.ts +76 -0
  98. package/explorer/etherscanV2/getListOfErc1155TokenTransferEventsByAddress.ts +74 -0
  99. package/explorer/etherscanV2/getListOfErc20TokenTransferEventsByAddress.ts +92 -0
  100. package/explorer/etherscanV2/getListOfErc721TokenTransferEventsByAddress.ts +68 -0
  101. package/explorer/etherscanV2/getListOfInternalTransactionsByAddress.ts +100 -0
  102. package/explorer/etherscanV2/getListOfNormalTransactionsByAddress.ts +73 -0
  103. package/explorer/index.ts +1 -0
  104. package/index.ts +1 -0
  105. 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
+ }