解密初链钱包(truechain-light-wallet)

概述

简单看了下初链钱包的开源代码,发现初链钱包中主要是调用了LightWalletweb3的相关API。
LightWallet是一个实现了BIP32,BIP39BIP44的HD钱包。LightWallet提供 API来创建和签署交易,或者使用LightWallet生成的地址和密钥加密和解密数据。
下面就分别从创建钱包、导入钱包、备份钱包、账户余额、交易、交易记录等几个主要功能来详解。

1. 创建钱包

核心代码

let randomSeed = lightwallet.keystore.generateRandomSeed();
lightwallet.keystore.createVault(
    {
        password: this.state.pwd,
        seedPhrase: randomSeed,
        hdPathString: "m/44'/60'/0'/0"
    },
    (err, ks) => {
        ks.keyFromPassword(this.state.pwd, (err, pwDerivedKey) => {
            ks.generateNewAddress(pwDerivedKey, 1);
            var address = ks.getAddresses();
            let keystoreV3 = web3.eth.accounts
                .privateKeyToAccount('0x' + ks.exportPrivateKey(address[0], pwDerivedKey))
                .encrypt(this.state.pwd);
            storage.save({
                key: 'walletInfo',
                data: {
                    walletAddress: address[0],
                    keystoreV3: keystoreV3,
                    ks: ks
                    },
                expires: null
            });
            storage.save({
                key: 'walletName',
                data: {
                    walletName: this.state.walletName
                },
                expires: null
            });
            /** 跳转到导出钱包页面 **/
        });
    }
);

主要方法
a.

let randomSeed = lightwallet.keystore.generateRandomSeed();
  • 生成由12个随机单词组成的字符串。
  • 参数
    • extraEntropy
      可选。如果传入了该值,那么该值将会与javascriptRNG(随机数生成器)随机生成的数据以某种方式关联起来,然后再对其进行hash生成最终的种子。

b.

lightwallet.keystore.createVault(
  {
   password: this.state.pwd,
   seedPhrase: randomSeed,
   hdPathString: "m/44'/60'/0'/0"
  },
  (err, ks) => {
    /** 其他逻辑代码 **/
});
  • 创建lightwallet keystore的实例(ks)。
  • 参数:
    • password
      必填。序列化时用于加密keystore的字符串。
    • seedPharse
      必填。用来生成所有账户的12个单词的助记词。
    • salt
      选填。用来加密和解密keystore的盐。如果未填则将随机生成一个。
    • hdPathString
      必填。用户必须提供一个符合BIP32HD路径字符串,以前的默认值为m/0'/0'/0',现在常用的是BIP44的路径m/44'/60'/0'/0

c.

ks.keyFromPassword(this.state.pwd, (err, pwDerivedKey) => {
    /** 一些逻辑代码 **/
})
  • 通过用户输入的密码及内置的salt生成Uint8Array类型的派生密钥,派生密钥用于加密和解密keystore
  • 参数
    • password
      用户输入的密码。
    • callback
      回调有两个参数,错误信息(err)及派生密钥(pwDerivedKey)。

d.

ks.generateNewAddress(pwDerivedKey, 1);
  • 步骤c得到派生密钥的回调中,调用此方法得到地址/私钥对。
  • 参数
    • pwDerivedKey
      必填。上一步中得到的派生密钥。
    • num
      选填。想要生成的地址/私钥对个数。默认值为1。

e.

var address = ks.getAddresses();
  • 返回当前存储在keystore中的十六进制字符串地址列表。
  • 返回值
    • 地址列表

f.

ks.exportPrivateKey(address[0], pwDerivedKey)
  • 导出私钥。此方法应谨慎调用,因为推荐使用keystore进行签名,所以通常不需要导出私钥。
  • 参数
    • address
      必填。导出私钥的地址。
    • pwDerivedKey
      必填。派生密钥,通过派生密钥解密并返回与该地址对应的私钥。

g.

