Leçon 2

FA1.2 Standardで最初のトークンを作成する

このレッスンでは、TezosのFA1.2規格を使用してファンジブルトークンを作成するプロセスを見ていきます。 SmartPyオンラインIDEを使用して、スマートコントラクトを記述してデプロイします。 FA1.2規格は、主にファンジブルトークン、つまり同一の特性を持ち、1対1で交換できるトークンに使用されることに注意してください。

ステップバイステップガイド

  1. SmartPy IDE へのアクセス

  2. まず、SmartPy オンライン IDE を開きます https://smartpy.io/ide/。 これは、スマートコントラクトの作成、テスト、デプロイに使用するプラットフォームです。

  3. FA1.2 テンプレートの開始

  4. 左サイドバーの「Templates by Type」をクリックし、「FA1.2」を選択します。 新しいタブが開き、FA1.2 契約テンプレートが表示されます。 これは、FA1.2 標準に準拠したすぐに使用できる契約です。

  5. FA1.2 テンプレートについて

  6. このテンプレートには、トークンの転送、転送の承認、残高の確認、トークンの総供給量の表示など、代替可能なトークンの基本機能があります。 このコントラクトには、トークンの鋳造と燃焼、およびガバナンス管理のための追加機能も含まれています。

  7. このテンプレートを研究し、その機能を理解していることを確認してください。 この時点ですべてを理解していなくても問題ありませんが、このコントラクトで実行できる操作の一般的な感覚をつかむようにしてください。
    たとえば、SmartPy IDEまたは以下のテンプレートからコードをコピーできます。

ニシキヘビ
# 代替可能な資産 - FA12
# https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md に触発されて

SmartPy を SP としてインポートする

#以下のメタデータは単なる例であり、ベースとして機能します。
#コンテンツは、ユーザーが
#IPFSにコピーしてアップロードできます。TZIP16_Metadata_Base = {
    "name": "SmartPy FA1.2 Token Template",
    "description": "Example Template for an FA1.2 Contract from SmartPy",
    "authors": ["SmartPy Dev Team"],
    "homepage": "https://smartpy.io",
    "interfaces": ["TZIP-007-2021-04-17", "TZIP-016-2021-04-17"],
}

@sp.モジュール
デフm():
    class AdminInterface(sp.契約):
        @sp.private(with_storage="読み取り専用")
        def is_administrator_(self, sender):
            sp.cast(sp.sender, sp.addressなど)
            """標準ではない。継承によって再定義される可能性がある。"""            True を返す

class CommonInterface(AdminInterface):
        デフ __init__(セルフ):
            AdminInterfaceです。__init__(自己)            self.data.balances = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.address、
                    sp.record(approvals=sp.map[sp.address, sp.nat]、balance=sp.nat)、                ],
            )
            self.data.total_supply = 0
            self.data.token_metadata = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.natや
                    sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes])、
                ],
            )
            self.data.metadata = sp.cast(                sp.big_map()、
                sp.big_map[sp.string, sp.bytes]です。
            )
            self.data.balances = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.address、
                    sp.record(approvals=sp.map[sp.address, sp.nat]、balance=sp.nat)、                ],
            )
            self.data.total_supply = 0
            self.data.token_metadata = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.natや
                    sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes])、
                ],
            )
            self.data.metadata = sp.cast(                sp.big_map()、
                sp.big_map[sp.string, sp.bytes]です。
            )

@sp.private(with_storage="読み取り専用")
        デフis_paused_(自己):
            """標準ではない。継承によって再定義される可能性がある。"""            Falseを返す

クラスFa1_2(CommonInterface):
        デフ __init__(自己、メタデータ、台帳、token_metadata):
            """
            token_metadata仕様:https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#token-metadata            トークン固有のメタデータは、型 (マップ文字列バイト) の Michelson 値として格納/提示されます。            いくつかのキーは予約済みで、事前定義されています。

- "" : トークンメタデータの JSON 表現を指す TZIP-016 URI に対応する必要があります。            - "name" :トークンに「表示名」を与えるUTF-8文字列である必要があります。            - "symbol" :トークンの短い識別子のUTF-8文字列である必要があります(例: XTZ、EUR、...)。
            - "decimals" : 整数 (10 進数の UTF-8 文字列に変換) である必要があります。
                これは、表示目的でトークン残高の小数点の位置を定義します。            contract_metadata仕様:https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
            """
            CommonInterfaceです。__init__(自己)            self.data.metadata = メタデータ
            self.data.token_metadata = sp.big_map(                {0: sp.record(token_id=0, token_info=token_metadata)}
            )

