Lektion 2

Erstellen Sie Ihr erstes Token mit FA1.2 Standard

In dieser Lektion werden wir den Prozess der Erstellung eines fungiblen Tokens unter Verwendung des FA1.2-Standards auf Tezos durchlaufen. Wir werden die SmartPy-Online-IDE verwenden, um unseren Smart Contract zu schreiben und bereitzustellen. Beachten Sie, dass der FA1.2-Standard hauptsächlich für fungible Token verwendet wird, d. h. Token, die identische Eigenschaften haben und eins zu eins ausgetauscht werden können.

Schritt für Schritt Anleitung

  1. Zugriff auf die SmartPy-IDE

  2. Öffnen Sie zunächst die SmartPy-Online-IDE unter https://smartpy.io/ide/. Dies ist die Plattform, die wir zum Schreiben, Testen und Bereitstellen unserer Smart Contracts verwenden werden.

  3. Initiieren der FA1.2-Vorlage

  4. Klicken Sie in der linken Seitenleiste auf „Vorlagen nach Typ“ und wählen Sie „FA1.2“. Es öffnet sich ein neuer Tab mit der FA1.2-Vertragsvorlage. Hierbei handelt es sich um einen gebrauchsfertigen Vertrag, der dem FA1.2-Standard folgt.

  5. Die FA1.2-Vorlage verstehen

  6. Diese Vorlage verfügt über die Grundfunktionen für einen fungiblen Token, einschließlich der Übertragung von Token, der Genehmigung von Übertragungen, der Überprüfung von Guthaben und der Anzeige des gesamten Token-Vorrats. Der Vertrag umfasst außerdem zusätzliche Funktionen zum Prägen und Brennen von Token sowie zum Governance-Management.

  7. Studieren Sie diese Vorlage und stellen Sie sicher, dass Sie ihre Funktionen verstehen. Es ist in Ordnung, wenn Sie an dieser Stelle nicht alles verstehen, aber versuchen Sie, einen allgemeinen Eindruck von den Vorgängen zu bekommen, die dieser Vertrag ausführen kann.
    Sie können den Code beispielsweise aus der Vorlage in der SmartPy-IDE oder hier unten kopieren:

