Skip to content

Commit f344c8b

Browse files
Etherscan Plugin (#206)
* feat: Add Etherscan plugin with all endpoints Co-Authored-By: [email protected] <[email protected]> * chore: update pnpm lockfile Co-Authored-By: [email protected] <[email protected]> * fix: update tsconfig extends path Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]>
1 parent 25dc4a7 commit f344c8b

File tree

10 files changed

+576
-0
lines changed

10 files changed

+576
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# GOAT Etherscan Plugin
2+
3+
This plugin enables your agent to interact with the Ethereum blockchain data from Etherscan.
4+
5+
## Installation
6+
7+
```bash
8+
pnpm add @goat-sdk/plugin-etherscan
9+
```
10+
11+
## Configuration
12+
13+
To use the Etherscan plugin, you'll need an API key from [Etherscan](https://etherscan.io/apis).
14+
15+
```typescript
16+
import { etherscan } from "@goat-sdk/plugin-etherscan";
17+
18+
const tools = getOnChainTools({
19+
plugins: [
20+
etherscan({
21+
apiKey: "YOUR_ETHERSCAN_API_KEY",
22+
}),
23+
],
24+
});
25+
```
26+
27+
## Features
28+
29+
- Account balance and transaction history
30+
- Contract ABI and source code retrieval
31+
- Transaction status and receipt information
32+
- Block data
33+
- Token balances and transfers
34+
- Gas price tracking
35+
- Event logs
36+
37+
## Network Support
38+
39+
The plugin supports both mainnet and testnet networks:
40+
- Ethereum Mainnet
41+
- Goerli Testnet
42+
- Sepolia Testnet
43+
44+
## Example Usage
45+
46+
```typescript
47+
// Get account balance
48+
const balance = await tools.get_account_balance({
49+
address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
50+
network: "mainnet",
51+
});
52+
53+
// Get contract ABI
54+
const abi = await tools.get_contract_abi({
55+
address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
56+
network: "mainnet",
57+
});
58+
59+
// Get gas price
60+
const gasPrice = await tools.get_gas_price({
61+
network: "mainnet",
62+
});
63+
```
64+
65+
## API Reference
66+
67+
For detailed API documentation, please refer to the TypeScript types and JSDoc comments in the source code.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@goat-sdk/plugin-etherscan",
3+
"version": "0.1.0",
4+
"files": ["dist/**/*", "README.md", "package.json"],
5+
"scripts": {
6+
"build": "tsup",
7+
"clean": "rm -rf dist",
8+
"test": "vitest run --passWithNoTests"
9+
},
10+
"sideEffects": false,
11+
"main": "./dist/index.js",
12+
"module": "./dist/index.mjs",
13+
"types": "./dist/index.d.ts",
14+
"dependencies": {
15+
"@goat-sdk/core": "workspace:*",
16+
"reflect-metadata": "^0.1.13",
17+
"zod": "catalog:"
18+
},
19+
"peerDependencies": {
20+
"@goat-sdk/core": "workspace:*"
21+
},
22+
"homepage": "https://ohmygoat.dev",
23+
"repository": {
24+
"type": "git",
25+
"url": "git+https://github.com/goat-sdk/goat.git"
26+
},
27+
"license": "MIT",
28+
"bugs": {
29+
"url": "https://github.com/goat-sdk/goat/issues"
30+
},
31+
"keywords": ["ai", "agents", "web3", "blockchain", "etherscan"]
32+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Chain, PluginBase, WalletClientBase } from "@goat-sdk/core";
2+
import { EtherscanService } from "./etherscan.service";
3+
4+
interface EtherscanPluginOptions {
5+
apiKey: string;
6+
}
7+
8+
export class EtherscanPlugin extends PluginBase<WalletClientBase> {
9+
constructor({ apiKey }: EtherscanPluginOptions) {
10+
super("etherscan", [new EtherscanService(apiKey)]);
11+
}
12+
13+
supportsChain(chain: Chain): boolean {
14+
return chain.type === "evm";
15+
}
16+
}
17+
18+
export function etherscan(options: EtherscanPluginOptions) {
19+
return new EtherscanPlugin(options);
20+
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import "reflect-metadata";
2+
import { Tool, WalletClientBase } from "@goat-sdk/core";
3+
import {
4+
AccountBalanceParameters,
5+
AccountTransactionsParameters,
6+
BlockByNumberParameters,
7+
ContractABIParameters,
8+
ContractSourceCodeParameters,
9+
EventLogsParameters,
10+
GasPriceParameters,
11+
TokenBalanceParameters,
12+
TransactionReceiptParameters,
13+
TransactionStatusParameters,
14+
} from "./parameters";
15+
import { buildUrl, etherscanRequest } from "./request";
16+
17+
// Common response types
18+
export interface AccountBalance {
19+
status: string;
20+
message: string;
21+
result: string;
22+
}
23+
24+
export interface TransactionList {
25+
status: string;
26+
message: string;
27+
result: Array<{
28+
blockNumber: string;
29+
timeStamp: string;
30+
hash: string;
31+
from: string;
32+
to: string;
33+
value: string;
34+
gas: string;
35+
gasPrice: string;
36+
}>;
37+
}
38+
39+
// Types for better type safety
40+
export type EtherscanResponse<T> = Promise<T>;
41+
42+
export class EtherscanService {
43+
constructor(private readonly apiKey: string) {}
44+
45+
@Tool({
46+
name: "get_account_balance",
47+
description: "Get account balance from Etherscan",
48+
})
49+
async getAccountBalance(
50+
_walletClient: WalletClientBase,
51+
parameters: AccountBalanceParameters,
52+
): EtherscanResponse<AccountBalance> {
53+
const { address, tag, network } = parameters;
54+
return etherscanRequest(
55+
buildUrl(network, "account", "balance", {
56+
address,
57+
tag,
58+
}),
59+
this.apiKey,
60+
);
61+
}
62+
63+
@Tool({
64+
name: "get_account_transactions",
65+
description: "Get account transactions from Etherscan",
66+
})
67+
async getAccountTransactions(
68+
_walletClient: WalletClientBase,
69+
parameters: AccountTransactionsParameters,
70+
): EtherscanResponse<TransactionList> {
71+
const { address, startBlock, endBlock, page, offset, sort, network } = parameters;
72+
return etherscanRequest(
73+
buildUrl(network, "account", "txlist", {
74+
address,
75+
startblock: startBlock,
76+
endblock: endBlock,
77+
page,
78+
offset,
79+
sort,
80+
}),
81+
this.apiKey,
82+
);
83+
}
84+
85+
@Tool({
86+
name: "get_contract_abi",
87+
description: "Get contract ABI from Etherscan",
88+
})
89+
async getContractABI(
90+
_walletClient: WalletClientBase,
91+
parameters: ContractABIParameters,
92+
): EtherscanResponse<string> {
93+
const { address, network } = parameters;
94+
return etherscanRequest(
95+
buildUrl(network, "contract", "getabi", {
96+
address,
97+
}),
98+
this.apiKey,
99+
);
100+
}
101+
102+
@Tool({
103+
name: "get_contract_source_code",
104+
description: "Get contract source code from Etherscan",
105+
})
106+
async getContractSourceCode(
107+
_walletClient: WalletClientBase,
108+
parameters: ContractSourceCodeParameters,
109+
): EtherscanResponse<string> {
110+
const { address, network } = parameters;
111+
return etherscanRequest(
112+
buildUrl(network, "contract", "getsourcecode", {
113+
address,
114+
}),
115+
this.apiKey,
116+
);
117+
}
118+
119+
@Tool({
120+
name: "get_transaction_status",
121+
description: "Get transaction status from Etherscan",
122+
})
123+
async getTransactionStatus(
124+
_walletClient: WalletClientBase,
125+
parameters: TransactionStatusParameters,
126+
): EtherscanResponse<{ status: string; message: string; result: { status: string } }> {
127+
const { txhash, network } = parameters;
128+
return etherscanRequest(
129+
buildUrl(network, "transaction", "getstatus", {
130+
txhash,
131+
}),
132+
this.apiKey,
133+
);
134+
}
135+
136+
@Tool({
137+
name: "get_transaction_receipt",
138+
description: "Get transaction receipt from Etherscan",
139+
})
140+
async getTransactionReceipt(
141+
_walletClient: WalletClientBase,
142+
parameters: TransactionReceiptParameters,
143+
): EtherscanResponse<{ status: string; message: string; result: { status: string } }> {
144+
const { txhash, network } = parameters;
145+
return etherscanRequest(
146+
buildUrl(network, "transaction", "gettxreceiptstatus", {
147+
txhash,
148+
}),
149+
this.apiKey,
150+
);
151+
}
152+
153+
@Tool({
154+
name: "get_block_by_number",
155+
description: "Get block by number from Etherscan",
156+
})
157+
async getBlockByNumber(
158+
_walletClient: WalletClientBase,
159+
parameters: BlockByNumberParameters,
160+
): EtherscanResponse<{
161+
jsonrpc: string;
162+
id: number;
163+
result: {
164+
number: string;
165+
hash: string;
166+
parentHash: string;
167+
transactions: string[];
168+
timestamp: string;
169+
nonce: string;
170+
difficulty: string;
171+
gasLimit: string;
172+
gasUsed: string;
173+
miner: string;
174+
extraData: string;
175+
size: string;
176+
};
177+
}> {
178+
const { blockNumber, network } = parameters;
179+
return etherscanRequest(
180+
buildUrl(network, "proxy", "eth_getBlockByNumber", {
181+
tag: typeof blockNumber === "number" ? `0x${blockNumber.toString(16)}` : blockNumber,
182+
boolean: true,
183+
}),
184+
this.apiKey,
185+
);
186+
}
187+
188+
@Tool({
189+
name: "get_token_balance",
190+
description: "Get token balance from Etherscan",
191+
})
192+
async getTokenBalance(
193+
_walletClient: WalletClientBase,
194+
parameters: TokenBalanceParameters,
195+
): EtherscanResponse<{ status: string; message: string; result: string }> {
196+
const { address, contractAddress, tag, network } = parameters;
197+
return etherscanRequest(
198+
buildUrl(network, "account", "tokenbalance", {
199+
address,
200+
contractaddress: contractAddress,
201+
tag,
202+
}),
203+
this.apiKey,
204+
);
205+
}
206+
207+
@Tool({
208+
name: "get_gas_price",
209+
description: "Get current gas price from Etherscan",
210+
})
211+
async getGasPrice(
212+
_walletClient: WalletClientBase,
213+
parameters: GasPriceParameters,
214+
): EtherscanResponse<{ jsonrpc: string; id: number; result: string }> {
215+
const { network } = parameters;
216+
return etherscanRequest(buildUrl(network, "proxy", "eth_gasPrice", {}), this.apiKey);
217+
}
218+
219+
@Tool({
220+
name: "get_event_logs",
221+
description: "Get event logs from Etherscan",
222+
})
223+
async getEventLogs(
224+
_walletClient: WalletClientBase,
225+
parameters: EventLogsParameters,
226+
): EtherscanResponse<{
227+
status: string;
228+
message: string;
229+
result: Array<{
230+
address: string;
231+
topics: string[];
232+
data: string;
233+
blockNumber: string;
234+
timeStamp: string;
235+
gasPrice: string;
236+
gasUsed: string;
237+
logIndex: string;
238+
transactionHash: string;
239+
transactionIndex: string;
240+
blockHash: string;
241+
}>;
242+
}> {
243+
const { address, fromBlock, toBlock, topic0, topic1, topic2, topic3, network } = parameters;
244+
const params: Record<string, string | number> = {
245+
address,
246+
fromBlock: typeof fromBlock === "number" ? `0x${fromBlock.toString(16)}` : fromBlock,
247+
toBlock: typeof toBlock === "number" ? `0x${toBlock.toString(16)}` : toBlock,
248+
};
249+
250+
if (topic0) params.topic0 = topic0;
251+
if (topic1) params.topic1 = topic1;
252+
if (topic2) params.topic2 = topic2;
253+
if (topic3) params.topic3 = topic3;
254+
255+
return etherscanRequest(buildUrl(network, "logs", "getLogs", params), this.apiKey);
256+
}
257+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from "./etherscan.plugin";
2+
export * from "./etherscan.service";
3+
export * from "./parameters";
4+
export * from "./request";

0 commit comments

Comments
 (0)