ledger.items()のownerの場合:                self.data.balances[所有者.キー] = owner.value (所有者.値)
                self.data.total_supply += owner.value.balance (所有者.値.残高)

# TODO:この機能が実装されたときにアクティブ化します。            # self.init_metadata("メタデータ", メタデータ)

@sp.エントリポイント
        def transfer(self, param):
            sp.cast(
                paramや
                sp.record(from_=sp.address, to_=sp.address、 value=sp.nat).layout(                    ("from_ as from", ("to_ as to", "value"))
                ),
            )
            balance_from = self.data.balances.get(                param.from_、 デフォルト=sp.record(balance=0, approvals={})
            )
            balance_to = self.data.balances.get(                param.to_、 デフォルト=sp.record(balance=0, approvals={})
            )
            balance_from.残高 = sp.as_nat(                balance_from.balance - param.value、 エラー="FA1.2_InsufficientBalance"
            )
            balance_to.balance += param.value
            self.is_administrator_(sp.sender)でない場合:                assert not self.is_paused_()、 「FA1.2_Paused」                param.from_場合 != sp.sender:
                    balance_from.承認[sp.sender] = sp.as_nat(                        balance_from.approvals[sp.sender] - param.value、                        エラー="FA1.2_NotAllowed"、
                    )
            self.data.balances[param.from_] = balance_from
            self.data.balances[param.to_] = balance_to

@sp.エントリポイント
        def approve(self, param):
            sp.cast(
                paramや
                sp.record(spender=sp.address, value=sp.nat).layout(                    (「浪費者」、「価値」)
                ),
            )
            assert not self.is_paused_()、 「FA1.2_Paused」            spender_balance = self.data.balances.get(                sp.sender、デフォルト= sp.record(balance=0、 approvals={})
            )
            alreadyApproved = spender_balance.approvals.get(param.spender, デフォルト = 0)
            アサート (
                alreadyApproved == 0 または param.value == 0
            )、「FA1.2_UnsafeAllowanceChange」            spender_balance.承認[param.spender] = param.value
            self.data.balances[sp.sender] = spender_balance

@sp.エントリポイント
        デフgetBalance(自己、パラメータ):
            (アドレス、コールバック) = param
            結果= self.data.balances.get(                アドレス、デフォルト= sp.record(balance=0、 approvals={})
            ).バランス
            sp.transfer(結果, sp.tez(0), コールバック)

@sp.エントリポイント
        デフgetAllowance(自己、パラメータ):
            (引数、コールバック)= param
            結果= self.data.balances.get(                args.ownerや デフォルト=sp.record(balance=0, approvals={})
            ).approvals.get(args.spender, デフォルト = 0)
            sp.transfer(結果, sp.tez(0), コールバック)

@sp.エントリポイント
        デフgetTotalSupply(自己、パラメータ):
            sp.cast(param, sp.pair[sp.unit, sp.contract[sp.nat]])            sp.transfer(self.data.total_supply、 sp.tez(0), sp.snd(param))