Python 
 # Fungible Assets – FA12 
 # Inspiriert von https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 Smartpy als SP importieren 


 # Die folgenden Metadaten sind nur ein Beispiel dient als Basis, 
 # der Inhalt wird zum Erstellen der Metadaten-JSON verwendet, die Benutzer 
 # kopieren und auf IPFS hochladen können.
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.module 
 def m(): 
 class AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.sender, sp.address) 
 """Nicht Standard, kann durch Vererbung neu definiert werden."""
            return True 

 class CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(selbst)
            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="read-only") 
 def is_paused_(self): 
 """Nicht Standard, kann durch Vererbung neu definiert werden."""
            return False 

 class Fa1_2(CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ Proposals/tzip-12/tzip-12.md#token-metadata
            Tokenspezifische Metadaten werden als Michelson-Wert vom Typ (Mapping-String-Bytes) gespeichert/dargestellt.
            Einige der Schlüssel sind reserviert und vordefiniert: 

 – „“: Sollte einem TZIP-016-URI entsprechen, der auf eine JSON-Darstellung der Token-Metadaten verweist.
            - „Name“: Sollte eine UTF-8-Zeichenfolge sein, die dem Token einen „Anzeigenamen“ gibt.
            - „Symbol“: Sollte eine UTF-8-Zeichenfolge für die Kurzkennung des Tokens sein (z. B XTZ, EUR, …). 
 – „Dezimalzahlen“: Sollte eine Ganzzahl sein (konvertiert in eine UTF-8-Zeichenfolge im Dezimalformat) 
 , die die Position des Dezimalpunkts in Token-Salden für Anzeigezwecke definiert.

            Contract_Metadata-Spezifikation: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 „““ 
 CommonInterface.__init__(selbst)
            self.data.metadata = Metadaten 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 für Besitzer in ledger.items():
                self.data.balances[owner.key] = Eigentümer.Wert 
 self.data.total_supply +=owner.value.balance 

 # TODO: Aktivieren, wenn diese Funktion implementiert ist.
            # self.init_metadata("metadata", Metadaten) 

 @sp.entrypoint 
 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_, default=sp.record(balance=0, Approvals={}) 
 ) 
 balance_to = self.data.balances.get(
                param.to_, default=sp.record(balance=0, genehmigt={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance – param.value, error="FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 wenn nicht self.is_administrator_(sp.sender):
                behaupten nicht self.is_paused_(), „FA1.2_Paused“
                wenn param.from_ != sp.sender: 
 balance_from.approvals[sp.sender] = sp.as_nat(
                        balance_from.approvals[sp.sender] – param.value,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_from 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def genehmigt(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, value=sp.nat).layout(
                    ("spender", "value") 
 ), 
 ) 
 activate not self.is_paused_(), „FA1.2_Paused“
            spender_balance = self.data.balances.get(
                sp.sender, default=sp.record(balance=0, Approvals={}) 
 ) 
 bereitsApproved = spender_balance.approvals.get(param.spender, Standard = 0) 
 Assert ( 
 bereits Approved == 0 oder param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            spender_balance.approvals[param.spender] = param.value 
 self.data.balances[sp.sender] = spender_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (address, callback) = param 
 result = self.data.balances.get(
                Adresse, default=sp.record(balance=0, Approvals={}) 
 ).balance 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, callback) = param 
 result = self.data.balances.get(
                args.owner, default=sp.record(balance=0, Approvals={}) 
 ).approvals.get(args.spender, default=0) 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 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): 
 """Gibt den Token-Metadaten-URI für das angegebene Token zurück. (token_id muss 0 sein)."""
            sp.cast(token_id, sp.nat) 
 return self.data.token_metadata[token_id]

    ########## 
 # Mixins # 
 ##########

    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

    ########## 
 # Tests # 
 ########## 

 Klasse Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(selbst, Administrator, Metadaten, Ledger, token_metadata): 
 ChangeMetadata.__init__(selbst)
            Brennen.__init__(selbst)
            Minze.__init__(selbst)
            Fa1_2.__init__(selbst, metadata, ledger, token_metadata) 
 Pause.__init__(selbst)
            Administrator.__init__(selbst, Administrator) 

 Klasse Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.option[sp.nat])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 

 class Viewer_address(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.option[sp.address])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 


 if „templates“ not in __name__: 

 @sp.add_test(name="FA12") 
 def test(): 
 sc = sp.test_scenario(m)
        sc.h1("FA1.2-Vorlage - Fungible Assets") 

 # sp.test_account generiert deterministisch ED25519-Schlüsselpaare: 
 admin = sp.test_account("Administrator")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Lassen Sie uns die Konten anzeigen: 
 sc.h1("Accounts") 
 sc.show([admin, alice, bob] 
 

 sc.h1("Contract") 
 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"
            ),
        }
        5tract_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("Offchain-Ansicht - token_metadata") 
 sc.verify_equal( 
 sp.View(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("Versuch, Metadaten zu aktualisieren") 
 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("Entrypoints") 
 sc.h2("Admin prägt ein paar Münzen") 
 c1.mint(address=alice.address, value=12).run (Absender=Administrator)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        sc.h2("Alice wechselt zu Bob") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob versucht, von Alice zu übertragen, aber er hat nicht ihre Zustimmung") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice genehmigt Bob- und Bob-Transfers") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob)
        sc.h2("Bob versucht, von Alice zu viel zu übertragen") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Admin verbrennt Bob-Token") 
 c1.burn(address=bob.address, value=1).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice versucht, Bob-Token zu verbrennen") 
 c1.burn(address=bob.address, value=1).run(sender=alice, valid=False) 
 sc.h2("Admin pausiert den Vertrag und Alice kann nicht mehr übertragen") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=alice, valid=False 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Admin transfers while on pause") 
 c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=admin)
        sc.h2("Der Administrator hebt die Pause des Vertrags auf und Übertragungen sind zulässig") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=alice)

        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("Views") 
 sc.h2("Balance") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "target").open_some() 
 c1.getBalance((alice.address, Ziel)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrator") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAddress, view_administrator.address, "target" 
 ).open_some() 
 c1.getAdministrator((sp.unit, target)) 
 sc.verify_equal(view_administrator.data.last, sp.some(admin.address))

        sc.h2("Total Supply") 
 view_totalSupply = m.Viewer_nat() 
 sc += view_totalSupply 
 target = sp.contract(sp.TNat, view_totalSupply.address, "target").open_some() 
 c1.getTotalSupply((sp.unit, target)) 
 sc.verify_equal(view_totalSupply.data.last, sp.some(17)) 

 sc.h2("Allowance") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "target").open_some() 
 c1.getAllowance((sp.record(owner=alice.address, spender=bob.address), target)) 
 sc.verify_equal(view_allowance.data.last, sp.some(1))
  1. Führen Sie den Vertrag aus. Sie werden so etwas sehen