web3.eth.accounts.privateKeyToAccount('0x' + ks.exportPrivateKey(address[0], pwDerivedKey))
  • 使用指定的私钥创建一个账户对象。
  • 参数
    • privateKey
      必填。私钥。
  • 返回值
    • 账户对象(Account)。结构如下:
    export declare interface Account {
    address: string
    privateKey: string
    publicKey: string
    }
    

h.

let keystoreV3 = web3.eth.accounts
    .privateKeyToAccount('0x' + ks.exportPrivateKey(address[0],pwDerivedKey))
    .encrypt(this.state.pwd);
  • 通过web3.eth.accounts.encrypt方法将私钥加密变换为keystorev3标准格式。
  • 参数
    • privateKey
      要加密的私钥。
    • password
      用于加密的密码。
  • 返回值
    • 加密后的keystorev3 JSON

至此,钱包的创建已完成。客户端保存的钱包信息如下:

storage.save({
    key: 'walletInfo',
    data: {
        walletAddress: address[0],
        keystoreV3: keystoreV3,
        ks: ks
    },
    expires: null
});

2. 导入钱包

i. 助记词导入

核心代码

lightWallet.keystore.createVault(
    {
        password: option.password,
        seedPhrase: option.mnemonic,
        hdPathString: option.hdPathString
    },
    (err, ks) => {
        ks.keyFromPassword(option.password, (err, pwDerivedKey) => {
            ks.generateNewAddress(pwDerivedKey, 1);
            var address = ks.getAddresses();
            let keystoreV3 = web3.eth.accounts
                .privateKeyToAccount('0x' + ks.exportPrivateKey(address[0], pwDerivedKey))
                .encrypt(option.password);
            storage.save({
                key: 'walletInfo',
                data: {
                    walletAddress: address[0],
                    keystoreV3: keystoreV3,
                    ks: ks
                },
                expires: null
            });
            storage.save({
                key: 'walletName',
                data: {
                    walletName: '新钱包'
                },
                expires: null
            });
            setTimeout(() => {
                option._this.refs.loading.close();
                option._this.props.navigation.navigate('Home');
            }, 100);
        });
    }
);

助记词导入和创建钱包流程大致相同,这里将不再赘述。

ii. 私钥导入

核心代码

try {
    let keystoreV3 = web3.eth.accounts.encrypt(this.state.privateFile, this.state.privatePwd);
    storage.save({
        key: 'walletInfo',
        data: {
            walletAddress: '0x' + keystoreV3.address,
            keystoreV3: keystoreV3
            },
        expires: null
    });
    storage.save({
        key: 'walletName',
        data: {
            walletName: '新钱包'
        },
        expires: null
    });
    setTimeout(() => {
        this.refs.loading.close();
        this.props.navigation.navigate('Home');
    }, 100);
} catch (err) {
    this.refs.loading.close();
    setTimeout(() => {
        Alert.alert(null, I18n.t('wallet.privateKeyIsWrong')); // '提示', '私钥无效,请重新输入!'
    }, 100);
}

私钥导入调用方法上文均已详解,此处将不再赘述。

iii. keystore导入

核心代码

try {
    let account = web3.eth.accounts.decrypt(this.state.keystoreFile, this.state.keystorePwd);
    storage.save({
        key: 'walletInfo',
        data: {
            walletAddress: account.address,
            keystoreV3: JSON.parse(this.state.keystoreFile)
        },
        expires: null
    });
    storage.save({
        key: 'walletName',
        data: {
            walletName: '新钱包'
        },
        expires: null
    });
    setTimeout(() => {
        this.refs.loading.close();
        this.props.navigation.navigate('Home');
    }, 100);
} catch (e) {
    this.refs.loading.close();
    setTimeout(() => {
        Alert.alert(null, I18n.t('wallet.wrongByKeystoreOrPwd'));
        // '提示', '导入钱包失败, 请检查keystore或者密码是否正确');
    }, 100);
}

主要方法
a.

