Truffle使用web3.js调试智能合约的一些备忘

进入develop调试模式

1
2
$ truffle compile && truffle develop
$ deploy

deploy之后,我们可以在testrpc上调试智能合约。

获取地址balance

1
2
truffle(develop)> web3.eth.getBalance('0x627306090abab3a6e1400e9345bc60c78a8bef57').toNumber()
96722887800000000000

注意balance的单位是wei。转换成eth则是

1
2
truffle(develop)> web3.fromWei(96722887800000000000, 'ether')
'96.7228878'

payable的函数调用

1
Loto.deployed().then(function(instance){return instance.Buy(5, {value: web3.toWei(0.1, "ether")});}).then(function(value){return value});

纯Get函数调用

1
Loto.deployed().then(function(instance){return instance.getSlots.call();}).then(function(value){return value});

从ERC20 到 ERC721

ERC20的问题点

我们熟悉的基于以太坊的ICO,通常使用ERC20标准发行Token。

ERC20标准的Token主要有两个问题。

第一个问题是,如果错误地将ERC20的Token发送到了非用户地址(比如合约地址)上,大多数情况下,该合约不具备处理相应Token的能力,所以这笔Token就看永久性丧失了。

第二个问题,基于ERC20发行的Token,相同的Token都具有可替代性(fungible), 拿简单的例子来说,我拥有的5ETH和别人拥有的5ETH并没有本质上的区别,相同条件下都可以在交易所换成相同数量的法币。这个性质本身没有什么问题,但是就Token的使用来说,其功能就会受到很大限制。

ERC223 解决了第一个问题。当发送者错误发送到不支持相应Token的合约地址上的是时候,错误发送的Token可以允许被召回。ERC223的细节就此不展开了。我们主要集中讨论第二个问题。

ERC721 以及 Non-fungible

ERC721是Non-fungible Token的标准。著名的以太坊上的游戏CryptoKitties就是基于ERC721的Token。现实生活中,有很多价值是Non-fungible的,最简单的例子就是你在游戏里抽的扭蛋。

ERC721与ERC20的不同之处,主要有两处。

第一,任何一个Token都有相应的一个id,合约本身会用一个mapping记录token的id以及相应的所有者。

为了满足Non-fungible的特性,任何一个Token都会有相应的MetaData。直接将每一个Token本身的特性记录在Blokchain上通常代价很昂贵,所以,我们通常的做法是保持一个Token id和ipfs或者外部https链接的mapping。例如下面的例子。

1
2
3
4
5
6
contract MyNFT {
mapping(uint256 => string) tokenLinks;
function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl) {
return tokenLinks[_tokenId];
}
}

参考资料

更加进一步的说明可能比较多余。建议直接看下面的参考资料。

参考1

参考2

参考3

使用Truflle开发基于oraclize的应用

Oraclize 是什么

Oraclize是将外部的变化引入Blockchain的一套工具。Oraclize已经部署到了很多Blockchain当中,
最常见的,在Ethererum上,Oraclize是一个智能合约。
直接安例子比较直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.4.11;
import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";

contract ExampleContract is usingOraclize {

string public EURGBP;
event LogPriceUpdated(string price);
event LogNewOraclizeQuery(string description);

function ExampleContract() payable {
updatePrice();
}

function __callback(bytes32 myid, string result) {
if (msg.sender != oraclize_cbAddress()) throw;
EURGBP = result;
LogPriceUpdated(result);
}

function updatePrice() payable {
if (oraclize_getPrice("URL") > this.balance) {
LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
oraclize_query("URL", "json(https://api.fixer.io/latest?symbols=USD,GBP).rates.GBP");
}
}
}

上面的智能合约调用了oraclize_query这个oraclize提供的方法。oraclize会监听整个Ethereum Blockchain,在区块链外调用这个query,获取到结果之后,调用目标合约的__callback方法并把query的结果作为参数传入。