Der ursprüngliche FA1.2-Vertrag verfügt über grundlegende Funktionen wie die Übertragung von Token, die Genehmigung von Übertragungen, die Überprüfung von Guthaben und die Anzeige des Gesamtbestands an Token. Jetzt werden wir diese Funktionalität erweitern.

  • Admin: Wir werden einen Vertrag einführen, der es einem Administrator ermöglicht, bestimmte Aktionen durchzuführen, wie z. B. das Pausieren des Vertrags, und andere Konten daran hindert, diese Funktionen zu nutzen.
  • Pause: Mit dieser Funktion können wir den Vertrag pausieren und wieder aufheben. Wenn der Vertrag pausiert ist, kann ihn niemand außer dem Administrator verwenden.
  • Mint: Mit dieser Vertragsfunktion kann der Administrator neue Token erstellen.
  • Brennen: Diese Vertragsfunktion ermöglicht es dem Administrator, Token zu zerstören.
  • ChangeMetadata: Mit dieser Funktion kann der Administrator die Metadaten des Vertrags aktualisieren.
    Jede dieser Funktionalitäten ist in separaten Klassen definiert.

Glückwunsch! Sie haben Ihren ersten fungiblen Token auf Tezos mit dem FA1.2-Standard erstellt!

In der nächsten Lektion lernen wir, wie wir mit dem soeben erstellten Token-Vertrag interagieren. Dazu gehören die Übertragung von Token, die Genehmigung von Token-Übertragungen sowie die Überprüfung des Token-Guthabens und des Gesamtvorrats. Bleiben Sie dran!

Haftungsausschluss
* Kryptoinvestitionen sind mit erheblichen Risiken verbunden. Bitte lassen Sie Vorsicht walten. Der Kurs ist nicht als Anlageberatung gedacht.
* Der Kurs wird von dem Autor erstellt, der Gate Learn beigetreten ist. Vom Autor geteilte Meinungen spiegeln nicht zwangsläufig die Meinung von Gate Learn wider.
Katalog
Lektion 2

Erstellen Sie Ihr erstes Token mit FA1.2 Standard

In dieser Lektion werden wir den Prozess der Erstellung eines fungiblen Tokens unter Verwendung des FA1.2-Standards auf Tezos durchlaufen. Wir werden die SmartPy-Online-IDE verwenden, um unseren Smart Contract zu schreiben und bereitzustellen. Beachten Sie, dass der FA1.2-Standard hauptsächlich für fungible Token verwendet wird, d. h. Token, die identische Eigenschaften haben und eins zu eins ausgetauscht werden können.

Schritt für Schritt Anleitung

  1. Zugriff auf die SmartPy-IDE

  2. Öffnen Sie zunächst die SmartPy-Online-IDE unter https://smartpy.io/ide/. Dies ist die Plattform, die wir zum Schreiben, Testen und Bereitstellen unserer Smart Contracts verwenden werden.

  3. Initiieren der FA1.2-Vorlage

  4. Klicken Sie in der linken Seitenleiste auf „Vorlagen nach Typ“ und wählen Sie „FA1.2“. Es öffnet sich ein neuer Tab mit der FA1.2-Vertragsvorlage. Hierbei handelt es sich um einen gebrauchsfertigen Vertrag, der dem FA1.2-Standard folgt.

  5. Die FA1.2-Vorlage verstehen

  6. Diese Vorlage verfügt über die Grundfunktionen für einen fungiblen Token, einschließlich der Übertragung von Token, der Genehmigung von Übertragungen, der Überprüfung von Guthaben und der Anzeige des gesamten Token-Vorrats. Der Vertrag umfasst außerdem zusätzliche Funktionen zum Prägen und Brennen von Token sowie zum Governance-Management.

  7. Studieren Sie diese Vorlage und stellen Sie sicher, dass Sie ihre Funktionen verstehen. Es ist in Ordnung, wenn Sie an dieser Stelle nicht alles verstehen, aber versuchen Sie, einen allgemeinen Eindruck von den Vorgängen zu bekommen, die dieser Vertrag ausführen kann.
    Sie können den Code beispielsweise aus der Vorlage in der SmartPy-IDE oder hier unten kopieren:

