前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开发以太坊安卓钱包系列4 - 获取以太及Token余额

开发以太坊安卓钱包系列4 - 获取以太及Token余额

作者头像
Tiny熊
发布2019-04-28 11:13:22
1.8K0
发布2019-04-28 11:13:22
举报

这是如何开发以太坊安卓钱包系列,接上一篇[1]继续展示钱包账号资产信息,这篇来看看如何获取账号的以太余额及Token余额。

回顾

在上一篇[2]中,为了避免 UI 与上面4个数据的耦合,使用了一个TokensViewModel,并且已经完成当前选中账号defaultWallet的获取,我们在回看一下TokensViewModel的定义:

代码语言:javascript
复制
public class TokensViewModel extends ViewModel {
    private final MutableLiveData<ETHWallet> defaultWallet;
    private final MutableLiveData<NetworkInfo> defaultNetwork;

    private final MutableLiveData<Token[]> tokens;
    private final MutableLiveData<Ticker> prices;
}

上面还有三个变量,一个是tokens, 当前账号下 所拥有的 Token 数组; 一个是defaultNetwork当前选中网络,还有一个prices我们下一遍介绍。

为什么需要 defaultNetwork 来保存网络信息呢? 这是因为同一个账号,他在不同的网络下,其余额是不同的,而登链钱包又可以支持多个不同的网络,所有我们在获取账号余额前,需要确定一下其网络。

网络

以太坊网络

这里补充下以太坊网络,当前以太坊在使用的网络有5个:

•Mainnet :主网,真正有价值的网络,当前Pow共识;•Ropsten :测试网网络, 使用Pow,和当前的公有链环境一致;•Kovan :测试网网络, 使用PoA共识,仅parity钱包支持;•Rinkeby:测试网网络,使用PoA共识 仅geth钱包支持;•Goerli:测试网网络,为Eth2.0 作准备启动的一个跨客户端的网络。

除此之外,登链钱包还支持本地开发网络。

NetworkInfo

代码中使用 NetworkInfo类 来表示一个网络,其定义如下,大家看一下注释:

代码语言:javascript
复制


public class NetworkInfo {
    public final String name;  // 网络名称,如 mainnet, ropsten
    public final String symbol;  // ETH
    public final String rpcServerUrl;  // 节点提供的rpc 服务地址
    public final String backendUrl;    // 查询交易的列表的服务url
    public final String etherscanUrl;
    public final int chainId;
    public final boolean isMainNetwork;
}

EthereumNetworkRepository.java中用一个 NetworkInfo 数组 NETWORKS 列出了所有支持的网络,其中包含了一个本地开发网络,:

代码语言:javascript
复制
private final NetworkInfo[] NETWORKS = new NetworkInfo[] {
            new NetworkInfo("Mainnet","ETH",
                    "https://mainnet.infura.io/llyrtzQ3YhkdESt2Fzrk",
                    "https://api.trustwalletapp.com/",
                    "https://etherscan.io/",1, true),
            // ignore some ...  
            new NetworkInfo("local_dev","ETH",
                    "http://192.168.8.100:8545",
                    "http://192.168.8.100:8000/",
                    "",1337, false),
    };

NetworkInfo中节点及交易查询服务,我们可以选择自己搭建节点[3](使用Geth、Ganache 等工具),或使用第三方的服务。

测试网络

如果是测试网络,就必须得自己搭建节点,如使用geth启动一个网络:

代码语言:javascript
复制
geth --datadir  my_datadir --dev --rpc --rpcaddr "0.0.0.0" console

特别要注意,需要对--rpcaddr 进行设置,表示哪一个地址能接受RPC请求,因为默认情况下,geth只接受来自 localhost 的请求,这样就无法接受到来自手机的客户端的请求。 如果是Ganache,可以点击Ganache右上角的设置,进行配置。

确定当前网络

在钱包有一个设置项,会把用户选中的网络的name保存到 SharedPreference, 如图:

确定网络的代码逻辑就简单了: 从SharedPreference读取到选中的网络名再对NETWORKS 做一个匹配,代码[4]在EthereumNetworkRepository中,大家可对照查看。

Coin 还是 Token