有了oraclize,我们想要在blockchain内部调用外部api的时候,只需要写好相应的callback函数就行了。

用Truffle将上例的智能合约部署到Rinkeby TestNet

其实下面的内容和oraclize关系不大。不过既然尝试了解oraclize,不如将其部署之后熟悉一下中间的步骤。

首先是需要的东西。

  • Mist 钱包以及testnet的账号。注意同步最新的内容。

  • Geth

  • Rinkeby Testnet的Eth。网上所以下faucet一下就出来了。

用Geth 跑起local 节点

Mist自带的Geth已经同步了最新的链上的内容,我们需要关掉mist,然后在local用Geth跑Rinkeby的节点。

1
geth --rinkeby --rpc --rpcapi db,eth,net,web3,personal --unlock="0x889a183da0d5450CCbC4604a8200eEb47020f719" --rpccorsdomain https://localhost:3000

注意要输入解锁账号的phrase。

建立truffle项目进行配置

1
2
3
$ mkdir truffle-test
$ cd truffle-test
$ truffle init

需要修改truffle.js的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*", // Match any network id
},
rinkeby: {
host: "localhost", // Connect to geth on the specified
port: 8545,
from: "0x889a183da0d5450CCbC4604a8200eEb47020f719", // default address to use for any transaction Truffle makes during migrations
network_id: 4,
gas: 4612388 // Gas limit used for deploys
}
}
};

然后书写我们的合约。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# contracts/Example.sol

pragma solidity ^0.4.11;
import "./oraclizeAPI.sol";

contract ExampleContract is usingOraclize {

string public EURGBP;
event LogPriceUpdated(string price);
event LogNewOraclizeQuery(string description);

function ExampleContract() payable {
updatePrice();
}

function __callback(bytes32 myid, string result) {
if (msg.sender != oraclize_cbAddress()) throw;
EURGBP = result;
LogPriceUpdated(result);
}

function updatePrice() payable {
if (oraclize_getPrice("URL") > this.balance) {
LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
} else {
LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
oraclize_query("URL", "json(https://api.fixer.io/latest?symbols=USD,GBP).rates.GBP");
}
}
}

我们需要在local应用oraclize。

1
2
3
$ cd contracts
$ wget https://raw.githubusercontent.com/oraclize/ethereum-api/master/oraclizeAPI_0.5.sol
$ mv oraclizeAPI_0.5.sol oraclizeAPI.sol

接下来在migrations下面添加2_deploy_contracts.js

1
2
3
4
5
var Example = artifacts.require("ExampleContract");

module.exports = function(deployer) {
deployer.deploy(Example);
};

这样智能合约的内容就完成了。
rk rinkeby

a 译truffle compile, 部署 truffle migrate --network rinkeby

大功告成。

用Truffle console 调试

1
2
3
4
5
6
$ truffle console --network rinkeby
truffle(rinkeby)> ExampleContract.deployed().then(function(instance){return instance.EURGBP.call();}).then(function(value){return value});
'0.87523'
truffle(rinkeby)> ExampleContract.deployed().then(function(instance){return instance.updatePrice({value: web3.toWei(0.01, "ether")});}).then(function(value){return value.logs[0].
args});
{ description: 'Oraclize query was sent, standing by for the answer..' }

最后如果要监听回掉函数里面的Event,可以用下面的方法。

1
ExampleContract.deployed().then(meta => { const allEvents = meta.allEvents({ fromBlock: 0, toBlock: 'latest' });allEvents.watch((err, res) => { console.log(err, res);});});

以上流水账仅供备忘。

使用webpack开发以太坊的前端应用

前情提要

使用Truffle开发以太坊智能合约一文中,我们建立了一个最基本的剪刀石头布的以太坊智能合约,并部署到了local的testnet上。

排除这个合约本身的不安全的问题,考虑到用户不可能在truffle的console上敲js代码来执行调用合约,我们还得得建立简单方便的前端应用。

