ABI
- ABI(Application Binary Interface,应用二进制接口)是智能合约的接口定义,它描述了智能合约的函数和事件。对于以太坊区块链,ABI 是一个 JSON 格式的对象数组,它定义了如何与已部署的智能合约进行交互。
ABI 的作用:
- 合约交互: 当你使用 Web3.js 或其他以太坊库与智能合约交互时,ABI 告诉库如何构造正确的交易,调用合约中的方法,或解析事件和返回值。
- 输入和输出定义: ABI 明确描述了合约中函数的输入参数类型、输出类型、函数名称以及函数是否改变区块链状态(即是否需要支付
Gas
)。
ABI 的组成部分:
ABI 通常包括以下信息:
- 函数签名:合约中每个函数的名称和参数类型。
- 函数类型:
function
: 普通函数,可能是只读(不修改区块链状态)或写入(会修改状态,需要 Gas)。constructor
: 构造函数,用于部署合约时调用。event
: 事件,用于通知某些操作已发生,通常可以在交易日志中捕获。fallback
或receive
:回退函数,当调用合约但没有指定函数时执行。
- 输入和输出:函数参数的类型和顺序,以及返回值的类型。
- 状态可见性:定义函数是否是
view
(只读)或pure
(纯函数,不读取或修改状态),或者是否需要 Gas。
ABI 示例:
- 假设有一个智能合约,定义了一个
getValue()
函数和一个setValue(uint256)
函数,它们的 ABI 可能像下面这样:
[
{
"constant": true,
"inputs": [],
"name": "getValue",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_value",
"type": "uint256"
}
],
"name": "setValue",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]
解释:
-
getValue
函数:constant: true
: 表明这是一个只读函数,不会修改区块链状态(即不需要Gas
)。inputs: []
: 没有输入参数。outputs
: 返回一个uint256
类型的值。stateMutability: view
: 表示它是只读的函数。
-
setValue
函数:constant: false
: 表明这是一个可以修改区块链状态的函数(需要Gas
)。inputs
: 需要一个名为_value
的uint256
类型的输入参数。outputs
: 没有输出。stateMutability: nonpayable
: 表示这个函数不会接收以太币(ETH)。
如何获取 ABI:
- 从编译合约时生成:当你使用 Solidity 编译器(如
solc
或 Remix)编译合约时,ABI 会自动生成。
- 从区块链浏览器获取:如果合约已经部署在区块链上(如以太坊主网或测试网),你可以通过区块链浏览器(例如 Etherscan)查找合约,通常 ABI 会在合约页面中提供。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fetch Contract ABI</title>
</head>
<body>
<h1>Fetch and Display Contract ABI</h1>
<p id="abiData">Fetching ABI data...</p>
<script>
async function main() {
try {
// 发起 API 请求,获取合约的 ABI,注意链接为api-sepolia.etherscan.io,和地址匹配
const response = await fetch('https://api-sepolia.etherscan.io/api?module=contract&action=getabi&address=0xFc7a5BD22dFc48565D6f04698E566Dd0C71d3155&apikey=YourApiKeyToken');
const data = await response.json();
// 获取 ABI 并在页面上显示
let abi = data.result;
console.log(abi);
// 更新 HTML 显示 ABI 数据
document.getElementById('abiData').innerText = abi;
} catch (error) {
console.error('Error fetching ABI:', error);
document.getElementById('abiData').innerText = 'Error fetching ABI';
}
}
// 执行函数
main();
</script>
</body>
</html>
使用 ABI:
实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fetch and Add User</title>
<!-- 在浏览器环境中直接使用 <script> 标签时,需要使用预编译的 UMD(Universal Module Definition)版本,它适用于所有浏览器,而不需要模块打包工具 -->
<!-- 所以 <script src="https://cdn.jsdelivr.net/npm/ethers/dist/ethers.min.js"></script> 会 Uncaught SyntaxError: Unexpected token 'export' (at ethers.min.js:1:498792)-->
<!-- <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"></script> 这个地址无法访问-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/5.2.0/ethers.umd.min.js"></script>
</head>
<body>
<h1>Fetch and Add User to Smart Contract</h1>
<!-- 输入框用于输入用户ID并查询用户名 -->
<h3>Fetch User Name by ID</h3>
<label for="fetchId">Enter ID:</label>
<input type="text" id="fetchId" placeholder="Enter user ID">
<!-- 显示获取的用户名 -->
<p>Fetched User Name: <span id="fetchedName">Not fetched</span></p>
<!-- 按钮调用 get_name() 函数 -->
<button onclick="fetchName()">Fetch Name</button>
<hr>
<!-- 输入框用于添加用户的ID和Name -->
<h3>Add User</h3>
<label for="addId">Enter ID:</label>
<input type="text" id="addId" placeholder="Enter user ID">
<br><br>
<label for="addName">Enter Name:</label>
<input type="text" id="addName" placeholder="Enter user name">
<br><br>
<!-- 显示添加用户的结果 -->
<p>Add User Result: <span id="addResult">Not added</span></p>
<!-- 按钮调用 addUser() 函数 -->
<button onclick="addUser()">Add User</button>
<script>
let signer, contract;
async function connect() {
// 请求用户连接 MetaMask 等钱包
await window.ethereum.enable();
const provider = new ethers.providers.Web3Provider(window.ethereum);
signer = provider.getSigner();
}
async function connectContract() {
if (!signer) await connect();
// 创建合约实例
contract = new ethers.Contract(
'0xd9145CCE52D386f254917e481eB44e9943F39138', // 合约地址
[
{
"inputs": [
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
},
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "addUser",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"name": "get_name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "id_of_name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "users",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
],
signer
);
}
// 调用 get_name 函数,查询用户名
async function fetchName() {
const userId = document.getElementById('fetchId').value;
if (!contract) await connectContract();
try {
const name = await contract.get_name(userId);
document.getElementById('fetchedName').innerText = name;
} catch (error) {
console.error("Error fetching name:", error);
document.getElementById('fetchedName').innerText = "Error fetching name";
}
}
// 调用 addUser 函数,添加用户
async function addUser() {
const userId = document.getElementById('addId').value;
const userName = document.getElementById('addName').value;
if (!contract) await connectContract();
try {
const tx = await contract.addUser(userId, userName); // 提交交易
await tx.wait(); // 等待交易完成
document.getElementById('addResult').innerText = "User added successfully!";
} catch (error) {
console.error("Error adding user:", error);
document.getElementById('addResult').innerText = "Error adding user";
}
}
</script>
</body>
</html>