Coin 指的是以太币,Token 是大家通常所说的代币 或 通证,以太余额何Token余额,他们的获取方式是不一样的,明白这一点很重要,有必要先介绍下以太坊账户模型。

以太坊账户模型

以太币Eth是以太坊的原生代币,在以太坊的账户模型中,有一个字段balance存储着余额,例如账号的定义像下面:

代码语言:javascript
复制
class Account {
  nonce: '0x01',
  balance: '0x03e7',  // wei
  stateRoot: '0x56abc....',
  codeHash: '0x56abc....', 
}

获取以太币的余额只需要调用web3j提供的RPC接口eth_getBalance[5]。

而一个地址的Token余额,他记录在Token合约上,注意合约其实也是一个账户(合约账户),Token是指 符合ERC20标准[6]的合约, 每个地址的余额通常存储在一个Mapping类型的balanceOf变量中,获取地址的余额需要调用合约的balanceOf方法,并给他传递地址作为参数。

如果在合约地址上调用 eth_getBalance, 获取的是合约上所存的 eth余额。

Token & TokenInfo

在登链代码里,每一种币及余额封装成了一个Token类,不论是以太币还是Token 都处理是一个Token实例。

这里Token 命名不是很严谨,以太币一般称为Coin,为了方便,Coin和Token 都统一作为Token处理,Coin 作为一个特殊的Token,了解这一点对后文阅读很重要。

Token的定义如下:

代码语言:javascript
复制
public class Token {
    public final TokenInfo tokenInfo;
    public final String balance;    // 币余额
    public String value;            // 币对应的法币价值
}

public class TokenInfo {
    public final String address;  // 合约地址
    public final String name;
    public final String symbol;   // 代币符号
    public final int decimals;
}

账号所有资产

资产包括以太币资产及Token资产。

关联 Token

在获取账号余额之前,我们需要先知道有多少 Token 种类,然后再获取每种Token余额。在登链钱包[7]中,每一账号在某个网络下所关联 Token种类,保存为一个 Realm[8]文件,相关逻辑在RealmTokenSource类中。

Realm 是一个移动端数据库,是替代sqlite的一种解决方案。

在用户通过以下界面添加新资产,会调用RealmTokenSource类的put方法保存到.realm文件。

现在来看看如何获取账号所关联的 Token, 逻辑上比较简单,不过涉及了多个类,我把调用序列图梳理一下:

通过这个调用过程,最终通过TokensViewModel类的onTokens获取到Token种类。

代码语言:javascript
复制
   private void onTokens(Token[] tokens) {        this.tokens.postValue(tokens);   }

在PropertyFragmeng界面中订阅收到数据之后,把它设置到界面的Adapter里,完成Token列表的显示。

Ethplorer-API 服务

TokenRepository在执行fetch方法时,如果是在主网下,会调用代码[9]中 EthplorerTokenService类,从第三方服务Ethplorer-API[10]获取到获取到某一个地址所关联的所有的Token种类。

Ethplorer-API提供的API更多,不过我们只需要getAddressInfo[11]接口,请求接口如下:

代码语言:javascript
复制
/getAddressInfo/0xaccount?apiKey=freekey

Ethplorer-API 的免费接口是有请求限额,每2秒才能发起一个请求,需要注意访问频度。

余额 balance

获取以太余额

分为两步:

•先构造出web3j 对象•web3j 调用 ethGetBalance 获取以太余额

web3j对象的构造方法如下:

代码语言:javascript
复制
web3j = Web3j.build(new HttpService(networkInfo.rpcServerUrl, httpClient, false));

web3j对象在TokenRepository初始化的时候完成,在TokenRepository获取到Token列表之后,如果是以太币会随即会调用getEthBalance 方法:

代码语言:javascript
复制
private BigDecimal getEthBalance(String walletAddress) throws Exception {
        return new BigDecimal(web3j
                .ethGetBalance(walletAddress, DefaultBlockParameterName.LATEST)
                .send()
                .getBalance());
    }

获取 Token 数量

