メインコンテンツまでスキップ
バージョン: 3.5.0

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_receiptreceipts フィールドのうち func_nametoken.iost/transfer の項目があれば送金成立です。通貨、アカウント、金額は該当項目の content フィールドからさらにパースしてください。詳細は 送金成功の判定方法 を参照。

transferFreeze(tokenSymbol, accFrom, accTo, amountStr, unfreezeTime, memo)

Optional: アプリケーションは本メソッドの存在を前提にしてはいけません

必要な権限: accFrom

accFrom から accTo へ tokenSymbol を amountStr 分、memo と共に送金しつつ、unfreezeTime まで該当量のトークンを凍結します。 unfreezeTime は凍結解除時刻の unix 時間をナノ秒単位で表したものです。

送金成功の判定基準は token.iost コントラクトの仕様と同じです。トランザクションが成功した上で、tx_receiptreceipts フィールドのうち func_nametoken.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;