-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
519 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './blockscan' | ||
export * from './chain' | ||
export * from './types' |
Oops, something went wrong.