游戏步骤

首先是完成后的简陋的UI的截图。

游戏时候,使用metamask连上local的testnet。然后自己扮演两个角色切换账户即可。

首先两个账户分别转账5wei进行注册。

然后两个用户分别选择是出石头,剪刀,还是布。

最后赢家被记录到LasterWinner这个项目里面。赢家还将获得对方的5wei。

boilerplate

建立一个新的目录。Truffle提供了很方便的已经成型的boilerplate来建立前端的应用。

1
2
3
$ mkdir rps-frontend
$ cd rps-frontend
$ truffle unbox webpack

一阵各种download之后,我们发现当前目录下多出来很多东西。
webpack的boilerplate是一个简单的基于ETH的Token的例子。
其中最主要的是app/这个文件。

1
2
3
4
5
6
app
├── index.html
├── javascripts
│   └── app.js
└── stylesheets
└── app.css

里面的内容由于不是我们需要部署的协议的内容,所以基本上我们都要移除。

首先删掉contracts里面除了Migration.sol以外的所有内容,然后再用这里的文件替换migrations里面的2_deploy_contracts.js的内容。这样理论上我们就可以部署我们的石头剪刀布协议了。

添加前端逻辑

打开app/javascripts/app.js进行修改。
具体可以参考这里, 我们只提几个注意点。

1
2
3
4
5
6
7
8
import "../stylesheets/app.css";

import { default as Web3} from 'web3'; // 用来与eth的节点交互的lib。这里我们需要用这个库来获取用户信息。
import { default as contract } from 'truffle-contract'

import rps_artifacts from '../../build/contracts/rps.json' // 协议的ABI接口。这个是通过truffle migrate之后生成的。

var Rps= contract(rps_artifacts);

也许我们已经习惯使用MetaMask来和基于Ethereum的网页应用交互。下面则是获取默认用户账号信息的代码,已经由这个boilderplate自动生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
web3.eth.getAccounts(function(err, accs) {
if (err != null) {
alert("There was an error fetching your accounts.");
return;
}

if (accs.length == 0) {
alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
return;
}

accounts = accs;
account = accounts[0];

self.refreshWinner();
});

接下来是通过API查询blockchain获取最后赢家的函数。核心的是rps.getLastWinner.call({from: account}), 这和我们在truffle的console的输入一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
refreshWinner: function() {
var self = this;
var rps;
Rps.deployed().then(function(instance) {
rps = instance;
return rps.getLastWinner.call({from: account}); // 注意这里
}).then(function(value) {
var balance_element = document.getElementById("winner");
balance_element.innerHTML = value.valueOf();
}).catch(function(e) {
console.log(e);
self.setStatus("Error getting balance; see log.");
});
},

再然后是注册用户并转5wei的代码。同样核心是rps.register({value: amount, from: account})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

register: function() {
var self = this;
var amount = parseInt(document.getElementById("amount").value);
this.setStatus("Initiating transaction... (please wait)");
var rps;
Rps.deployed().then(function(instance) {
rps= instance;
return rps.register({value: amount, from: account});
}).then(function() {
self.setStatus("Transaction complete!");
self.refreshWinner();
}).catch(function(e) {
console.log(e);
self.setStatus("Error sending coin; see log.");
});
},

最后是玩家执行游戏进行选择的代码。重点还是rps.play(selection, {from: account})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
play: function() {
var self = this;
var selection = document.getElementById("selection").value;
var rps;
Rps.deployed().then(function(instance) {
rps= instance;
return rps.play(selection, {from: account}); //重点
}).then(function() {
self.setStatus("Transaction complete!");
self.refreshWinner();
}).catch(function(e) {
console.log(e);
self.setStatus("Error sending coin; see log.");
});

}

修改UI