Python 
 # Fungible Assets – FA12 
 # Inspiriert von https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 Smartpy als SP importieren 


 # Die folgenden Metadaten sind nur ein Beispiel dient als Basis, 
 # der Inhalt wird zum Erstellen der Metadaten-JSON verwendet, die Benutzer 
 # kopieren und auf IPFS hochladen können.
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.module 
 def m(): 
 class AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.sender, sp.address) 
 """Nicht Standard, kann durch Vererbung neu definiert werden."""
            return True 

 class CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(selbst)
            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="read-only") 
 def is_paused_(self): 
 """Nicht Standard, kann durch Vererbung neu definiert werden."""
            return False 

 class Fa1_2(CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ Proposals/tzip-12/tzip-12.md#token-metadata
            Tokenspezifische Metadaten werden als Michelson-Wert vom Typ (Mapping-String-Bytes) gespeichert/dargestellt.
            Einige der Schlüssel sind reserviert und vordefiniert: 

 – „“: Sollte einem TZIP-016-URI entsprechen, der auf eine JSON-Darstellung der Token-Metadaten verweist.
            - „Name“: Sollte eine UTF-8-Zeichenfolge sein, die dem Token einen „Anzeigenamen“ gibt.
            - „Symbol“: Sollte eine UTF-8-Zeichenfolge für die Kurzkennung des Tokens sein (z. B XTZ, EUR, …). 
 – „Dezimalzahlen“: Sollte eine Ganzzahl sein (konvertiert in eine UTF-8-Zeichenfolge im Dezimalformat) 
 , die die Position des Dezimalpunkts in Token-Salden für Anzeigezwecke definiert.

            Contract_Metadata-Spezifikation: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 „““ 
 CommonInterface.__init__(selbst)
            self.data.metadata = Metadaten 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 für Besitzer in ledger.items():
                self.data.balances[owner.key] = Eigentümer.Wert 
 self.data.total_supply +=owner.value.balance 

 # TODO: Aktivieren, wenn diese Funktion implementiert ist.
            # self.init_metadata("metadata", Metadaten) 

 @sp.entrypoint 
 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_, default=sp.record(balance=0, Approvals={}) 
 ) 
 balance_to = self.data.balances.get(
                param.to_, default=sp.record(balance=0, genehmigt={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance – param.value, error="FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 wenn nicht self.is_administrator_(sp.sender):
                behaupten nicht self.is_paused_(), „FA1.2_Paused“
                wenn param.from_ != sp.sender: 
 balance_from.approvals[sp.sender] = sp.as_nat(
                        balance_from.approvals[sp.sender] – param.value,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_from 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def genehmigt(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, value=sp.nat).layout(
                    ("spender", "value") 
 ), 
 ) 
 activate not self.is_paused_(), „FA1.2_Paused“
            spender_balance = self.data.balances.get(
                sp.sender, default=sp.record(balance=0, Approvals={}) 
 ) 
 bereitsApproved = spender_balance.approvals.get(param.spender, Standard = 0) 
 Assert ( 
 bereits Approved == 0 oder param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            spender_balance.approvals[param.spender] = param.value 
 self.data.balances[sp.sender] = spender_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (address, callback) = param 
 result = self.data.balances.get(
                Adresse, default=sp.record(balance=0, Approvals={}) 
 ).balance 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, callback) = param 
 result = self.data.balances.get(
                args.owner, default=sp.record(balance=0, Approvals={}) 
 ).approvals.get(args.spender, default=0) 
 sp.transfer(result, sp.tez(0), callback) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 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): 
 """Gibt den Token-Metadaten-URI für das angegebene Token zurück. (token_id muss 0 sein)."""
            sp.cast(token_id, sp.nat) 
 return self.data.token_metadata[token_id]

    ########## 
 # Mixins # 
 ##########

    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

    ########## 
 # Tests # 
 ########## 

 Klasse Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(selbst, Administrator, Metadaten, Ledger, token_metadata): 
 ChangeMetadata.__init__(selbst)
            Brennen.__init__(selbst)
            Minze.__init__(selbst)
            Fa1_2.__init__(selbst, metadata, ledger, token_metadata) 
 Pause.__init__(selbst)
            Administrator.__init__(selbst, Administrator) 

 Klasse Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.option[sp.nat])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 

 class Viewer_address(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.option[sp.address])

        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 


 if „templates“ not in __name__: 

 @sp.add_test(name="FA12") 
 def test(): 
 sc = sp.test_scenario(m)
        sc.h1("FA1.2-Vorlage - Fungible Assets") 

 # sp.test_account generiert deterministisch ED25519-Schlüsselpaare: 
 admin = sp.test_account("Administrator")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Lassen Sie uns die Konten anzeigen: 
 sc.h1("Accounts") 
 sc.show([admin, alice, bob] 
 

 sc.h1("Contract") 
 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"
            ),
        }
        5tract_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("Offchain-Ansicht - token_metadata") 
 sc.verify_equal( 
 sp.View(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("Versuch, Metadaten zu aktualisieren") 
 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("Entrypoints") 
 sc.h2("Admin prägt ein paar Münzen") 
 c1.mint(address=alice.address, value=12).run (Absender=Administrator)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        sc.h2("Alice wechselt zu Bob") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob versucht, von Alice zu übertragen, aber er hat nicht ihre Zustimmung") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice genehmigt Bob- und Bob-Transfers") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob)
        sc.h2("Bob versucht, von Alice zu viel zu übertragen") 
 c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Admin verbrennt Bob-Token") 
 c1.burn(address=bob.address, value=1).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice versucht, Bob-Token zu verbrennen") 
 c1.burn(address=bob.address, value=1).run(sender=alice, valid=False) 
 sc.h2("Admin pausiert den Vertrag und Alice kann nicht mehr übertragen") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.address, to_=bob.address, value=4).run(
            sender=alice, valid=False 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Admin transfers while on pause") 
 c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=admin)
        sc.h2("Der Administrator hebt die Pause des Vertrags auf und Übertragungen sind zulässig") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.address, value=1).run(sender=alice)

        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("Views") 
 sc.h2("Balance") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "target").open_some() 
 c1.getBalance((alice.address, Ziel)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrator") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAddress, view_administrator.address, "target" 
 ).open_some() 
 c1.getAdministrator((sp.unit, target)) 
 sc.verify_equal(view_administrator.data.last, sp.some(admin.address))

        sc.h2("Total Supply") 
 view_totalSupply = m.Viewer_nat() 
 sc += view_totalSupply 
 target = sp.contract(sp.TNat, view_totalSupply.address, "target").open_some() 
 c1.getTotalSupply((sp.unit, target)) 
 sc.verify_equal(view_totalSupply.data.last, sp.some(17)) 

 sc.h2("Allowance") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "target").open_some() 
 c1.getAllowance((sp.record(owner=alice.address, spender=bob.address), target)) 
 sc.verify_equal(view_allowance.data.last, sp.some(1))
  1. Führen Sie den Vertrag aus. Sie werden so etwas sehen