let account = web3.eth.accounts.decrypt(this.state.keystoreFile, this.state.keystorePwd);
  • 解密给定的keystore对象,并创建账户。
  • 参数
    • keystoreJsonV3
      要解密的keystore文件。
    • password
      用来解密的密码。
  • 返回值
    • 解密的账户对象(Account)

3. 备份钱包

i. 导出助记词

核心代码

storage.load({ key: 'walletInfo' }).then((res) => {
    let mneKeystore = lightwallet.keystore.deserialize(JSON.stringify(res.ks));
    mneKeystore.keyFromPassword(params.walletPassword, (err, pwDerivedKey) => {
        let Mnemonic = mneKeystore.getSeed(pwDerivedKey);
        this.setState({
            Mnemonic: Mnemonic
        });
    });
});

主要方法
a.

let mneKeystore = lightwallet.keystore.deserialize(JSON.stringify(res.ks));
  • 传入一个序列化的keystore字符串返回一个反序列化keystore
  • 参数
    • serialized_keystore
      序列化的keystore文件
  • 返回值
    • 反序列化keystore

b.

let Mnemonic = mneKeystore.getSeed(pwDerivedKey);
  • 给定派生密钥,解密并返回12个单词的助记词种子。
  • 参数
    • pwDerivedKey
      派生密钥
  • 返回值
    助记词

ii. 导出keystore

核心代码

componentDidMount() {
    const { params } = this.props.navigation.state;
    this.setState({
        keystoreV3: JSON.stringify(params.keystoreV3)
    });
}

将保存在本地的keystorev3文件取出,展示给用户导出即可。

4. 账户余额

i. 获取ETH余额

核心代码

web3.eth.getBalance(this.state.walletAddress).then((res) => {
    let eth_banlance = this.show(web3.utils.fromWei(res, 'ether'));
    this.setState({ eth_banlance });
});

主要方法
a.

web3.eth.getBalance(this.state.walletAddress).then((res) => {
});
  • 获取指定块中特定账户地址的余额。
  • 参数
    • address
      要检查余额的账户地址。
    • defaultBlock
      可选,使用该参数覆盖web3.eth.defaultBlock属性值。
    • callback
      可选的回调函数,该回调的第一个参数为error对象,第二个参数为结果值。
  • 返回值
    • 一个Promise对象,其解析值为指定账户地址的余额字符串,以wei为单位。

b.

let eth_banlance = this.show(web3.utils.fromWei(res, 'ether'));
  • 将给定的以wei为单位的值转换为其他单位的数值。注意,wei是最小的以太单位,应当总是使用wei进行计算,仅在需要显示时进行转换。

ii. 获取truechain测试网余额

核心代码

webtrue.getBalance(this.state.walletAddress).then((res) => {
    let true_beta_banlance = this.show(web3.utils.fromWei(res, 'ether'));
    this.setState({ true_beta_banlance });
});

webtrue定义如下:

const Web3 = require('web3');
const WebTrue = require('etrue');
function check(host) {
    if (host.includes('ropsten')) {
        store.dispatch({
            type: 'CONTRACTADDR',
            TRUEContractAddr: '0x2792d677B7Ba6B7072bd2293F64BC0C1CDe23ac1',
            TTRContractAddr: '0x635AfeB8739f908A37b3d312cB4958CB2033F456'
        });
    } else {
        store.dispatch({
            type: 'CONTRACTADDR',
            TRUEContractAddr:
            '0xa4d17ab1ee0efdd23edc2869e7ba96b89eecf9ab',
            TTRContractAddr: '0xf2bb016e8c9c8975654dcd62f318323a8a79d48e'
        });
    }
    global.host = host;
    const web3 = new Web3(new Web3.providers.HttpProvider(host));
    const webtrue = new WebTrue.modules.ETrue(trueHost);
    global.webtrue = webtrue;
}

主要方法
a.