接下来就是修改默认的boilerplate的index.html了。
我们得添加上显示最后赢家,输入转账数目,确认注册,选择出拳内容,确认出拳内容的一些东西。
由于boilerplate本来写得比较脏,我也只是适当修改,所以请自己参考下面完整的repo吧。

编译,部署,执行

最后就是打开truffle dev 然后依次compile,migrate --reset了。truffle dev会自动建立local的testrpc的testnet,所以这个时候npm run dev,打开浏览器就可以看到我们上面截图中的应用内容了。

最后的话

使用MetaMask大大方便了用户通过webapplication来执行智能合约。也使得我们可以顾虑很少地开发更加健全的智能合约。试想一下,如果没有MetaMask,你的智能合约就有可能需要用户去输入自己的private key。那你的应用大抵也就没人用了。

这里是整个完整的项目。

以上。

使用Truffle开发以太坊智能合约

什么是Truffle

如果你的第一反应是什么是以太坊,那么谷歌应该会回答的很详细。

以太坊智能合约使用编程语言Solidity书写,很多开发者一看到要学习一门新语言就望而却步。然而其实仔细看Solidity的语法,就会发现对于大多数程序员来说还是比较容易上手的。

而其实开发以太坊智能合约的瓶颈在于如何部署,调试,测试。

使用Truffle,可以很好解决这一系列问题。

使用Truffle部署剪刀石头布智能合约

首先用npm 安装Truffle。

1
npm install truffle -g

我们用truffle来部署一个简单的见到石头布的游戏。

游戏规则如下:
游戏双方首先各自向合约地址转账至少5wei,然后当准备就绪之后,双方调用play函数输入剪刀石头布的选择,最后智能合约自动将钱转账给赢家。

由于本文目的是熟悉Truffle开发,部署,调试智能合约的步骤,所以智能合约我事先已经准备好了。
请直接使用这里的内容

不过由于我们要熟悉Truffle的使用步骤,所以请不要fork整个repo。

首先创建一个新的目录。初始化Truffle。

1
2
3
mkdir rps
cd rps
truffle init

这个时候会多出来几个东西。

1
2
3
4
5
6
7
8
9
10
.
├── contracts
│   ├── Migrations.sol
│  
├── migrations
│   ├── 1_initial_migration.js
│  
├── test
├── truffle-config.js
└── truffle.js

创建contracts/rps.sol 文件,把上面提供的智能合约的内容粘贴进去。

然后我们需要创建migrations/2_deploy_contracts.js。其中内容如下。

1
2
3
4
5
var  Rps = artifacts.require("rps");

module.exports = function(deployer) {
deployer.deploy(Rps);
};

解释一下。contracts目录下面存放的就是智能合约文件,而migrations下面则是存放的部署文件。
这里你也许会感到奇怪一开始就存在的Migration.sol1_initial_migration.js是什么东西。
这个官方解释是帮助你获取部署的合约地址的合约。所以我们放着不管就行了。

接下来就是部署。
输入truffle development, Truffle会自动调用testrpc开启一个独立的testnet环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  rps truffle dev
Truffle Develop started at https://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

下面的那串英文是用来恢复密钥,导入密钥的原始口令。不过不用担心,所有测试环境这串英文都一样。

首先编译

1
> compile

接下来部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
truffle(develop)> migrate --reset
Using network 'develop'.

Running migration: 1_initial_migration.js
Replacing Migrations...
... 0x9ff90a0b06bc3e9183807fe28a800030fa2b9330405eb3e86d29d16d147d83df
Migrations: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
... 0xcc6faa4193f59d7da676c7557e13271f41a8a97ca0ce171911aaf0355b92f11d
Saving artifacts...
Running migration: 2_deploy_contracts.js
Replacing rps...
... 0x44dbe0a34b7065946240a1bf14fe7b2e090aa976d5d5a8e649660952df4866fd
rps: 0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f
Saving successful migration to network...
... 0x61826f086be719997aa398ef8cbc8ba8d3fb9cdd04f7486fb0f2e0c7565d2b59
Saving artifacts...