@sp.offchain_view()を呼び出します。
        def token_metadata(self, token_id):
            """指定されたトークンのトークンメタデータURIを返します。 (token_id は 0 でなければなりません)。            sp.cast(token_id、sp.nat)
            リターンself.data.token_metadata[token_id]    ##########
    # ミックスイン #
    ##########

    class Admin(sp.Contract):
        def __init__(self, administrator):
            self.data.administrator = administrator

        @sp.private(with_storage="read-only")
        def is_administrator_(self, sender):
            return sender == self.data.administrator

        @sp.entrypoint
        def setAdministrator(self, params):
            sp.cast(params, sp.address)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.administrator = params

        @sp.entrypoint()
        def getAdministrator(self, param):
            sp.cast(param, sp.pair[sp.unit, sp.contract[sp.address]])
            sp.transfer(self.data.administrator, sp.tez(0), sp.snd(param))

        @sp.onchain_view()
        def get_administrator(self):
            return self.data.administrator

    class Pause(AdminInterface):
        def __init__(self):
            AdminInterface.__init__(self)
            self.data.paused = False

        @sp.private(with_storage="read-only")
        def is_paused_(self):
            return self.data.paused

        @sp.entrypoint
        def setPause(self, param):
            sp.cast(param, sp.bool)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.paused = param

    class Mint(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def mint(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance += param.value
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply += param.value

    class Burn(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def burn(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance = sp.as_nat(
                receiver_balance.balance - param.value,
                error="FA1.2_InsufficientBalance",
            )
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply = sp.as_nat(self.data.total_supply - param.value)

    class ChangeMetadata(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def update_metadata(self, key, value):
            """An entrypoint to allow the contract metadata to be updated."""
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.metadata[key] = value

    #######
    # テスト #
    ##########

クラスFa1_2TestFull(Admin、Pause、Fa1_2、Mint、Burn、ChangeMetadata):
        デフ __init__(自己、管理者、メタデータ、台帳、token_metadata):
            ChangeMetadataです。__init__(自己)            燃える。__init__(自己)            ミント。__init__(自己)            Fa1_2。__init__(self, メタデータ、台帳、token_metadata)
            休止。__init__(自己)            管理者__init__(self、 管理者)

クラスViewer_nat(sp.契約):
        デフ __init__(セルフ):
            self.data.last = sp.cast(なし、 sp.option[sp.nat]) です。        @sp.エントリポイント
        def target(self, params):
            self.data.last = sp です。一部(パラメータ)

クラスViewer_address(sp.契約):
        デフ __init__(セルフ):
            self.data.last = sp.cast(なし、 sp.option[sp.address])        @sp.エントリポイント
        def target(self, params):
            self.data.last = sp です。一部(パラメータ)

「テンプレート」が __name__にない場合:

@sp.add_test(名前= "FA12")
    def test()を使用します。
        sc = sp.test_scenario(m)        sc.h1("FA1.2 テンプレート - 代替可能な資産")

# sp.test_account はED25519キーペアを確定的に生成します。
        admin = sp.test_account("管理者")        alice = sp.test_account("アリス")        bob = sp.test_account("ロバート")        # アカウントを表示しましょう。
        sc.h1("アカウント")
        sc.show([管理者、アリス、ボブ])

sc.h1("契約")
        token_metadata = {
            "decimals": sp.utils.bytes_of_string("18"),  # Mandatory by the spec
            "name": sp.utils.bytes_of_string("My Great Token"),  # Recommended
            "symbol": sp.utils.bytes_of_string("MGT"),  # Recommended
            # Extra fields
            "icon": sp.utils.bytes_of_string(
                "https://smartpy.io/static/img/logo-only.svg"
            ),
        }
        contract_metadata = sp.utils.metadata_of_url(            「ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd」
        )

c1 = m.Fa1_2TestFull(
            administrator=admin.addressの場合、
            metadata=contract_metadata の場合、
            token_metadata=token_metadata、
            ledger={}や
        )
        sc + = c1の

sc.h1("オフチェーンビュー - token_metadata")
        sc.verify_equal(
            SPの。ビュー(c1, "token_metadata")(0),
            sp.record(
                token_id=0 の場合、
                token_info=sp.map(                    {
                        "decimals": sp.utils.bytes_of_string("18"),
                        "name": sp.utils.bytes_of_string("My Great Token"),
                        "symbol": sp.utils.bytes_of_string("MGT"),
                        "icon": sp.utils.bytes_of_string(
                            "https://smartpy.io/static/img/logo-only.svg"
                        ),
                    }
                ),
            ),
        )

sc.h1("メタデータの更新を試みました")
        sc.verify(
            c1.data.metadata[""]            == sp.utils.bytes_of_string(                「ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd」
            )
        )
        c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
        sc.verify(c1.data.metadata[""] == sp.bytes("0x00"))

sc.h1("エントリポイント")
        sc.h2("管理者が数枚のコインを鋳造")
        c1.mint(アドレス=アリス.アドレス、値=12).run(送信者=管理者)        c1.mint(アドレス=アリス.アドレス、値=3).run(送信者=管理者)        c1.mint(アドレス=アリス.アドレス、値=3).run(送信者=管理者)        sc.h2("アリスはボブに転送します")
        c1.transfer(from_=alice.address, to_=bob.address、 値=4).run(送信者=アリス)        sc.verify(c1.data.balances[alice.address].balance == 14)
        sc.h2("ボブはアリスから転勤しようとするが、アリスの承認を得ていない")
        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            送信者=ボブ、有効=False
        )
        sc.h2("Alice が Bob と Bob の転送を承認")
        c1.approve(spender=bob.address, value=5).run(sender=alice)        c1.transfer(from_=alice.address, to_=bob.address、 値=4).run(送信者=ボブ)        sc.h2("ボブはアリスから過剰転送を試みます")
        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            送信者=ボブ、有効=False
        )
        sc.h2("管理者がボブトークンを燃やす")
        c1.burn(アドレス= bob.address、 値=1).run(送信者=管理者)        sc.verify(c1.data.balances[alice.address].balance == 10)
        sc.h2("アリスはボブトークンを燃やそうとします")
        c1.burn(アドレス= bob.address、 value=1).run(sender=alice, valid=False) です。
        sc.h2("管理者が契約を一時停止し、アリスはもう転送できません")
        c1.setPause(True).run(sender=admin)        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            sender=アリス、有効=False
        )
        sc.verify(c1.data.balances[alice.address].balance == 10)
        sc.h2("一時停止中に管理者が転送します")
        c1.transfer(from_=alice.address, to_=bob.address、 値=1).run(送信者=管理者)        sc.h2("管理者が契約の一時停止を解除し、転送が許可されます")
        c1.setPause(False).run(sender = admin)        sc.verify(c1.data.balances[alice.address].balance == 9)
        c1.transfer(from_=alice.address, to_=bob.address、 値=1).run(送信者=アリス)        sc.verify(c1.data.total_supply == 17)
        sc.verify(c1.data.balances[alice.address].balance == 8)
        sc.verify(c1.data.balances[bob.address].balance == 9)

sc.h1("ビュー")
        sc.h2("残高")
        view_balance = m.Viewer_nat()
        sc += view_balance
        target = sp.contract(sp.TNat、view_balance.address、 "ターゲット").open_some()
        c1.getBalance((alice.address, ターゲット))
        sc.verify_equal(view_balance.data.last、 sp.some(8))

sc.h2("管理者")
        view_administrator = m.Viewer_address()
        sc += view_administrator
        target = sp.contract(            SPの。TAddress、view_administrator.address、 「ターゲット」
        ).open_some()
        c1.getAdministrator((sp.unit, ターゲット))
        sc.verify_equal(view_administrator.data.last、 sp.some(admin.address))        sc.h2("総供給量")
        view_totalSupply = m.Viewer_nat()
        sc += view_totalSupply
        target = sp.contract(sp.TNat、view_totalSupply.address、 "ターゲット").open_some()
        c1.getTotalSupply((sp.unit, ターゲット))
        sc.verify_equal(view_totalSupply.data.last、 sp.some(17))

sc.h2("手当")
        view_allowance = m.Viewer_nat()
        sc += view_allowance
        target = sp.contract(sp.TNat、view_allowance.address、 "ターゲット").open_some()
        c1.getAllowance((sp.record(owner=alice.address, spender=bob.address), ターゲット))
        sc.verify_equal(view_allowance.data.last、 sp.some(1))
  1. コントラクトを実行します。 次のようなものが表示されます

元のFA1.2コントラクトには、トークンの転送、転送の承認、残高の確認、トークンの総供給量の表示などの基本的な機能があります。 次に、この機能を強化します。

  • 管理者: 管理者が契約の一時停止などの特定のアクションを実行できるようにし、他のアカウントがこれらの機能を使用できないようにする契約を導入します。
  • 一時停止:この機能を使用すると、契約を一時停止および一時停止解除できます。 契約が一時停止されると、管理者以外は誰も使用できなくなります。
  • ミント:この契約機能により、管理者は新しいトークンを作成できます。
  • バーン: このコントラクト機能を使用すると、管理者はトークンを破棄できます。
  • ChangeMetadata: この機能を使用すると、管理者はコントラクトのメタデータを更新できます。
    これらの各機能は、個別のクラスで定義されています。

万丈! FA1.2規格を使用してTezosで最初のファンジブルトークンを作成しました。

次のレッスンでは、作成したトークンコントラクトを操作する方法を学習します。 これには、トークンの転送、トークンの転送の承認、トークンの残高と総供給量の確認が含まれます。 乞うご期待!

Clause de non-responsabilité
* Les investissements en cryptomonnaies comportent des risques importants. Veuillez faire preuve de prudence. Le cours n'est pas destiné à fournir des conseils en investissement.
* Ce cours a été créé par l'auteur qui a rejoint Gate Learn. Toute opinion partagée par l'auteur ne représente pas Gate Learn.
Catalogue
Leçon 2

FA1.2 Standardで最初のトークンを作成する

このレッスンでは、TezosのFA1.2規格を使用してファンジブルトークンを作成するプロセスを見ていきます。 SmartPyオンラインIDEを使用して、スマートコントラクトを記述してデプロイします。 FA1.2規格は、主にファンジブルトークン、つまり同一の特性を持ち、1対1で交換できるトークンに使用されることに注意してください。

ステップバイステップガイド

  1. SmartPy IDE へのアクセス

  2. まず、SmartPy オンライン IDE を開きます https://smartpy.io/ide/。 これは、スマートコントラクトの作成、テスト、デプロイに使用するプラットフォームです。

  3. FA1.2 テンプレートの開始

  4. 左サイドバーの「Templates by Type」をクリックし、「FA1.2」を選択します。 新しいタブが開き、FA1.2 契約テンプレートが表示されます。 これは、FA1.2 標準に準拠したすぐに使用できる契約です。

  5. FA1.2 テンプレートについて

  6. このテンプレートには、トークンの転送、転送の承認、残高の確認、トークンの総供給量の表示など、代替可能なトークンの基本機能があります。 このコントラクトには、トークンの鋳造と燃焼、およびガバナンス管理のための追加機能も含まれています。

  7. このテンプレートを研究し、その機能を理解していることを確認してください。 この時点ですべてを理解していなくても問題ありませんが、このコントラクトで実行できる操作の一般的な感覚をつかむようにしてください。
    たとえば、SmartPy IDEまたは以下のテンプレートからコードをコピーできます。

ニシキヘビ
# 代替可能な資産 - FA12
# https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md に触発されて

SmartPy を SP としてインポートする

#以下のメタデータは単なる例であり、ベースとして機能します。
#コンテンツは、ユーザーが
#IPFSにコピーしてアップロードできます。TZIP16_Metadata_Base = {
    "name": "SmartPy FA1.2 Token Template",
    "description": "Example Template for an FA1.2 Contract from SmartPy",
    "authors": ["SmartPy Dev Team"],
    "homepage": "https://smartpy.io",
    "interfaces": ["TZIP-007-2021-04-17", "TZIP-016-2021-04-17"],
}

@sp.モジュール
デフm():
    class AdminInterface(sp.契約):
        @sp.private(with_storage="読み取り専用")
        def is_administrator_(self, sender):
            sp.cast(sp.sender, sp.addressなど)
            """標準ではない。継承によって再定義される可能性がある。"""            True を返す

class CommonInterface(AdminInterface):
        デフ __init__(セルフ):
            AdminInterfaceです。__init__(自己)            self.data.balances = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.address、
                    sp.record(approvals=sp.map[sp.address, sp.nat]、balance=sp.nat)、                ],
            )
            self.data.total_supply = 0
            self.data.token_metadata = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.natや
                    sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes])、
                ],
            )
            self.data.metadata = sp.cast(                sp.big_map()、
                sp.big_map[sp.string, sp.bytes]です。
            )
            self.data.balances = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.address、
                    sp.record(approvals=sp.map[sp.address, sp.nat]、balance=sp.nat)、                ],
            )
            self.data.total_supply = 0
            self.data.token_metadata = sp.cast(                sp.big_map()、
                sp.big_map[
                    sp.natや
                    sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes])、
                ],
            )
            self.data.metadata = sp.cast(                sp.big_map()、
                sp.big_map[sp.string, sp.bytes]です。
            )

@sp.private(with_storage="読み取り専用")
        デフis_paused_(自己):
            """標準ではない。継承によって再定義される可能性がある。"""            Falseを返す

クラスFa1_2(CommonInterface):
        デフ __init__(自己、メタデータ、台帳、token_metadata):
            """
            token_metadata仕様:https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#token-metadata            トークン固有のメタデータは、型 (マップ文字列バイト) の Michelson 値として格納/提示されます。            いくつかのキーは予約済みで、事前定義されています。

