查询账户余额
查询账户余额
读取一个账户的余额相当简单。
调用 ethclient
的 BalanceAt
方法,给它传递账户地址和可选的区块号。将区块号设置为 nil
将返回最新的余额。
account := common.HexToAddress("0x25836239F7b632635F815689389C537133248edb")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {log.Fatal(err)
}fmt.Println(balance)
传区块高度能读取指定区块时的账户余额,区块高度必须是 big.Int
类型。
blockNumber := big.NewInt(5532993)
balance, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {log.Fatal(err)
}fmt.Println(balance)
以太坊中的数字是使用尽可能小的单位来处理的,因为它们是定点精度,在 ETH 中它是_wei_。要读取 ETH 值,您必须做计算 wei/10^18
。因为我们正在处理大数,我们得导入原生的 Go math
和 math/big
包。这是您做的转换。
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))fmt.Println(ethValue) // 25.729324269165216041
待处理的余额
有时您想知道待处理的账户余额是多少,例如,在提交或等待交易确认后。客户端提供了类似 BalanceAt
的方法,名为 PendingBalanceAt
,它接收账户地址作为参数。
pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance)
完整代码
package mainimport ("context""fmt""log""math""math/big""github.com/ethereum/go-ethereum/common""github.com/ethereum/go-ethereum/ethclient"
)func main() {client, err := ethclient.Dial("https://cloudflare-eth.com")if err != nil {log.Fatal(err)}account := common.HexToAddress("0x25836239F7b632635F815689389C537133248edb")balance, err := client.BalanceAt(context.Background(), account, nil)if err != nil {log.Fatal(err)}fmt.Println(balance)blockNumber := big.NewInt(5532993)balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber)if err != nil {log.Fatal(err)}fmt.Println(balanceAt) // 25729324269165216042fbalance := new(big.Float)fbalance.SetString(balanceAt.String())ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))fmt.Println(ethValue) // 25.729324269165216041pendingBalance, err := client.PendingBalanceAt(context.Background(), account)fmt.Println(pendingBalance) // 25729324269165216042
}
查询代币余额
首先需要得到一个代币合约的地址,在之前的 2.6 中,我们部署了一个 ERC20 合约,可以使用这个合约的地址作为本次示例的合约地址。
并且可以先创建一个 interface 的 solidity 文件,这个文件可以在 Openzeppelin 仓库中找到:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)pragma solidity ^0.8.20;/*** @dev Interface of the ERC-20 standard as defined in the ERC.*/
interface IERC20 {/*** @dev Emitted when `value` tokens are moved from one account (`from`) to* another (`to`).** Note that `value` may be zero.*/event Transfer(address indexed from, address indexed to, uint256 value);/*** @dev Emitted when the allowance of a `spender` for an `owner` is set by* a call to {approve}. `value` is the new allowance.*/event Approval(address indexed owner, address indexed spender, uint256 value);/*** @dev Returns the value of tokens in existence.*/function totalSupply() external view returns (uint256);/*** @dev Returns the value of tokens owned by `account`.*/function balanceOf(address account) external view returns (uint256);/*** @dev Moves a `value` amount of tokens from the caller's account to `to`.** Returns a boolean value indicating whether the operation succeeded.** Emits a {Transfer} event.*/function transfer(address to, uint256 value) external returns (bool);/*** @dev Returns the remaining number of tokens that `spender` will be* allowed to spend on behalf of `owner` through {transferFrom}. This is* zero by default.** This value changes when {approve} or {transferFrom} are called.*/function allowance(address owner, address spender) external view returns (uint256);/*** @dev Sets a `value` amount of tokens as the allowance of `spender` over the* caller's tokens.** Returns a boolean value indicating whether the operation succeeded.** IMPORTANT: Beware that changing an allowance with this method brings the risk* that someone may use both the old and the new allowance by unfortunate* transaction ordering. One possible solution to mitigate this race* condition is to first reduce the spender's allowance to 0 and set the* desired value afterwards:* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729** Emits an {Approval} event.*/function approve(address spender, uint256 value) external returns (bool);/*** @dev Moves a `value` amount of tokens from `from` to `to` using the* allowance mechanism. `value` is then deducted from the caller's* allowance.** Returns a boolean value indicating whether the operation succeeded.** Emits a {Transfer} event.*/function transferFrom(address from, address to, uint256 value) external returns (bool);
}
和另外一个文件 IERC20Metadata.sol:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)pragma solidity ^0.8.20;import {IERC20} from "../IERC20.sol";/*** @dev Interface for the optional metadata functions from the ERC-20 standard.*/
interface IERC20Metadata is IERC20 {/*** @dev Returns the name of the token.*/function name() external view returns (string memory);/*** @dev Returns the symbol of the token.*/function symbol() external view returns (string memory);/*** @dev Returns the decimals places of the token.*/function decimals() external view returns (uint8);
}
这个文件虽然没有被实现,但是可以使用工具单独处理,生成调用合约时所需的 ABI 的 JSON 文件,并使用 abigen 工具根据 ABI 的 JSON 文件生成 go 代码。
solcjs --abi IERC20Metadata.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go
假设我们已经创建了以太坊客户端实例,将新的 token 包导入我们的项目,并实例化它。这个例子里我们用 RCCDemoToken
代币的地址.
tokenAddress := common.HexToAddress("0xfadea654ea83c00e5003d2ea15c59830b65471c0")
instance, err := token.NewToken(tokenAddress, client)
if err != nil {log.Fatal(err)
}
我们可以调用任何 ERC20 的方法。 例如,我们可以查询用户的代币余额。
address := common.HexToAddress("0x25836239F7b632635F815689389C537133248edb")
bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
if err != nil {log.Fatal(err)
}
fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"
我们还可以读 ERC20 智能合约的公共变量。
name, err := instance.Name(&bind.CallOpts{})
if err != nil {log.Fatal(err)
}
symbol, err := instance.Symbol(&bind.CallOpts{})
if err != nil {log.Fatal(err)
}
decimals, err := instance.Decimals(&bind.CallOpts{})
if err != nil {log.Fatal(err)
}
fmt.Printf("name: %s\n", name) // "name: Golem Network"
fmt.Printf("symbol: %s\n", symbol) // "symbol: GNT"
fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"
我们可以做一些简单的数学运算将余额转换为可读的十进制格式。
fbal := new(big.Float)
fbal.SetString(bal.String())
value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))
fmt.Printf("balance: %f", value) // "balance: 74605500.647409"
完整的代码
package main
import ("fmt""log""math""math/big""github.com/ethereum/go-ethereum/accounts/abi/bind""github.com/ethereum/go-ethereum/common""github.com/ethereum/go-ethereum/ethclient"token "./contracts_erc20" // for demo
)
func main() {client, err := ethclient.Dial("https://cloudflare-eth.com")if err != nil {log.Fatal(err)}// Golem (GNT) AddresstokenAddress := common.HexToAddress("0xfadea654ea83c00e5003d2ea15c59830b65471c0")instance, err := token.NewToken(tokenAddress, client)if err != nil {log.Fatal(err)}address := common.HexToAddress("0x25836239F7b632635F815689389C537133248edb")bal, err := instance.BalanceOf(&bind.CallOpts{}, address)if err != nil {log.Fatal(err)}name, err := instance.Name(&bind.CallOpts{})if err != nil {log.Fatal(err)}symbol, err := instance.Symbol(&bind.CallOpts{})if err != nil {log.Fatal(err)}decimals, err := instance.Decimals(&bind.CallOpts{})if err != nil {log.Fatal(err)}fmt.Printf("name: %s\n", name) // "name: Golem Network"fmt.Printf("symbol: %s\n", symbol) // "symbol: GNT"fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"fbal := new(big.Float)fbal.SetString(bal.String())value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))fmt.Printf("balance: %f", value) // "balance: 74605500.647409"
}