const web3 = new Web3(new Web3.providers.HttpProvider(host));
  • 该方法返回当前有效的通信服务提供器。调用的服务器类型选择有以下几种
    • HttpProvider
      HTTP服务提供器已经被弃用,因为它不支持订阅
    • WebsocketProvider
      Websocket服务提供器是用于传统的浏览器中的标准方法
    • IpcProvider
      当运行一个本地节点时,IPC服务提供器用于node.js下的DApp环境,该方法提供最安全的连接。

关于eture了解不是很多,可以参考一下这篇运用docker镜像搭建TrueChain测试私有环境

iii. 获取truechain主网余额

核心代码

// TRUE合约余额
getBalance(
    iterface,
    this.state.walletAddress,
    store.getState().contractAddr.TRUEContractAddr,
    (true_banlance) => {
        true_banlance = this.show(true_banlance);
        this.setState({ true_banlance });
    }
);
// TTR合约余额
getBalance(
    iterface,
    this.state.walletAddress,
    store.getState().contractAddr.TTRContractAddr,
    (ttr_banlance) => {
        ttr_banlance = this.show(ttr_banlance);
        this.setState({ ttr_banlance });
    }
);

getBalance方法实现:

function getBalance(iterface, address, ContractAddr, callback) {
    var myContract = new web3.eth.Contract(iterface, ContractAddr);
    myContract.methods.balanceOf(address).call().then(function(res) {
        let balance = web3.utils.fromWei(res, 'ether');
        callback(balance);
    });
}

PS: 项目在初链币(TRUE)之外,于2018年5月2日发布新ERC20代币TTR白皮书。
主要方法
a.

var myContract = new web3.eth.Contract(iterface, ContractAddr);
  • web3.eth.Contract类简化了与以太坊区块链上智能合约的交互。创建合约对象时, 只需指定相应智能合约的json接口,web3就可以自动地将所有的调用转换为底层基于RPCABI调用。
    通过web3的封装,与智能合约的交互就像与JavaScript对象一样简单。
  • 参数
    • jsonInterface
      要实例化的合约的json接口。
    • address
      可选,要调用的合约的地址,也可以在之后使用myContract.options.address = '0x1234..'来指定该地址。
    • options
      可选,合约的配置对象,其中某些字段用作调用和交易的回调:
      • from
        交易发送方地址。
      • gasPrice
        用于交易的gas价格,单位:wei
      • gas
        交易可用的最大gas量,即gas limit
      • data
        合约的字节码,部署合约时需要。
  • 返回值
    • 带有所有合约方法和合约事件的合约实例对象。
      truechain-light-wallet中,合约接口定义如下
const iterface = [
    {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_spender",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "approve",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "totalSupply",
        "outputs": [
            {
                "name": "supply",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "",
                "type": "address"
            },
            {
                "name": "",
                "type": "address"
            }
        ],
        "name": "votingInfo",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_from",
                "type": "address"
            },
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transferFrom",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "name": "balances",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "ticketsOf",
        "outputs": [
            {
                "name": "tickets",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [],
        "name": "kill",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "founder",
        "outputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "vote",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "newFounder",
                "type": "address"
            }
        ],
        "name": "changeFounder",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "voteEndTime",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "name": "totalVotes",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transfer",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_tos",
                "type": "address[]"
            },
            {
                "name": "_values",
                "type": "uint256[]"
            }
        ],
        "name": "distributeMultiple",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            }
        ],
        "name": "voteAll",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_endTime",
                "type": "uint256"
            }
        ],
        "name": "setEndTime",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "name": "frozen",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            },
            {
                "name": "_spender",
                "type": "address"
            }
        ],
        "name": "allowance",
        "outputs": [
            {
                "name": "remaining",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "distributed",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_amount",
                "type": "uint256"
            }
        ],
        "name": "distribute",
        "outputs": [
            {
                "name": "success",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "_from",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "_to",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "Transfer",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "_from",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "_to",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "Vote",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "_owner",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "_spender",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "Approval",
        "type": "event"
    }
];
export default iterface;
  • ABI全称Application Binary Interface, 是调用智能合约函数以及合约之间函数调用的消息编码格式定义,也可以理解为智能合约函数调用的接口说明。 类似Webservice里的SOAP协议一样;也就是定义操作函数签名,参数编码,返回结果编码等。使用ABI协议时必须要求在编译时知道类型,即强类型相关。
  • ABI定义
{
    "constant": false, //方法修饰符,false表示函数内可以修改状态变量
    "inputs": [ //方法参数,它是一个对应数组,数组里的每个对象都是一个参数说明
        {
            "name": "a", //第一个参数的名字
            "type": "uint256" //第一个参数的类型
        },
        {
            "name": "b", //第二个参数的名字
            "type": "bytes32" //第二个参数的类型
        },
        {
            "name": "c", //第三个参数的名字
            "type": "bytes32[]" ////第三个参数的类型
        }
    ],
    "name": "lotus", //方法名
    "outputs": [], //方法返回值,格式和inputs类型相同
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function" //方法类型,function, constructor, fallback,event
},

