Skip to content

Commit

Permalink
feat: blockscan
Browse files Browse the repository at this point in the history
  • Loading branch information
KABBOUCHI committed Oct 30, 2023
1 parent d1a2553 commit 55fa705
Show file tree
Hide file tree
Showing 9 changed files with 519 additions and 0 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ let provider = toJsonRpcProvider(web3.currentProvider)// Web3.js provider, ex:
let provider = toJsonRpcProvider("https://rpc.ankr.com/eth") // Http RPC URL
let provider = toJsonRpcProvider(new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth')) // ethers JsonRpcProvider instance
```

### Blockscan
```ts
import { Blockscan, Chain } from "@instadapp/utils";

const etherscan = new Blockscan(Chain.Mainnet);

await etherscan.get("0x6975be450864c02b4613023c2152ee0743572325")
await etherscan.getTransactions("0x6975be450864c02b4613023c2152ee0743572325")
await etherscan.getInternalTransactions("0x6975be450864c02b4613023c2152ee0743572325")
await etherscan.getErc20TokenTransferEvents("0x6975be450864c02b4613023c2152ee0743572325")
await etherscan.getErc721TokenTransferEvents("0x6975be450864c02b4613023c2152ee0743572325")
await etherscan.getErc1155TokenTransferEvents("0x6975be450864c02b4613023c2152ee0743572325")

const basescan = Blockscan.custom('https://basescan.org','https://api.basescan.org/api')
await basescan.contractSourceCode('0x833589fcd6edb6e08f4c7c32d4f71b54bda02913')
```

## 💻 Development

- Clone this repository
Expand Down
196 changes: 196 additions & 0 deletions src/blockscan/blockscan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import axios from 'axios'
import { Chain, getChainScanUrls } from './chain'
import { ERC1155TokenTransferEvent, ERC20TokenTransferEvent, ERC721TokenTransferEvent, InternalTransaction, NormalTransaction } from './types'

export class Blockscan {
apiUrl: string
baseUrl: string

/**
* Create a new client with the correct endpoints based on the chain and provided API key
*/
constructor (private chain: Chain | number, private apiKey?: string, options?: any) {
if (chain === Chain.Custom) {
if (!options?.apiUrl) {
throw new Error('Custom chain requires apiUrl')
}

if (!options?.baseUrl) {
throw new Error('Custom chain requires baseUrl')
}

this.apiUrl = options.apiUrl
this.baseUrl = options.baseUrl
} else {
const { base, api } = getChainScanUrls(chain)

this.apiUrl = api
this.baseUrl = base
}
}

/**
* Create a new client with custom endpoints
*/
static custom (baseUrl: string, apiUrl: string, apiKey?: string) {
return new Blockscan(Chain.Custom, apiKey, { baseUrl, apiUrl })
}

/**
* Fetches a contract's verified source code and its metadata.
*/
async contractSourceCode (address: string): Promise<any[]> {
return await this.query({
module: 'contract',
action: 'getsourcecode',
address
})
}

/**
* Returns the list of transactions performed by an address, with optional pagination.
*/
async getTransactions (user: string, params?: {
startblock?: number,
endblock?: number,
sort?: 'asc' | 'desc',
page?: number,
offset?: number
}): Promise<NormalTransaction[]> {
return await this
.query({
module: 'account',
action: 'txlist',
address: user,
...params
})
.catch((error) => {
if (error.message.includes('No transactions found')) {
return []
}

throw error
})
}

/**
* Returns the list of internal transactions performed by an address or within a transaction, with optional pagination.
*/
async getInternalTransactions (user: string, params?: {
startblock?: number,
endblock?: number,
sort?: 'asc' | 'desc',
page?: number,
offset?: number
}): Promise<InternalTransaction[]> {
return await this
.query({
module: 'account',
action: 'txlistinternal',
address: user,
...params
}).catch((error) => {
if (error.message.includes('No transactions found')) {
return []
}

throw error
})
}

/**
* Returns the list of ERC-20 tokens transferred by an address, with optional filtering by token contract.
*/
async getErc20TokenTransferEvents (
eventQuery: { address: string } | { contract: string } | { address: string, contract: string },
params?: {
startblock?: number,
endblock?: number,
sort?: 'asc' | 'desc',
page?: number,
offset?: number
}): Promise<ERC20TokenTransferEvent[]> {
return await this
.query({
module: 'account',
action: 'tokentx',
...eventQuery,
...params
}).catch((error) => {
if (error.message.includes('No transactions found')) {
return []
}

throw error
})
}

/**
* Returns the list of ERC-721 ( NFT ) tokens transferred by an address, with optional filtering by token contract.
*/
async getErc721TokenTransferEvents (
eventQuery: { address: string } | { contract: string } | { address: string, contract: string },
params?: {
startblock?: number,
endblock?: number,
sort?: 'asc' | 'desc',
page?: number,
offset?: number
}): Promise<ERC721TokenTransferEvent[]> {
return await this
.query({
module: 'account',
action: 'tokennfttx',
...eventQuery,
...params
}).catch((error) => {
if (error.message.includes('No transactions found')) {
return []
}

throw error
})
}

/**
* Returns the list of ERC-1155 ( NFT ) tokens transferred by an address, with optional filtering by token contract.
*/
async getErc1155TokenTransferEvents (
eventQuery: { address: string } | { contract: string } | { address: string, contract: string },
params?: {
startblock?: number,
endblock?: number,
sort?: 'asc' | 'desc',
page?: number,
offset?: number
}): Promise<ERC1155TokenTransferEvent[]> {
return await this
.query({
module: 'account',
action: 'token1155tx',
...eventQuery,
...params
}).catch((error) => {
if (error.message.includes('No transactions found')) {
return []
}

throw error
})
}

public async query (params: { module: string, action: string, [key: string]: any }) {
try {
params = Object.assign(params, { apikey: this.apiKey })

const { data } = await axios.get(this.apiUrl, { params })
if (data.status !== '1') {
throw new Error(typeof data.result === 'string' ? data.result : data.message)
}

return data.result
} catch (error) {
throw new Error(error.message)
}
}
}
112 changes: 112 additions & 0 deletions src/blockscan/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
export enum Chain {
Custom = -1,
Mainnet = 1,
Ropsten = 3,
Rinkeby = 4,
Goerli = 5,
Kovan = 42,
Sepolia = 1397,
Polygon = 137,
PolygonMumbai = 80001,
Avalanche = 43114,
Optimism = 10,
Arbitrum = 42161,
Gnosis = 100,
BSC = 56,
PolygonZkEVM = 1101,
Fantom = 250,
Base = 8453,
Scroll = 534352,
}

export function getChainScanUrls (chain: Chain | number): { base: string, api: string } {
switch (chain) {
case Chain.Mainnet:
return {
base: 'https://etherscan.io',
api: 'https://api.etherscan.io/api'
}
case Chain.Ropsten:
return {
base: 'https://ropsten.etherscan.io',
api: 'https://api-ropsten.etherscan.io/api'
}
case Chain.Kovan:
return {
base: 'https://kovan.etherscan.io',
api: 'https://api-kovan.etherscan.io/api'
}
case Chain.Rinkeby:
return {
base: 'https://rinkeby.etherscan.io',
api: 'https://api-rinkeby.etherscan.io/api'
}
case Chain.Goerli:
return {
base: 'https://goerli.etherscan.io',
api: 'https://api-goerli.etherscan.io/api'
}
case Chain.Sepolia:
return {
base: 'https://sepolia.etherscan.io',
api: 'https://api-sepolia.etherscan.io/api'
}
case Chain.Polygon:
return {
base: 'https://polygonscan.com',
api: 'https://api.polygonscan.com/api'
}
case Chain.PolygonMumbai:
return {
base: 'https://mumbai.polygonscan.com',
api: 'https://api-testnet.polygonscan.com/api'
}
case Chain.Avalanche:
return {
base: 'https://snowtrace.io',
api: 'https://api.snowtrace.io/api'
}
case Chain.Optimism:
return {
base: 'https://optimistic.etherscan.io',
api: 'https://api-optimistic.etherscan.io/api'
}
case Chain.Arbitrum:
return {
base: 'https://arbiscan.io',
api: 'https://api.arbiscan.io/api'
}
case Chain.Gnosis:
return {
base: 'https://gnosisscan.io',
api: 'https://api.gnosisscan.io/api'
}
case Chain.BSC:
return {
base: 'https://bscscan.com',
api: 'https://api.bscscan.com/api'
}
case Chain.PolygonZkEVM:
return {
base: 'https://zkevm.polygonscan.com',
api: 'https://api-zkevm.polygonscan.com/api'
}
case Chain.Fantom:
return {
base: 'https://ftmscan.com',
api: 'https://api.ftmscan.com/api'
}
case Chain.Base:
return {
base: 'https://basescan.org',
api: 'https://api.basescan.org/api'
}
case Chain.Scroll:
return {
base: 'https://scrollscan.org',
api: 'https://api.scrollscan.org/api'
}
default:
throw new Error(`Unsupported chain ${chain}`)
}
}
3 changes: 3 additions & 0 deletions src/blockscan/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './blockscan'
export * from './chain'
export * from './types'

0 comments on commit 55fa705

Please sign in to comment.