Der ursprüngliche FA1.2-Vertrag verfügt über grundlegende Funktionen wie die Übertragung von Token, die Genehmigung von Übertragungen, die Überprüfung von Guthaben und die Anzeige des Gesamtbestands an Token. Jetzt werden wir diese Funktionalität erweitern.

  • Admin: Wir werden einen Vertrag einführen, der es einem Administrator ermöglicht, bestimmte Aktionen durchzuführen, wie z. B. das Pausieren des Vertrags, und andere Konten daran hindert, diese Funktionen zu nutzen.
  • Pause: Mit dieser Funktion können wir den Vertrag pausieren und wieder aufheben. Wenn der Vertrag pausiert ist, kann ihn niemand außer dem Administrator verwenden.
  • Mint: Mit dieser Vertragsfunktion kann der Administrator neue Token erstellen.
  • Brennen: Diese Vertragsfunktion ermöglicht es dem Administrator, Token zu zerstören.
  • ChangeMetadata: Mit dieser Funktion kann der Administrator die Metadaten des Vertrags aktualisieren.
    Jede dieser Funktionalitäten ist in separaten Klassen definiert.

Glückwunsch! Sie haben Ihren ersten fungiblen Token auf Tezos mit dem FA1.2-Standard erstellt!

In der nächsten Lektion lernen wir, wie wir mit dem soeben erstellten Token-Vertrag interagieren. Dazu gehören die Übertragung von Token, die Genehmigung von Token-Übertragungen sowie die Überprüfung des Token-Guthabens und des Gesamtvorrats. Bleiben Sie dran!

Haftungsausschluss
* Kryptoinvestitionen sind mit erheblichen Risiken verbunden. Bitte lassen Sie Vorsicht walten. Der Kurs ist nicht als Anlageberatung gedacht.
* Der Kurs wird von dem Autor erstellt, der Gate Learn beigetreten ist. Vom Autor geteilte Meinungen spiegeln nicht zwangsläufig die Meinung von Gate Learn wider.