在TokenRepository获取到Token列表之后,如果是ERC20代币会随即会调用getBalance 方法。 根据前面的介绍获取代币的余额需要调用合约的balanceOf方法,在以太坊上对合约方法的调用实际上会合约地址发起一个调用,调用的附加数据是函数及参数的ABI编码数据

之前写过一篇文章:如何理解以太坊ABI[12], 大家可以读一下。

用以下方法构造出balanceOf的ABI函数类型:

代码语言:javascript
复制
private static org.web3j.abi.datatypes.Function balanceOf(String owner) {
        return new org.web3j.abi.datatypes.Function(
                "balanceOf",
                Collections.singletonList(new Address(owner)),
                Collections.singletonList(new TypeReference<Uint256>() {}));
    }

获取到balanceOf的ABI 之后,经过编码之后,使用 createEthCallTransaction来构造这样一个交易:交易的发起者是当前的账号,交易的目标地址是合约地址,附加数据是编码之后的数据,getBalance方法如下:

代码语言:javascript
复制

private BigDecimal getBalance(String walletAddress, TokenInfo tokenInfo) throws Exception {
        org.web3j.abi.datatypes.Function function = balanceOf(walletAddress);
        String responseValue = callSmartContractFunction(function, tokenInfo.address, walletAddress);

        List<Type> response = FunctionReturnDecoder.decode(
                responseValue, function.getOutputParameters());
        if (response.size() == 1) {
            return new BigDecimal(((Uint256) response.get(0)).getValue());
        } else {
            return null;
        }
    }


    private String callSmartContractFunction(
            org.web3j.abi.datatypes.Function function, String contractAddress, String walletAddress) throws Exception {
        String encodedFunction = FunctionEncoder.encode(function);

        EthCall response = web3j.ethCall(
                Transaction.createEthCallTransaction(walletAddress, contractAddress, encodedFunction),
                DefaultBlockParameterName.LATEST)
                .sendAsync().get();
        return response.getValue();
    }

余额格式化

上面获取到的余额,是以最小单位表示的一个数,如以太币余额用wei表示,而现示给用户的数据是ether,即大家说的以太。

注: 1 eth = 10^18 wei , 更多单位转换[13]

转换方法如下:

代码语言:javascript
复制
BigDecimal decimalDivisor = new BigDecimal(Math.pow(10, decimals));
BigDecimal ethbalance = balance.divide(decimalDivisor);

对以太币而言 decimals 为 18,之后 ethbalance 会转化为一个保留4位小数点数的字符串保存到Token类型的balance变量,转换方法如下:

代码语言:javascript
复制
 ethBalance.setScale(4, RoundingMode.CEILING).toPlainString()

UI界面最终通过订阅 tokens 数组获取Token种类及余额,代码查阅PropertyFragment.java

References

[1] 上一篇: https://learnblockchain.cn/2019/03/24/eth_wallet_dev_3/ [3] 搭建节点: https://learnblockchain.cn/2018/03/18/create_private_blockchain/ [4] 代码: https://github.com/xilibi2003/Upchain-wallet [5] eth_getBalance: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getbalance [6] ERC20标准: https://learnblockchain.cn/2018/01/12/create_token/ [7] 登链钱包: https://github.com/xilibi2003/Upchain-wallet [8] Realm: https://realm.io/docs/java/latest/ [9] 代码: https://github.com/xilibi2003/Upchain-wallet [10] Ethplorer-API: https://api.ethplorer.io [11] getAddressInfo: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API#get-address-info [12] 如何理解以太坊ABI: https://learnblockchain.cn/2018/08/09/understand-abi/ [13] 单位转换: https://learnblockchain.cn/2018/02/02/solidity-unit/ [14] web3j: https://github.com/web3j [15] Realm: https://realm.io/docs/java/latest/ [16] Ethplorer-API: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回顾
  • 网络
    • 以太坊网络
      • NetworkInfo
        • 测试网络
          • 确定当前网络
          • Coin 还是 Token
            • 以太坊账户模型
              • Token & TokenInfo
              • 账号所有资产
                • 关联 Token
                  • Ethplorer-API 服务
                  • 余额 balance
                    • 获取以太余额
                      • 获取 Token 数量
                        • 余额格式化
                          • References
                          相关产品与服务
                          访问管理
                          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档