IRC21 標準
自前でデプロイするトークンのための標準インターフェースです。
概要
IOST にデプロイする標準トークンは、必ずシステムコントラクト token.iost を基に実装する必要があります。 多くの場合は token.iost コントラクトを通じて直接トークンを作成できますが、 カスタマイズしたトークンを作成したい場合は、自前のトークンコントラクトを実装してデプロイする必要があります。
カスタムトークンコントラクトは、ウォレットや取引所などのアプリケーションをサポートするために、以下のインターフェースを実装する必要があります。
ABI
{
"lang": "javascript",
"version": "1.0.0", // または別バージョン
"abi": [
// optional
{
"name": "issue",
"args": [
"string", // token_symbol
"string", // to
"string" // amount
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
// required
{
"name": "transfer",
"args": [
"string", // token_symbol
"string", // from
"string", // to
"string", // amount
"string" // memo
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
// optional
{
"name": "transferFreeze",
"args": [
"string", // token_symbol
"string", // from
"string", // to
"string", // amount
"number", // ナノ秒単位のタイムスタンプ
"string" // memo
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
// optional
{
"name": "destroy",
"args": [
"string", // token_symbol
"string", // from
"string" // amount
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
// optional
{
"name": "supply",
"args": [
"string" // token_symbol
]
},
// optional
{
"name": "totalSupply",
"args": [
"string" // token_symbol
]
},
// optional
{
"name": "balanceOf",
"args": [
"string", // token_symbol
"string" // owner
]
}
]
}
仕様
トークン情報
トークン情報は token.iost に保存されます。ウォレットなどのアプリケーションは情報の信頼性を確保するため、token.iost コントラクトに保存された情報を直接利用してください。
issue(tokenSymbol, acc, amountStr)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: tokenSymbol の issuer
acc アカウントへ tokenSymbol を発行します。amountStr は発行量を示す文字列で、"100"、"100.999" のような正の固定小数点表現でなければなりません。
transfer(tokenSymbol, accFrom, accTo, amountStr, memo)
必要な権限: accFrom
accFrom から accTo へ tokenSymbol を amountStr 分、memo と共に送金します。
amount は正の固定小数点表現、memo は本送金に対する 512 バイトを超えない追加文字列メッセージです。
送金成功の判定基準は token.iost コントラクトの仕様と同じです。トランザクションが成功した上で、tx_receipt の receipts フィールドのうち func_name が token.iost/transfer の項目があれば送金成立です。通貨、アカウント、金額は該当項目の content フィールドからさらにパースしてください。詳細は 送金成功の判定方法 を参照。
transferFreeze(tokenSymbol, accFrom, accTo, amountStr, unfreezeTime, memo)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: accFrom
accFrom から accTo へ tokenSymbol を amountStr 分、memo と共に送金しつつ、unfreezeTime まで該当量のトークンを凍結します。
unfreezeTime は凍結解除時刻の unix 時間をナノ秒単位で表したものです。
送金成功の判定基準は token.iost コントラクトの仕様と同じです。トランザクションが成功した上で、tx_receipt の receipts フィールドのうち func_name が token.iost/transferFreeze の項目があれば送金成立です。通貨、アカウント、金額、unfreezeTime は該当項目の content フィールドからさらにパースしてください。詳細は 送金成功の判定方法 を参照。
destroy(tokenSymbol, accFrom, amountStr)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: accFrom
accFrom アカウントのトークンを amountStr 分破棄します。破棄後、このトークンの supply も同量だけ減ります。つまり、一部トークンを破棄することで totalSupply の範囲内で追加発行が可能になります。
balanceOf(tokenSymbol, acc)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: なし
特定のトークンのアカウント残高を取得します。
supply(tokenSymbol)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: なし
特定のトークンの現在の流通量を取得します。
totalSupply(tokenSymbol)
Optional: アプリケーションは本メソッドの存在を前提にしてはいけません
必要な権限: なし
特定のトークンの totalSupply を取得します。
実装
以下に基本実装を示します。コードを書き換えてカスタマイズしてください。
// ABI:
{
"lang": "javascript",
"version": "1.0.0",
"abi": [
{
"name": "can_update",
"args": [
"string"
]
},
{
"name": "issue",
"args": [
"string",
"string",
"string"
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
{
"name": "transfer",
"args": [
"string",
"string",
"string",
"string",
"string"
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
{
"name": "transferFreeze",
"args": [
"string",
"string",
"string",
"string",
"number",
"string"
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
{
"name": "destroy",
"args": [
"string",
"string",
"string"
],
"amountLimit": [{
"token": "*",
"val": "unlimited"
}]
},
{
"name": "supply",
"args": [
"string"
]
},
{
"name": "totalSupply",
"args": [
"string"
]
},
{
"name": "balanceOf",
"args": [
"string",
"string"
]
}
]
}
// code:
const name = "ytk";
const fullName = "YTK Stable Coin"; // ウォレットやブラウザではトークン名を "name(fullName)" 形式で表示することを推奨します。例: ytk(YTK stable coin)
const decimal = 8;
const totalSupply = 90000000000;
const admin = "your_admin";
class Token {
init() {
blockchain.callWithAuth("token.iost", "create", [
name,
blockchain.contractName(),
totalSupply,
{
fullName,
decimal,
canTransfer: true,
onlyIssuerCanTransfer: true,
}
]);
}
can_update(data) {
return blockchain.requireAuth(blockchain.contractOwner(), "active");
}
_amount(amount) {
return new BigNumber(new BigNumber(amount).toFixed(decimal));
}
_checkToken(token_name) {
if (token_name !== name) {
throw "token not exist";
}
}
issue(token_name, to, amount) {
if (!blockchain.requireAuth(admin, "active")) {
throw "permission denied";
}
this._checkToken(token_name);
amount = this._amount(amount);
blockchain.callWithAuth("token.iost", "issue", [token_name, to, amount]);
}
transfer(token_name, from, to, amount, memo) {
this._checkToken(token_name);
amount = this._amount(amount);
blockchain.callWithAuth("token.iost", "transfer", [token_name, from, to, amount, memo])
}
transferFreeze(token_name, from, to, amount, timestamp, memo) {
this._checkToken(token_name);
amount = this._amount(amount);
blockchain.callWithAuth("token.iost", "transferFreeze", [token_name, from, to, amount, timestamp, memo]);
}
destroy(token_name, from, amount) {
this._checkToken(token_name);
amount = this._amount(amount);
blockchain.callWithAuth("token.iost", "destroy", [token_name, from, amount]);
}
// ABI を呼び出して結果を JSON 文字列としてパース
_call(contract, api, args) {
const ret = blockchain.callWithAuth(contract, api, args);
if (ret && Array.isArray(ret) && ret.length >= 1) {
return ret[0];
}
return null;
}
balanceOf(token_name, owner) {
this._checkToken(token_name);
return this._call("token.iost", "balanceOf", [token_name, owner]);
}
supply(token_name) {
this._checkToken(token_name);
return this._call("token.iost", "supply", [token_name]);
}
totalSupply(token_name) {
this._checkToken(token_name);
return this._call("token.iost", "totalSupply", [token_name]);
}
}
module.exports = Token;