可以看到我们的智能合约倍部署到了rps: 0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f这个地址。
我们引用这个地址并套上接口就可以调用智能合约里面的函数了。但是刚刚说过了,我们有migration.sol 这个合约来帮我们记住部署的地址,所以我们不需要关心这个地址,直接调用封装好的函数获取就行了。

Truffle的console中,默认使用第一个账户作为合约的调用者。

1
2
truffle(develop)> rps.deployed().then(function(instance){return instance.getMyBalance();});
BigNumber { s: 1, e: 19, c: [ 991693, 37699999999995 ] }

有一点比较在意的是,我们看到的blanance不是普通的整型,而是所谓的BigNumber。在Truffle里面可以直接调用bignumber.js的提供的扩展来将其转换成我们能理解的形式。

1
2
3
truffle(develop)> rps.deployed().then(function(instance){return instance.getMyBalance({from: "0xf17f52151ebef6c7334fad080c5704d77216b732"})}).then(function(value){return value.to
Number();});
99319236300000000000

我们再来看一下用户是否已经注册。

1
2
truffle(develop)> rps.deployed().then(function(instance){return instance.AmIPlayer1();});
false

可以看到用户还没有注册,现在我们开始注册用户。

1
2
3
4
5
6
7
8
9
10
11
12
truffle(develop)> rps.deployed().then(function(instance){return instance.register({value: 5});});
{ tx: '0x5d8ad0ba323971aef600d89cd9c7188edcf2b326265f9ff402be4626c7c2aba0',
receipt:
{ transactionHash: '0x5d8ad0ba323971aef600d89cd9c7188edcf2b326265f9ff402be4626c7c2aba0',
transactionIndex: 0,
blockHash: '0xaa5a88bc27b87e59556576fb4fcd613d4409c78308b957424bcafc42af9ad752',
blockNumber: 21,
gasUsed: 42526,
cumulativeGasUsed: 42526,
contractAddress: null,
logs: [] },
logs: [] }

value 表示转账的数目。这里转账数目是5wei。

接下来注册另一个用户。这个时候由于不是默认用户,所以我们在调用的时候需要带上。from 这个属性。

1
2
3
4
5
6
7
8
9
10
11
12
truffle(develop)> rps.deployed().then(function(instance){return instance.register({from: "0xf17f52151ebef6c7334fad080c5704d77216b732", value: 5});});
{ tx: '0x756985811c0543e8db681c47b500021dd1f3b202ddd090e837a5a8a11136a07e',
receipt:
{ transactionHash: '0x756985811c0543e8db681c47b500021dd1f3b202ddd090e837a5a8a11136a07e',
transactionIndex: 0,
blockHash: '0x96f175ead2a87824b556380923996cd96ce51ab02487c0ea1210a814493d6e26',
blockNumber: 22,
gasUsed: 42784,
cumulativeGasUsed: 42784,
contractAddress: null,
logs: [] },
logs: [] }

开始玩游戏。假设默认用户出石头,第二个用户出布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
truffle(develop)> rps.deployed().then(function(instance){return instance.play("paper", {from: "0xf17f52151ebef6c7334fad080c5704d77216b732"});});
{ tx: '0x78a9447e2357db30a48eb5123d343d888571a6a4a43856ee5bd6be48c78963ec',
receipt:
{ transactionHash: '0x78a9447e2357db30a48eb5123d343d888571a6a4a43856ee5bd6be48c78963ec',
transactionIndex: 0,
blockHash: '0x3e9bfed42be281c84b6a8d9041a9e2d816cbec14ddd722afb25c10f94cadcd6e',
blockNumber: 23,
gasUsed: 44004,
cumulativeGasUsed: 44004,
contractAddress: null,
logs: [] },
logs: [] }

