LoginSignup
3
3

More than 3 years have passed since last update.

R3 Corda : Token SDKを利用したトークン発行

Last updated at Posted at 2020-01-05

【本文】

コンソーシアム型DLT(Distributed Ledger Techonology)
DLT(Distributed Ledger Techonology)の基盤を提供するR3 Cordaでは、Token SDKを通じてトークン発行が可能となっている。
Cordaでのトークンとは、端的にデジタルアセットとしての証券(など、コモディティ含めた金融商品、及び法定通貨にペッグしたステーブルコイン)を指す。

Token SDKでは、Ethreumでよく聞くfungible token(ERC20)とnon-fungible token(ERC721)の両タイプが発行可能であり、またEvolvable type(デジタルアセットのライフサイクルの中で、例えば株式分割や要綱変更によって発行時より何らかの変更が発生する。)、もしくはfixed type(法定通貨のように原則として変更が加わらない。)の2種類のタイプが存在する。

詳細は、以下参照。
Corda State Hierarchy Design : Corda Token SDK
https://github.com/corda/token-sdk/blob/master/design/design.md

今回、GitHubのテンプレートをベースにToken SDKを利用したCordaでのトークンの要綱作成・発行・移転の簡易的な実装例を紹介する。
(注:Javaで実装。本来なら、トークンを制御する為のcontractも定義するべきだが、今回は無風。)

【簡単なフロー】

トークンの要綱作成(Create)、発行(Issue)、移転(Move)のイメージとしては、以下の流れとなる。
(注:今回は償還(Redeem)は割愛。)
なお、実際のコードは次章に例示しているが、CorDappとしてデプロイ後にRPCコマンドで叩くコマンドと実行結果と合わせ、簡単なフローを紹介。

(1)
FlowのCreateを呼び出す

CreateTestTokenFlow-実行コマンド

start CreateTestTokenFlow  importantInformationThatMayChange : xxxx


(2)
Evolvable Token Typeを通じてトークン要綱が生成される。この時、evolvable IDが発行される。
(以降、発行や移転時にはこのevolvable ID(uniqueIdentifier)を指定する。)

Vault検索:実行結果例(抜粋)

CreateTestTokenFlow-実行結果例

states:
- state:
    data: !<com.template.states.TestTokenType>
      importantInformationThatMayChange: "20200105-Test"
      uniqueIdentifier:
        externalId: null
        id: "23183716-8400-4f42-a5b3-38e8aa9693b2"
      fractionDigits: 2
      maintainer: "O=Sec-A, L=Tokyo, C=JP"
    contract: "com.template.TestTokenTypeContract"
    notary: "O=Notary, L=Tokyo, C=JP"
  ref:
    txhash: "2DC31421ED1349A26F1545AE09C3C0C4FC23D502B9489FF912EC2055BA51CDFA"
    index: 0


(3)
トークンを発行する。引数はトークン名や発行数量、発行者等を定義しておく。発行時には、evolvable IDを指定する。

IssueTestTokenFlow-実行コマンド

start IssueTestTokenFlow evolvableTokenId : xx-xx-xx-xx-xx, tokenname : XXXX , quantity : nnnn , issuer : "O=AAAA, L=XXX, C=XX"

Vault検索:実行結果例(抜粋)

IssueTestTokenFlow-実行結果例

states:
- state:
    data: !<com.r3.corda.lib.tokens.contracts.states.FungibleToken>
      amount: "500.00 TokenPointer(class com.template.states.TestTokenType, 23183716-8400-4f42-a5b3-38e8aa9693b2)\
        \ issued by Sec-A"
      holder: "O=Sec-A, L=Tokyo, C=JP"
      tokenTypeJarHash: null
    contract: "com.r3.corda.lib.tokens.contracts.FungibleTokenContract"
    notary: "O=Notary, L=Tokyo, C=JP"
  ref:
    txhash: "4FFD2908FAE545B22427A23C8AAF02BB1F148A003E06A662B8AFACCF3E275B4C"
    index: 0


(4)
トークンを移転する。引数はトークン名や発行数量、移転先等を定義しておく。移転時には、evolvable IDを指定する。

MoveTestTokenFlow-実行コマンド

start MoveTestTokenFlow evolvableTokenId : xx-xx-xx-xx-xx, tokenname : XXXX, quantity : nnnn, recipient : "O=BBBB, L=XXXX, C=XX"