b.

myContract.methods.balanceOf(address).call().then(function(res) {
}
  • myContract.methods
    为指定的合约方法创建一个交易对象,以便使用该交易对象进行调用、发送或估算gas。
    • 调用
    myContract.methods.myMethod([param1[, param2[, ...]]])
    
    可以使用以下语法获得指定方法的交易对象:
    // 1. 名称
    myContract.methods.myMethod(123)
    // 2. 带参名称
    myContract.methods'myMethod(uint256)'
    // 3. 签名
    myContract.methods'0x58cf5f10'
    
    这样就可以支持从javascript合约对象调用同名但参数不同的合约方法。
    • 参数
      取决于在JSON接口中定义的合约方法。返回一个交易对象。
    • 返回值
      交易对象,包含以下字段:
      • arguments(Array)
        之前传入方法的参数,可修改
      • call(Function)
        用来调用只读的合约方法,在EVM直接执行而不必发出交易,因此不会改变合约的状态
      • send(Function)
        用来向合约发送交易并执行方法,因此可以改变合约的状态
      • estimateGas(Function)
        用来估算方法在链上执行时的gas用量
      • encodeABI(Function)
        用来为合约方法进行ABI编码。
        示例:
    // 调用合约方法
    myContract.methods.myMethod(123).call({from:
    '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}, function(error,
    result){
        ...
    });
    
    // 发送交易,使用Promise对象获取返回结果
    myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'})
    .then(function(receipt){
        // receipt can also be a new contract instance, when coming from a "contract.deploy({...}).send()"
    });
    
    // 发送交易,使用事件获取返回结果
    myContract.methods.myMethod(123).send({from:    '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'})
    .on('transactionHash', function(hash){
        ...
    })
    .on('receipt', function(receipt){
        ...
    })
    .on('confirmation', function(confirmationNumber, receipt){
        ...
    })
    .on('error', console.error);
    
  • myContract.methods.balanceOf
    其中balanceOf即为上文中实例化的合约的json接口中的方法。通过传入地址返回用户余额。

5. 交易

核心代码

web3.eth.getGasPrice().then((res) => {
    this.setState({
        gasPrice: res,
        cost: res * web3.utils.fromWei(this.state.gas.toString(), 'ether')
    });
});
storage.load({ key: 'walletInfo' }).then((res) => {
    this.setState({
        fromAddr: res.walletAddress,
        keystoreV3: res.keystoreV3
    });
});
const { params } = this.props.navigation.state;
if (params.currencyName == 'ETH') {
    this.setState({
        gas: 25200
    },
    () => {
        this._sendTokens = () =>
        sendEth(
            this.state.fromAddr,
            this.state.toAddress,
            this.state.amount,
            this.state.password,
            this.state.keystoreV3,
            this.state.gas.toString(),
            this.state.gasPrice.toString(),
            (err, tx) => {
                if (err) {
                    this.refs.loading.close();
                    setTimeout(() => {
                        Alert.alert(null, I18n.t('public.transactionFailed'));
                        // Alert.alert(null, '发布交易失败,请稍后重试!');
                    }, 100);
                    console.log(err);
                } else {
                    this.refs.loading.close();
                    setTimeout(() => {
                        // 发布交易成功!
                        Alert.alert(null, I18n.t('public.transactionSuccess'), [
                            {
                                text: 'OK',
                                onPress: () => {
                                    this.props.navigation.navigate('Home');
                                    }
                                }
                            ]);
                    }, 100);
                    console.log(tx, '=======');
                }
            }
        );
    });
} else {
    this.setState(
        {
            gas: 80000
        },
        () => {
            this._sendTokens = () =>
            sendTokens(
                iterface,
                this.state.fromAddr,
                this.state.toAddress,
                this.state.amount,
                this.state.password,
                this.state.keystoreV3,
                this.state.ContractAddr,
                this.state.gas.toString(),
                this.state.gasPrice.toString(),
                (err, tx) => {
                    if (err) {
                        this.refs.loading.close();
                        setTimeout(() => {
                            Alert.alert(null, I18n.t('public.transactionFailed'));
                            // Alert.alert(null, '发布交易失败,请稍后重试!');
                        }, 100);
                        console.log(err);
                    } else {
                        this.refs.loading.close();
                        setTimeout(() => {
                            // 发布交易成功!
                            Alert.alert(null, I18n.t('public.transactionSuccess'), [
                                {
                                    text: 'OK',
                                    onPress: () => {
                                        this.props.navigation.navigate('Home');
                                    }
                                }
                            ]);
                        }, 100);
                        console.log(tx, '=======');
                    }
                }
            );
        }
    );
    let ContractAddr = params.currencyName + 'ContractAddr';
    this.setState({
        ContractAddr: store.getState().contractAddr[ContractAddr]
    });
}

sendEth方法如下:

function sendEth(fromAddress, toAddress, amount, password, keystore, gas, gasPrice, callback) {
    let account = web3.eth.accounts.decrypt(keystore, password),
    value = web3.utils.toWei(amount, 'ether');
    web3.eth.accounts.wallet.add(account);
    web3.eth.sendTransaction({
        from: fromAddress,
        to: toAddress,
        value: value,
        gasPrice: gasPrice,
        gas: gas
    }, function (error, txhash) {
        callback(error, txhash)
    });
};

sendTokens方法如下:

function sendTokens(iterface, fromAddr, toAddr, value, password, keystore, contractAddress, gas, gasPrice, callabck) {
    let contract = new web3.eth.Contract(iterface);
    contract.options.address = contractAddress;
    const account = web3.eth.accounts.decrypt(keystore, password);
    web3.eth.accounts.wallet.add(account);
    let value_wei = web3.utils.toWei(value, 'ether'),
        data = contract.methods.transfer(toAddr, value_wei).encodeABI();
    web3.eth.sendTransaction({
        from: fromAddr,
        to: contractAddress,
        value: '0x00',
        gasPrice: gasPrice,
        gas: gas,
        data: data
    },
    function (error, txhash) {
        callabck(error, txhash)
    })
}

主要方法
a.

web3.eth.getGasPrice().then((res) => {
    this.setState({
        gasPrice: res,
        cost: res * web3.utils.fromWei(this.state.gas.toString(), 'ether')
    });
});
  • 用来获取当前gas价格,该价格由最近的若干块的gas价格中值决定。
  • 返回值
    • 一个Promise对象,其解析值为表示当前gas价格的字符串,单位为wei

b.

value = web3.utils.toWei(amount, 'ether');
  • 将给定的以太金额转换为以wei为单位的数值。注意,wei是最小的以太单位,应当总是使用wei进行计算,仅在需要显示时进行转换。

c.

web3.eth.accounts.wallet.add(account);
  • 使用私钥或账户对象向钱包中添加一个账户。
  • 参数
    • account
      私钥,或者使用web3.eth.accounts.create()创建的账户对象。
  • 返回值
    • Object
      被添加的账户

d.

web3.eth.sendTransaction({

}, function (error, txhash) {

});
  • 向以太坊网络提交一个交易。
  • 参数
    • transactionObject,要发送的交易对象,包含以下字段:
      • from
        交易发送方账户地址,不设置该字段的话,则使用web3.eth.defaultAccount属性值。可设置为一个地址或本地钱包web3.eth.accounts.wallet中的索引序号
      • to
        可选,消息的目标地址,对于合约创建交易该字段为null
      • value
        wei为单位的交易金额,如果是创建合约的话则为合约基金?
      • gas
        可选,默认值:待定,用于交易的gas总量,未用完的gas会退还
      • gasPrice
        可选,该交易的gas价格,单位为wei,默认值为web3.eth.gasPrice属性值
      • data
        可选,可以是包含合约方法数据的ABI字符串,或者是合约创建交易中的初始化代码
      • nonce
        可选,使用该字段覆盖使用相同nonce值的挂起交易
    • callback
      可选的回调函数,其第一个参数为错误对象,第二个参数为结果
  • 返回值
    • 32字节长的交易哈希值。
      有三种调用方式
// compiled solidity source code using https://remix.ethereum.org
var code = "603d80600c6000396000f3007c01000000000000000000000000000000000000000000000000000000006000350463c6888fa18114602d57005b6007600435028060005260206000f3";

// 使用回调函数
web3.eth.sendTransaction({
    from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe',
    data: code // deploying a contracrt
}, function(error, hash){
    ...
});

// 使用promise
web3.eth.sendTransaction({
    from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe',
    to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
    value: '1000000000000000'
})
.then(function(receipt){
    ...
});

// 使用事件发生器
web3.eth.sendTransaction({
    from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe',
    to: '0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe',
    value: '1000000000000000'
})
.on('transactionHash', function(hash){
    /** 在交易发出并得到有效的交易哈希值后立刻触发 */
    ...
})
.on('receipt', function(receipt){
    /** 当交易收据有效后立刻触发 */
    ...
})
.on('confirmation', function(confirmationNumber, receipt){ 
    /** 在每次确认后立刻触发,最多12次确认。确认编号为第一个参数,收据为第二个参数。从0号确认开始触发 */
    ... 
})
.on('error', console.error); // 在发送交易的过程中如果出现错误则立刻触发。如果是out of gas错误,则传入第二个参数为交易收据

e.

data = contract.methods.transfer(toAddr, value_wei).encodeABI();
  • 其中transfer即为上文中实例化的合约的json接口中的方法。参数为转入地址和以wei为单位的转账金额。

6. 交易记录

i. 获取ETH交易记录

核心代码

const getTransactionRecord = (walletAddress) => {
    if (host.includes('ropsten')) {
        return axios.get(
            'http://api-ropsten.etherscan.io/api?module=account&action=txlist&address=' + 
            walletAddress + 
            '&sort=desc&apikey=YourApiKeyToken'
        );
    } else {
        return axios.get(
            'http://api.etherscan.io/api?module=account&action=txlist&address=' + 
            walletAddress + 
            '&sort=desc&apikey=YourApiKeyToken'
        );
    }
};

ropstenETH测试地址。

ii. 获取ERC20交易记录

核心代码

const getERC20TransactionRecord = (walletAddress, contractaddress) => {
    if (host.includes('ropsten')) {
        return axios.get(
            'https://api-ropsten.etherscan.io/api?module=account&action=tokentx&contractaddress=' +
            contractaddress +
            '&address=' +
            walletAddress +
            '&sort=desc&apikey=YourApiKeyToken'
        );
    } else {
        return axios.get(
            'https://api.etherscan.io/api?module=account&action=tokentx&contractaddress=' +
            contractaddress +
            '&address=' +
            walletAddress +
            '&sort=desc&apikey=YourApiKeyToken'
        );
    }
};

ropstenETH测试地址。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容