truffle(develop)> rps.deployed().then(function(instance){return instance.play("rock");});
{ tx: '0xac42883642732fa4fc6e62e9b7edc6d6a79a33c2331d3c1f7022ad4b706b96e6',
receipt:
{ transactionHash: '0xac42883642732fa4fc6e62e9b7edc6d6a79a33c2331d3c1f7022ad4b706b96e6',
transactionIndex: 0,
blockHash: '0x28b225671df0d97d734649def71d2a962c37cded0456d18fe665b4185685e020',
blockNumber: 24,
gasUsed: 60932,
cumulativeGasUsed: 60932,
contractAddress: null,
logs: [] },
logs: [] }

然后我们看一下谁是赢家。

1
2
truffle(develop)> rps.deployed().then(function(instance){return instance.getLastWinner();});
'0xf17f52151ebef6c7334fad080c5704d77216b732'

后话

Truffle的debug console 非常强大。如果熟悉JS,那么其实也十分容易上手。
本文没有涉及test。不过有兴趣的话可以看一下Truffle官网的教程,也是手把手,十分容易上手。
那么就先这样吧。

Ethereum 入门备忘

Timestamp

timestamp和挖矿难度成反比。在决定采用哪个block的时候,会选取难度最大的block。所以一般来说矿工都会根据当前的时间戳来计算难度,然后挖矿。那么难度岂不是越来越小? 放心,时间戳是递增的,所以只要找到个反比的映射函数,比时间增长的加速度低就行了。

Ethereum 入坑的基本备忘

最近开始学习Ethereum(以太坊)以及智能协议。由于以太坊相比比特币要复杂太多,所以梳理清楚概念尤其重要。

什么是Smart Contracts(智能协议)

可以把contract看成是持久化在ethereum 的blockchain的一段程序。智能协议和普通的用户节点一样,拥有地址,可以向智能协议转账,智能协议也可以向用户转账。用户可以调用智能协议定义的函数完成一系列功能。

智能协议下的Transaction(转账)

智能协议下的转账可以分为,单纯转出转入以太币,协议的部署,更新 这集中。协议的部署即把编译好的协议写入到以太坊的blockchain中。而协议的更新就是更新已经持久化在blockchain中的一些变量的数值。我们可以把智能协议看成是一个永远不会被释放的运行在内存中的程序。我们可以改变变量的值,调用程序中的函数。

智能协议的书写方式

智能协议多采用Solidity书写。Solidity是面向智能协议编程的静态编译语言。solidity的编译器为Solc,一般简单的项目可以使用钱包自带的强大的IDE一般的功能书写。比如Mist钱包

如何调用已经存在BlockChain上面的智能协议?

调用智能协议需要知道智能协议的地址,以及ABI(源码通过编译器在本地编译的时候会产生ABI, Mist钱包会自动帮你记录ABI), ABI就像是C语言的头文件一样,知道了智能协议地址以及ABI,就可以调用ABI中定义的函数了。

gas 和 ether

部署,执行智能协议需要消耗gas,gas相当于燃料一般,需要用ether换取。一般gas 和 ether之间又稳定的换取价格。
那么为什么执行智能协议不直接消耗ether,而要在中间放一个gas呢? 那是因为ether的价格变动很大,而执行智能协议的cost相对是固定的,如果用ether来计算,那么执行智能协议的cost也会有很大波动。

Ethereum Token的实现方式

基于以太坊智能协议,用户可以发行自己的货币。
具体例子可以参考这里, 我们发现用mist钱包可以很方便追踪自己有多自己创建的货币。我们知道,我们自己发行的货币也是一个智能协议,需要调用智能协议必须知道智能协议的地址和ABI,那么当我们watch一个自己持有的货币的数量,以及转账的时候,mist钱包是怎么知道这个货币协议的ABI的呢?原来其中有个公认的标准协议,只要按照这个标准协议来设计函数,那么MIST钱包就可以很方便套用上固定的ABI啦,我们只需要提供协议地址即可。

暂时先写这么多。