Vault検索:実行結果例(抜粋)「移転先」
amountが80に増加。

MoveTestTokenFlow-実行結果例

states:
- state:
    data: !<com.r3.corda.lib.tokens.contracts.states.FungibleToken>
      amount: "80.00 TokenPointer(class com.template.states.TestTokenType, 23183716-8400-4f42-a5b3-38e8aa9693b2)\
        \ issued by Sec-A"
      holder: "O=Sec-B, L=Osaka, C=JP"
      tokenTypeJarHash: null
    contract: "com.r3.corda.lib.tokens.contracts.FungibleTokenContract"
    notary: "O=Notary, L=Tokyo, C=JP"
  ref:
    txhash: "5DE519CE4101EFECFBEB242E283476D35D56C2D1F0E77549F03F10D6F27B37DA"
    index: 0

Vault検索:実行結果例(抜粋)「移転元」
amountが500->420に減少。


- state:
    data: !<com.r3.corda.lib.tokens.contracts.states.FungibleToken>
      amount: "420.00 TokenPointer(class com.template.states.TestTokenType, 23183716-8400-4f42-a5b3-38e8aa9693b2)\
        \ issued by Sec-A"
      holder: "O=Sec-A, L=Tokyo, C=JP"
      tokenTypeJarHash: null
    contract: "com.r3.corda.lib.tokens.contracts.FungibleTokenContract"
    notary: "O=Notary, L=Tokyo, C=JP"
  ref:
    txhash: "5DE519CE4101EFECFBEB242E283476D35D56C2D1F0E77549F03F10D6F27B37DA"
    index: 1

(以降、トークンの移転を複数所有者で実行)

以上の流れとなる。

【コード例】

Evolvable Token Type
(トークン・タイプ(ContractState)を定義:今回はEvolvable Type。)

TestTokenType

package com.template.states;

import com.google.common.collect.ImmutableList;
import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType;
import com.template.TestTokenTypeContract;
import net.corda.core.contracts.BelongsToContract;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.Party;

import java.util.List;
import java.util.Objects;

@BelongsToContract(TestTokenTypeContract.class)
public class TestTokenType extends EvolvableTokenType {

    private final String importantInformationThatMayChange;
    private final UniqueIdentifier uniqueIdentifier;
    private final int fractionDigits;
    private final Party maintainer;

    public TestTokenType(
            String importantInformationThatMayChange,
            UniqueIdentifier uniqueIdentifier,
            Party maintainer,
            int fractionDigits
            ) {
        this.importantInformationThatMayChange = importantInformationThatMayChange;
        this.uniqueIdentifier = uniqueIdentifier;
        this.fractionDigits = fractionDigits;
        this.maintainer = maintainer;
    }

    @Override
    public List<Party> getMaintainers() {
        return ImmutableList.of(maintainer);
    }

    @Override
    public int getFractionDigits() {
        return this.fractionDigits;
    }

    @Override
    public UniqueIdentifier getLinearId() {
        return this.uniqueIdentifier;
    }

    public String getImportantInformationThatMayChange() {
        return importantInformationThatMayChange;
    }

    public Party getMaintainer() {
        return maintainer;
    }

    public UniqueIdentifier getUniqueIdentifier() {
        return uniqueIdentifier;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestTokenType that = (TestTokenType) o;

        return getFractionDigits() == that.getFractionDigits() &&
                getImportantInformationThatMayChange().equals(that.getImportantInformationThatMayChange()) &&
                getMaintainer().equals(that.getMaintainer()) &&
                getUniqueIdentifier().equals(that.getUniqueIdentifier());
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                getImportantInformationThatMayChange(),
                getMaintainer(),
                getUniqueIdentifier(),
                getFractionDigits());
    }
}

Create Evolvable Token
(トークン発行の要綱作成のイメージ)

CreateTestTokenFlow
    /**
     * Create Evolvable Token using CreateEvolvableTokens
     */
    @StartableByRPC
    public static class CreateTestTokenFlow extends FlowLogic<SignedTransaction> {

        private final String importantInformationThatMayChange;

        public CreateTestTokenFlow(
                String importantInformationThatMayChange
        ) {
            this.importantInformationThatMayChange = importantInformationThatMayChange;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {
            //grab the notary
            Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);

            //create token type
            TestTokenType evolvableTokenType = new TestTokenType(
                    importantInformationThatMayChange,
                    new UniqueIdentifier(),
                    getOurIdentity(),
                    2
            );

            //warp it with transaction state specifying the notary
            TransactionState transactionState = new TransactionState(evolvableTokenType, notary);

            //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing
            return subFlow(new CreateEvolvableTokens(transactionState));
        }
    }