- "" : トークンメタデータの JSON 表現を指す TZIP-016 URI に対応する必要があります。            - "name" :トークンに「表示名」を与えるUTF-8文字列である必要があります。            - "symbol" :トークンの短い識別子のUTF-8文字列である必要があります(例: XTZ、EUR、...)。
            - "decimals" : 整数 (10 進数の UTF-8 文字列に変換) である必要があります。
                これは、表示目的でトークン残高の小数点の位置を定義します。            contract_metadata仕様:https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md
            """
            CommonInterfaceです。__init__(自己)            self.data.metadata = メタデータ
            self.data.token_metadata = sp.big_map(                {0: sp.record(token_id=0, token_info=token_metadata)}
            )

ledger.items()のownerの場合:                self.data.balances[所有者.キー] = owner.value (所有者.値)
                self.data.total_supply += owner.value.balance (所有者.値.残高)

# TODO:この機能が実装されたときにアクティブ化します。            # self.init_metadata("メタデータ", メタデータ)

@sp.エントリポイント
        def transfer(self, param):
            sp.cast(
                paramや
                sp.record(from_=sp.address, to_=sp.address、 value=sp.nat).layout(                    ("from_ as from", ("to_ as to", "value"))
                ),
            )
            balance_from = self.data.balances.get(                param.from_、 デフォルト=sp.record(balance=0, approvals={})
            )
            balance_to = self.data.balances.get(                param.to_、 デフォルト=sp.record(balance=0, approvals={})
            )
            balance_from.残高 = sp.as_nat(                balance_from.balance - param.value、 エラー="FA1.2_InsufficientBalance"
            )
            balance_to.balance += param.value
            self.is_administrator_(sp.sender)でない場合:                assert not self.is_paused_()、 「FA1.2_Paused」                param.from_場合 != sp.sender:
                    balance_from.承認[sp.sender] = sp.as_nat(                        balance_from.approvals[sp.sender] - param.value、                        エラー="FA1.2_NotAllowed"、
                    )
            self.data.balances[param.from_] = balance_from
            self.data.balances[param.to_] = balance_to

@sp.エントリポイント
        def approve(self, param):
            sp.cast(
                paramや
                sp.record(spender=sp.address, value=sp.nat).layout(                    (「浪費者」、「価値」)
                ),
            )
            assert not self.is_paused_()、 「FA1.2_Paused」            spender_balance = self.data.balances.get(                sp.sender、デフォルト= sp.record(balance=0、 approvals={})
            )
            alreadyApproved = spender_balance.approvals.get(param.spender, デフォルト = 0)
            アサート (
                alreadyApproved == 0 または param.value == 0
            )、「FA1.2_UnsafeAllowanceChange」            spender_balance.承認[param.spender] = param.value
            self.data.balances[sp.sender] = spender_balance

@sp.エントリポイント
        デフgetBalance(自己、パラメータ):
            (アドレス、コールバック) = param
            結果= self.data.balances.get(                アドレス、デフォルト= sp.record(balance=0、 approvals={})
            ).バランス
            sp.transfer(結果, sp.tez(0), コールバック)

@sp.エントリポイント
        デフgetAllowance(自己、パラメータ):
            (引数、コールバック)= param
            結果= self.data.balances.get(                args.ownerや デフォルト=sp.record(balance=0, approvals={})
            ).approvals.get(args.spender, デフォルト = 0)
            sp.transfer(結果, sp.tez(0), コールバック)

@sp.エントリポイント
        デフgetTotalSupply(自己、パラメータ):
            sp.cast(param, sp.pair[sp.unit, sp.contract[sp.nat]])            sp.transfer(self.data.total_supply、 sp.tez(0), sp.snd(param))

@sp.offchain_view()を呼び出します。
        def token_metadata(self, token_id):
            """指定されたトークンのトークンメタデータURIを返します。 (token_id は 0 でなければなりません)。            sp.cast(token_id、sp.nat)
            リターンself.data.token_metadata[token_id]    ##########
    # ミックスイン #
    ##########

    class Admin(sp.Contract):
        def __init__(self, administrator):
            self.data.administrator = administrator

        @sp.private(with_storage="read-only")
        def is_administrator_(self, sender):
            return sender == self.data.administrator

        @sp.entrypoint
        def setAdministrator(self, params):
            sp.cast(params, sp.address)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.administrator = params

        @sp.entrypoint()
        def getAdministrator(self, param):
            sp.cast(param, sp.pair[sp.unit, sp.contract[sp.address]])
            sp.transfer(self.data.administrator, sp.tez(0), sp.snd(param))

        @sp.onchain_view()
        def get_administrator(self):
            return self.data.administrator

    class Pause(AdminInterface):
        def __init__(self):
            AdminInterface.__init__(self)
            self.data.paused = False

        @sp.private(with_storage="read-only")
        def is_paused_(self):
            return self.data.paused

        @sp.entrypoint
        def setPause(self, param):
            sp.cast(param, sp.bool)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.paused = param

    class Mint(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def mint(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance += param.value
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply += param.value

    class Burn(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def burn(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance = sp.as_nat(
                receiver_balance.balance - param.value,
                error="FA1.2_InsufficientBalance",
            )
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply = sp.as_nat(self.data.total_supply - param.value)

    class ChangeMetadata(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)

        @sp.entrypoint
        def update_metadata(self, key, value):
            """An entrypoint to allow the contract metadata to be updated."""
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.metadata[key] = value

    #######
    # テスト #
    ##########

クラスFa1_2TestFull(Admin、Pause、Fa1_2、Mint、Burn、ChangeMetadata):
        デフ __init__(自己、管理者、メタデータ、台帳、token_metadata):
            ChangeMetadataです。__init__(自己)            燃える。__init__(自己)            ミント。__init__(自己)            Fa1_2。__init__(self, メタデータ、台帳、token_metadata)
            休止。__init__(自己)            管理者__init__(self、 管理者)

クラスViewer_nat(sp.契約):
        デフ __init__(セルフ):
            self.data.last = sp.cast(なし、 sp.option[sp.nat]) です。        @sp.エントリポイント
        def target(self, params):
            self.data.last = sp です。一部(パラメータ)

クラスViewer_address(sp.契約):
        デフ __init__(セルフ):
            self.data.last = sp.cast(なし、 sp.option[sp.address])        @sp.エントリポイント
        def target(self, params):
            self.data.last = sp です。一部(パラメータ)

「テンプレート」が __name__にない場合:

@sp.add_test(名前= "FA12")
    def test()を使用します。
        sc = sp.test_scenario(m)        sc.h1("FA1.2 テンプレート - 代替可能な資産")

# sp.test_account はED25519キーペアを確定的に生成します。
        admin = sp.test_account("管理者")        alice = sp.test_account("アリス")        bob = sp.test_account("ロバート")        # アカウントを表示しましょう。
        sc.h1("アカウント")
        sc.show([管理者、アリス、ボブ])

sc.h1("契約")
        token_metadata = {
            "decimals": sp.utils.bytes_of_string("18"),  # Mandatory by the spec
            "name": sp.utils.bytes_of_string("My Great Token"),  # Recommended
            "symbol": sp.utils.bytes_of_string("MGT"),  # Recommended
            # Extra fields
            "icon": sp.utils.bytes_of_string(
                "https://smartpy.io/static/img/logo-only.svg"
            ),
        }
        contract_metadata = sp.utils.metadata_of_url(            「ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd」
        )

c1 = m.Fa1_2TestFull(
            administrator=admin.addressの場合、
            metadata=contract_metadata の場合、
            token_metadata=token_metadata、
            ledger={}や
        )
        sc + = c1の

sc.h1("オフチェーンビュー - token_metadata")
        sc.verify_equal(
            SPの。ビュー(c1, "token_metadata")(0),
            sp.record(
                token_id=0 の場合、
                token_info=sp.map(                    {
                        "decimals": sp.utils.bytes_of_string("18"),
                        "name": sp.utils.bytes_of_string("My Great Token"),
                        "symbol": sp.utils.bytes_of_string("MGT"),
                        "icon": sp.utils.bytes_of_string(
                            "https://smartpy.io/static/img/logo-only.svg"
                        ),
                    }
                ),
            ),
        )

sc.h1("メタデータの更新を試みました")
        sc.verify(
            c1.data.metadata[""]            == sp.utils.bytes_of_string(                「ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd」
            )
        )
        c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
        sc.verify(c1.data.metadata[""] == sp.bytes("0x00"))

sc.h1("エントリポイント")
        sc.h2("管理者が数枚のコインを鋳造")
        c1.mint(アドレス=アリス.アドレス、値=12).run(送信者=管理者)        c1.mint(アドレス=アリス.アドレス、値=3).run(送信者=管理者)        c1.mint(アドレス=アリス.アドレス、値=3).run(送信者=管理者)        sc.h2("アリスはボブに転送します")
        c1.transfer(from_=alice.address, to_=bob.address、 値=4).run(送信者=アリス)        sc.verify(c1.data.balances[alice.address].balance == 14)
        sc.h2("ボブはアリスから転勤しようとするが、アリスの承認を得ていない")
        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            送信者=ボブ、有効=False
        )
        sc.h2("Alice が Bob と Bob の転送を承認")
        c1.approve(spender=bob.address, value=5).run(sender=alice)        c1.transfer(from_=alice.address, to_=bob.address、 値=4).run(送信者=ボブ)        sc.h2("ボブはアリスから過剰転送を試みます")
        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            送信者=ボブ、有効=False
        )
        sc.h2("管理者がボブトークンを燃やす")
        c1.burn(アドレス= bob.address、 値=1).run(送信者=管理者)        sc.verify(c1.data.balances[alice.address].balance == 10)
        sc.h2("アリスはボブトークンを燃やそうとします")
        c1.burn(アドレス= bob.address、 value=1).run(sender=alice, valid=False) です。
        sc.h2("管理者が契約を一時停止し、アリスはもう転送できません")
        c1.setPause(True).run(sender=admin)        c1.transfer(from_=alice.address, to_=bob.address、 value=4).run(            sender=アリス、有効=False
        )
        sc.verify(c1.data.balances[alice.address].balance == 10)
        sc.h2("一時停止中に管理者が転送します")
        c1.transfer(from_=alice.address, to_=bob.address、 値=1).run(送信者=管理者)        sc.h2("管理者が契約の一時停止を解除し、転送が許可されます")
        c1.setPause(False).run(sender = admin)        sc.verify(c1.data.balances[alice.address].balance == 9)
        c1.transfer(from_=alice.address, to_=bob.address、 値=1).run(送信者=アリス)        sc.verify(c1.data.total_supply == 17)
        sc.verify(c1.data.balances[alice.address].balance == 8)
        sc.verify(c1.data.balances[bob.address].balance == 9)

sc.h1("ビュー")
        sc.h2("残高")
        view_balance = m.Viewer_nat()
        sc += view_balance
        target = sp.contract(sp.TNat、view_balance.address、 "ターゲット").open_some()
        c1.getBalance((alice.address, ターゲット))
        sc.verify_equal(view_balance.data.last、 sp.some(8))

sc.h2("管理者")
        view_administrator = m.Viewer_address()
        sc += view_administrator
        target = sp.contract(            SPの。TAddress、view_administrator.address、 「ターゲット」
        ).open_some()
        c1.getAdministrator((sp.unit, ターゲット))
        sc.verify_equal(view_administrator.data.last、 sp.some(admin.address))        sc.h2("総供給量")
        view_totalSupply = m.Viewer_nat()
        sc += view_totalSupply
        target = sp.contract(sp.TNat、view_totalSupply.address、 "ターゲット").open_some()
        c1.getTotalSupply((sp.unit, ターゲット))
        sc.verify_equal(view_totalSupply.data.last、 sp.some(17))

sc.h2("手当")
        view_allowance = m.Viewer_nat()
        sc += view_allowance
        target = sp.contract(sp.TNat、view_allowance.address、 "ターゲット").open_some()
        c1.getAllowance((sp.record(owner=alice.address, spender=bob.address), ターゲット))
        sc.verify_equal(view_allowance.data.last、 sp.some(1))
  1. コントラクトを実行します。 次のようなものが表示されます

元のFA1.2コントラクトには、トークンの転送、転送の承認、残高の確認、トークンの総供給量の表示などの基本的な機能があります。 次に、この機能を強化します。

  • 管理者: 管理者が契約の一時停止などの特定のアクションを実行できるようにし、他のアカウントがこれらの機能を使用できないようにする契約を導入します。
  • 一時停止:この機能を使用すると、契約を一時停止および一時停止解除できます。 契約が一時停止されると、管理者以外は誰も使用できなくなります。
  • ミント:この契約機能により、管理者は新しいトークンを作成できます。
  • バーン: このコントラクト機能を使用すると、管理者はトークンを破棄できます。
  • ChangeMetadata: この機能を使用すると、管理者はコントラクトのメタデータを更新できます。
    これらの各機能は、個別のクラスで定義されています。

万丈! FA1.2規格を使用してTezosで最初のファンジブルトークンを作成しました。

次のレッスンでは、作成したトークンコントラクトを操作する方法を学習します。 これには、トークンの転送、トークンの転送の承認、トークンの残高と総供給量の確認が含まれます。 乞うご期待!

Clause de non-responsabilité
* Les investissements en cryptomonnaies comportent des risques importants. Veuillez faire preuve de prudence. Le cours n'est pas destiné à fournir des conseils en investissement.
* Ce cours a été créé par l'auteur qui a rejoint Gate Learn. Toute opinion partagée par l'auteur ne représente pas Gate Learn.