Issue Fungible Token
(トークン発行のイメージ)

IssueTestTokenFlow

    /**
     *  Issue Fungible Token using IssueTokens flow
     */
    @StartableByRPC
    public static class IssueTestTokenFlow extends FlowLogic<SignedTransaction> {
        private final String evolvableTokenId;
        private final String tokenname;
        private final Long quantity;
        private final Party issuer;

        public IssueTestTokenFlow(
                String evolvableTokenId,
                String tokenname,
                Long quantity,
                Party issuer) {
            this.evolvableTokenId = evolvableTokenId;
            this.issuer = issuer;
            this.tokenname = tokenname;
            this.quantity = quantity;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {

            UUID uuid = UUID.fromString(evolvableTokenId);

            //construct the query criteria
            QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null,
                    Vault.StateStatus.UNCONSUMED);

            // grab the token type off the ledger which was created using CreateEvolvableTokens flow
            StateAndRef<TestTokenType> stateAndRef = getServiceHub().getVaultService().
                    queryBy(TestTokenType.class, queryCriteria).getStates().get(0);
            TestTokenType evolvableTokenType = stateAndRef.getState().getData();

            //get the pointer pointer to the evolvable token type
            TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass());

            //assign the issuer who will be issuing the tokens
            IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(),tokenPointer);

            //specify how much amount to issue to holder
            Amount<IssuedTokenType> amount = new Amount(quantity, issuedTokenType);

            //create fungible amount specifying the new owner
            FungibleToken fungibleToken = new FungibleToken(amount, issuer, TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer));

            //use built in flow for issuing tokens on ledger
            return subFlow(new IssueTokens(ImmutableList.of(fungibleToken)));
        }
    }

Move Fungible Token
(トークン移転のイメージ)

MoveFungibleTokens
   /**
     *  Move Fungible Token using MoveFungibleTokens flow
     */
    @StartableByRPC
    public static class MoveTestTokenFlow extends FlowLogic<SignedTransaction> {
        private final String evolvableTokenId;
        private final String tokenname;
        private final Long quantity;
        private final Party recipient;

        public MoveTestTokenFlow(
                String evolvableTokenId,
                String tokenname,
                Long quantity,
                Party recipient) {
            this.evolvableTokenId = evolvableTokenId;
            this.recipient = recipient;
            this.tokenname = tokenname;
            this.quantity = quantity;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {

            UUID uuid = UUID.fromString(evolvableTokenId);

            //construct the query criteria
            QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null,
                    Vault.StateStatus.UNCONSUMED);

            // grab the token type off the ledger which was created using CreateEvolvableTokens flow
            StateAndRef<TestTokenType> stateAndRef = getServiceHub().getVaultService().
                    queryBy(TestTokenType.class, queryCriteria).getStates().get(0);
            TestTokenType evolvableTokenType = stateAndRef.getState().getData();

            //get the pointer pointer to the evolvable token type
            TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass());

            //specify how much amount to issue to holder
            Amount amount = new Amount<>(quantity, tokenPointer);

            PartyAndAmount partyAndAmount = new PartyAndAmount(recipient,amount);

            //use built in flow for moveing issued tokens on ledger
            return subFlow(new MoveFungibleTokens(partyAndAmount));
        }
    }

完成版: FlowWithTestToken
(トークンの発行や移転等のライフサイクル全体の各フローを定義)

FlowWithTestToken.java

package com.template.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType;
import com.r3.corda.lib.tokens.contracts.types.TokenPointer;
import com.r3.corda.lib.tokens.contracts.utilities.TransactionUtilitiesKt;
import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens;
import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens;
import com.r3.corda.lib.tokens.workflows.flows.rpc.MoveFungibleTokens;
import com.r3.corda.lib.tokens.workflows.types.PartyAndAmount;
import com.template.states.TestTokenType;
import net.corda.core.contracts.Amount;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.contracts.TransactionState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.flows.FlowException;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.StartableByRPC;
import net.corda.core.identity.Party;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import java.util.UUID;

public class FlowWithTestToken {

    private FlowWithTestToken() {
        //Instantiation not allowed
    }

    /**
     * Create Evolvable Token using CreateEvolvableTokens
     */
    @StartableByRPC
    public static class CreateTestTokenFlow extends FlowLogic<SignedTransaction> {

        private final String importantInformationThatMayChange;

        public CreateTestTokenFlow(
                String importantInformationThatMayChange
        ) {
            this.importantInformationThatMayChange = importantInformationThatMayChange;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {
            //grab the notary
            Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);

            //create token type
            TestTokenType evolvableTokenType = new TestTokenType(
                    importantInformationThatMayChange,
                    new UniqueIdentifier(),
                    getOurIdentity(),
                    2
            );

            //warp it with transaction state specifying the notary
            TransactionState transactionState = new TransactionState(evolvableTokenType, notary);

            //call built in sub flow CreateEvolvableTokens. This can be called via rpc or in unit testing
            return subFlow(new CreateEvolvableTokens(transactionState));
        }
    }

    /**
     *  Issue Fungible Token using IssueTokens flow
     */
    @StartableByRPC
    public static class IssueTestTokenFlow extends FlowLogic<SignedTransaction> {
        private final String evolvableTokenId;
        private final String tokenname;
        private final Long quantity;
        private final Party issuer;

        public IssueTestTokenFlow(
                String evolvableTokenId,
                String tokenname,
                Long quantity,
                Party issuer) {
            this.evolvableTokenId = evolvableTokenId;
            this.issuer = issuer;
            this.tokenname = tokenname;
            this.quantity = quantity;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {

            UUID uuid = UUID.fromString(evolvableTokenId);

            //construct the query criteria
            QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null,
                    Vault.StateStatus.UNCONSUMED);

            // grab the token type off the ledger which was created using CreateEvolvableTokens flow
            StateAndRef<TestTokenType> stateAndRef = getServiceHub().getVaultService().
                    queryBy(TestTokenType.class, queryCriteria).getStates().get(0);
            TestTokenType evolvableTokenType = stateAndRef.getState().getData();

            //get the pointer pointer to the evolvable token type
            TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass());

            //assign the issuer who will be issuing the tokens
            IssuedTokenType issuedTokenType = new IssuedTokenType(getOurIdentity(),tokenPointer);

            //specify how much amount to issue to holder
            Amount<IssuedTokenType> amount = new Amount(quantity, issuedTokenType);

            //create fungible amount specifying the new owner
            FungibleToken fungibleToken = new FungibleToken(amount, issuer, TransactionUtilitiesKt.getAttachmentIdForGenericParam(tokenPointer));

            //use built in flow for issuing tokens on ledger
            return subFlow(new IssueTokens(ImmutableList.of(fungibleToken)));
        }
    }

    /**
     *  Move Fungible Token using MoveFungibleTokens flow
     */
    @StartableByRPC
    public static class MoveTestTokenFlow extends FlowLogic<SignedTransaction> {
        private final String evolvableTokenId;
        private final String tokenname;
        private final Long quantity;
        private final Party recipient;

        public MoveTestTokenFlow(
                String evolvableTokenId,
                String tokenname,
                Long quantity,
                Party recipient) {
            this.evolvableTokenId = evolvableTokenId;
            this.recipient = recipient;
            this.tokenname = tokenname;
            this.quantity = quantity;
        }

        @Override
        @Suspendable
        public SignedTransaction call() throws FlowException {

            UUID uuid = UUID.fromString(evolvableTokenId);

            //construct the query criteria
            QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(uuid), null,
                    Vault.StateStatus.UNCONSUMED);

            // grab the token type off the ledger which was created using CreateEvolvableTokens flow
            StateAndRef<TestTokenType> stateAndRef = getServiceHub().getVaultService().
                    queryBy(TestTokenType.class, queryCriteria).getStates().get(0);
            TestTokenType evolvableTokenType = stateAndRef.getState().getData();

            //get the pointer pointer to the evolvable token type
            TokenPointer tokenPointer = evolvableTokenType.toPointer(evolvableTokenType.getClass());

            //specify how much amount to issue to holder
            Amount amount = new Amount<>(quantity, tokenPointer);

            PartyAndAmount partyAndAmount = new PartyAndAmount(recipient,amount);

            //use built in flow for moveing issued tokens on ledger
            return subFlow(new MoveFungibleTokens(partyAndAmount));
        }
    }
}

以上。

【参考文献】

備考:その他、日本語でも実装時にいくつか参考になる。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3