Lesson 2

Criando seu primeiro token com padrão FA1.2

Nesta lição, passaremos pelo processo de criação de um token fungível usando o padrão FA1.2 no Tezos. Usaremos o IDE online SmartPy para escrever e implantar nosso contrato inteligente. Tenha em mente que o padrão FA1.2 é usado principalmente para tokens fungíveis, o que significa tokens que possuem propriedades idênticas e podem ser trocados um por um.

Guia passo a passo

  1. Acessando o IDE SmartPy

  2. Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/. Esta é a plataforma que usaremos para escrever, testar e implantar nossos contratos inteligentes.

  3. Iniciando o modelo FA1.2

  4. Clique em “Modelos por tipo” na barra lateral esquerda e selecione “FA1.2”. Uma nova aba será aberta com o modelo de contrato FA1.2. Este é um contrato pronto para uso que segue o padrão FA1.2.

  5. Compreendendo o modelo FA1.2

  6. Este modelo possui a funcionalidade básica para um token fungível, que inclui transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. O contrato também inclui funcionalidades adicionais para cunhagem e queima de tokens, bem como para gestão de governança.

  7. Estude este modelo e certifique-se de compreender suas funcionalidades. Tudo bem se você não entendeu tudo neste momento, mas tente ter uma noção geral das operações que esse contrato pode realizar.
    Por exemplo, você pode copiar o código do modelo no IDE SmartPy ou aqui abaixo:

Python 
 # Ativos Fungíveis - FA12 
 # Inspirado em https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 importar smartpy como sp 


 # Os metadados abaixo são apenas um exemplo, são serve como base, 
 # o conteúdo é usado para construir o JSON de metadados que os usuários 
 # podem copiar e enviar para 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.module 
 def m(): 
 class AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.remetente, sp.address) 
 """Não é padrão, pode ser redefinido por herança."""
            retornar Verdadeiro 

 classe CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(próprio)
            self.data.balances=sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(aprovações=sp.map[sp.address, sp.nat], saldo=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(aprovações=sp.map[sp.address, sp.nat], saldo=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): 
 """Não é padrão, pode ser redefinido por meio de herança."""
            retornar Falso 

 classe Fa1_2 (CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propostas/tzip-12/tzip-12.md#token-metadata
            Os metadados específicos do token são armazenados/apresentados como um valor Michelson do tipo (map string bytes).
            Algumas das chaves são reservadas e predefinidas: 

 - "" : Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
            - "name": deve ser uma string UTF-8 dando um "nome de exibição" ao token.
            - "symbol" : Deve ser uma string UTF-8 para o identificador curto do token (por exemplo XTZ, EUR, …). 
 - "decimais": Deve ser um número inteiro (convertido para uma string UTF-8 em decimal) 
 que define a posição da vírgula decimal nos saldos de tokens para fins de exibição.

            Especificação de contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(auto)
            self.data.metadata = metadados 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 para proprietário em ledger.items():
                self.data.balances[proprietário.chave] = proprietário.valor 
 self.data.total_supply += proprietário.value.balance 

 # TODO: Ativar quando este recurso for implementado.
            # self.init_metadata("metadados", metadados) 

 @sp.entrypoint 
 def transfer(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, para_=sp.endereço, valor=sp.nat).layout(
                    ("from_ as from", ("to_ as to", "value")) 
 ), 
 ) 
 balance_from = self.data.balances.get(
                parâmetro.de_, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 balance_to = self.data.balances.get(
                param.to_, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance - param.value, erro = "FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 se não for self.is_administrator_(sp.sender):
                afirmar não self.is_paused_(), "FA1.2_Pausado"
                se parâmetro.from_ != sp.sender: 
 saldo_from.approvals[sp.sender] = sp.as_nat(
                        balance_from.approvals[sp.sender] - param.value,
                        erro = "FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_from 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def aprovar(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, valor=sp.nat).layout(
                    ("gastador", "valor") 
 ), 
 ) 
 afirma não self.is_paused_(), "FA1.2_Pausado"
            gastador_balance=self.data.balances.get(
                sp.sender, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 já aprovado = gastador_balance.approvals.get(param.spender, padrão = 0) 
 afirmação ( 
 já aprovado == 0 ou param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            gastador_balance.approvals[param.spender] = param.valor 
 self.data.balances[sp.sender] = gastador_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (endereço, retorno de chamada) = param 
 resultado = self.data.balances.get(
                endereço, padrão = sp.record (saldo = 0, aprovações={}) 
 ).balance 
 sp.transfer(resultado, sp.tez(0), retorno de chamada) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, retorno de chamada) = parâmetro 
 resultado = self.data.balances.get(
                args.proprietário, padrão = sp.record (saldo = 0, aprovações={}) 
 ).approvals.get(args.spender, padrão=0) 
 sp.transfer(resultado, sp.tez(0), retorno de chamada) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 sp.cast(param, sp.pair[sp.unit, sp.contrato[sp.nat]])
            sp.transfer(self.data.total_supply, sp.tez(0), sp.snd(param)) 

 @sp.offchain_view() 
 def token_metadata(self, token_id): 
 """Retorna o URI de metadados do token para o token fornecido. (token_id deve ser 0)."""
            sp.cast(token_id, sp.nat) 
 retorna 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

    ########## 
 # Testes # 
 ########### 

 classe Fa1_2TestFull (Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(self, administrador, metadados, razão, token_metadata): 
 ChangeMetadata.__init__(próprio)
            Queimar.__init__(próprio)
            Hortelã.__init__(próprio)
            Fa1_2.__init__(próprio, metadados, razão, token_metadata) 
 Pausa.__init__(próprio)
            Administrador.__init__(próprio, administrador) 

 classe Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.opção[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(Nenhum, sp.opção[sp.endereço])

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


 se "modelos" não estiverem em __name__: 

 @sp.add_test(name="FA12") 
 def teste(): 
 sc = sp.test_scenario(m)
        sc.h1("Modelo FA1.2 - Ativos fungíveis") 

 # sp.test_account gera pares de chaves ED25519 deterministicamente: 
 admin = sp.test_account("Administrator")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Vamos exibir as contas: 
 sc.h1("Contas") 
 sc.show([admin, alice, bob]) 

 sc.h1("Contrato") 
 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. metadados_de_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 administrador=admin.address, 
 metadata=contract_metadata, 
 token_metadata=token_metadata, 
 ledger={}, 
 ) 
 sc += c1 

 sc .h1("Visualização offchain - 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("Tentativa de atualizar metadados") 
 sc.verify( 
 c1.data.metadata[""]
            == sp.utils.bytes_of_string(
                "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 
 ) 
 c1.update_metadata(key="", valor=sp.bytes("0x00")).run(sender=admin) 
 sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00")) 

 sc.h1("Pontos de entrada") 
 sc.h2("Admin cunha algumas moedas") 
 c1.mint(address=alice.address, value=12).run (remetente = administrador)
        c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
        c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
        sc.h2("Alice transfere para Bob") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(remetente=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob tenta transferir de Alice, mas não tem a aprovação dela") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice aprova transferências de Bob e Bob") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(remetente=bob)
        sc.h2("Bob tenta transferir demais de Alice") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Admin queima token de Bob") 
 c1.burn(address=bob.address, valor=1).run(remetente=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice tenta queimar token de Bob") 
 c1.burn(address=bob.address, valor=1).run(remetente=alice, valid=False) 
 sc.h2("Admin pausa o contrato e Alice não pode mais transferir") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(
            remetente=alice, válido=Falso 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Transferências de administrador durante a pausa") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=admin)
        sc.h2("Admin retoma o contrato e transferências são permitidas") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=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("Visualizações") 
 sc.h2("Balance") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "destino").open_some() 
 c1.getBalance((alice.address, alvo)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrador") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAddress, view_administrator.address, "destino" 
 ).open_some() 
 c1.getAdministrator((sp.unit, alvo)) 
 sc.verify_equal(view_administrator.data.last, sp.algum(endereço.admin))

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

 sc.h2("Permissão") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 alvo = sp.contract(sp.TNat, view_allowance.address, "alvo").open_some() 
 c1.getAllowance((sp.record(proprietário=alice.address, gastador=bob.address), alvo)) 
 sc.verify_equal(view_allowance.data.last, sp.algum(1))
  1. Execute o contrato. Você verá algo assim

O contrato FA1.2 original possui funcionalidades básicas, como transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. Agora vamos aprimorar essa funcionalidade.

  • Administrador: apresentaremos um contrato que permite que um administrador execute ações específicas, como pausar o contrato, e impede que outras contas usem essas funções.
  • Pausa: Este recurso nos permite pausar e retomar o contrato. Quando o contrato é pausado, ninguém pode utilizá-lo, exceto o administrador.
  • Mint: Este recurso de contrato permite ao administrador criar novos tokens.
  • Queimar: Este recurso de contrato permite ao administrador destruir tokens.
  • ChangeMetadata: Este recurso permite ao administrador atualizar os metadados do contrato.
    Cada uma dessas funcionalidades é definida em classes separadas.

Parabéns! Você criou seu primeiro token fungível no Tezos usando o padrão FA1.2!

Na próxima lição, aprenderemos como interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de tokens e a verificação do saldo de tokens e do fornecimento total. Fique atento!

Disclaimer
* Crypto investment involves significant risks. Please proceed with caution. The course is not intended as investment advice.
* The course is created by the author who has joined Gate Learn. Any opinion shared by the author does not represent Gate Learn.
Catalog
Lesson 2

Criando seu primeiro token com padrão FA1.2

Nesta lição, passaremos pelo processo de criação de um token fungível usando o padrão FA1.2 no Tezos. Usaremos o IDE online SmartPy para escrever e implantar nosso contrato inteligente. Tenha em mente que o padrão FA1.2 é usado principalmente para tokens fungíveis, o que significa tokens que possuem propriedades idênticas e podem ser trocados um por um.

Guia passo a passo

  1. Acessando o IDE SmartPy

  2. Primeiro, abra o IDE online SmartPy em https://smartpy.io/ide/. Esta é a plataforma que usaremos para escrever, testar e implantar nossos contratos inteligentes.

  3. Iniciando o modelo FA1.2

  4. Clique em “Modelos por tipo” na barra lateral esquerda e selecione “FA1.2”. Uma nova aba será aberta com o modelo de contrato FA1.2. Este é um contrato pronto para uso que segue o padrão FA1.2.

  5. Compreendendo o modelo FA1.2

  6. Este modelo possui a funcionalidade básica para um token fungível, que inclui transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. O contrato também inclui funcionalidades adicionais para cunhagem e queima de tokens, bem como para gestão de governança.

  7. Estude este modelo e certifique-se de compreender suas funcionalidades. Tudo bem se você não entendeu tudo neste momento, mas tente ter uma noção geral das operações que esse contrato pode realizar.
    Por exemplo, você pode copiar o código do modelo no IDE SmartPy ou aqui abaixo:

Python 
 # Ativos Fungíveis - FA12 
 # Inspirado em https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 importar smartpy como sp 


 # Os metadados abaixo são apenas um exemplo, são serve como base, 
 # o conteúdo é usado para construir o JSON de metadados que os usuários 
 # podem copiar e enviar para 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.module 
 def m(): 
 class AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.remetente, sp.address) 
 """Não é padrão, pode ser redefinido por herança."""
            retornar Verdadeiro 

 classe CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(próprio)
            self.data.balances=sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(aprovações=sp.map[sp.address, sp.nat], saldo=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(aprovações=sp.map[sp.address, sp.nat], saldo=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): 
 """Não é padrão, pode ser redefinido por meio de herança."""
            retornar Falso 

 classe Fa1_2 (CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propostas/tzip-12/tzip-12.md#token-metadata
            Os metadados específicos do token são armazenados/apresentados como um valor Michelson do tipo (map string bytes).
            Algumas das chaves são reservadas e predefinidas: 

 - "" : Deve corresponder a um URI TZIP-016 que aponta para uma representação JSON dos metadados do token.
            - "name": deve ser uma string UTF-8 dando um "nome de exibição" ao token.
            - "symbol" : Deve ser uma string UTF-8 para o identificador curto do token (por exemplo XTZ, EUR, …). 
 - "decimais": Deve ser um número inteiro (convertido para uma string UTF-8 em decimal) 
 que define a posição da vírgula decimal nos saldos de tokens para fins de exibição.

            Especificação de contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(auto)
            self.data.metadata = metadados 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 para proprietário em ledger.items():
                self.data.balances[proprietário.chave] = proprietário.valor 
 self.data.total_supply += proprietário.value.balance 

 # TODO: Ativar quando este recurso for implementado.
            # self.init_metadata("metadados", metadados) 

 @sp.entrypoint 
 def transfer(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, para_=sp.endereço, valor=sp.nat).layout(
                    ("from_ as from", ("to_ as to", "value")) 
 ), 
 ) 
 balance_from = self.data.balances.get(
                parâmetro.de_, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 balance_to = self.data.balances.get(
                param.to_, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance - param.value, erro = "FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 se não for self.is_administrator_(sp.sender):
                afirmar não self.is_paused_(), "FA1.2_Pausado"
                se parâmetro.from_ != sp.sender: 
 saldo_from.approvals[sp.sender] = sp.as_nat(
                        balance_from.approvals[sp.sender] - param.value,
                        erro = "FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_from 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def aprovar(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, valor=sp.nat).layout(
                    ("gastador", "valor") 
 ), 
 ) 
 afirma não self.is_paused_(), "FA1.2_Pausado"
            gastador_balance=self.data.balances.get(
                sp.sender, padrão = sp.record (saldo = 0, aprovações={}) 
 ) 
 já aprovado = gastador_balance.approvals.get(param.spender, padrão = 0) 
 afirmação ( 
 já aprovado == 0 ou param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            gastador_balance.approvals[param.spender] = param.valor 
 self.data.balances[sp.sender] = gastador_balance 

 @sp.entrypoint 
 def getBalance(self, param): 
 (endereço, retorno de chamada) = param 
 resultado = self.data.balances.get(
                endereço, padrão = sp.record (saldo = 0, aprovações={}) 
 ).balance 
 sp.transfer(resultado, sp.tez(0), retorno de chamada) 

 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, retorno de chamada) = parâmetro 
 resultado = self.data.balances.get(
                args.proprietário, padrão = sp.record (saldo = 0, aprovações={}) 
 ).approvals.get(args.spender, padrão=0) 
 sp.transfer(resultado, sp.tez(0), retorno de chamada) 

 @sp.entrypoint 
 def getTotalSupply(self, param): 
 sp.cast(param, sp.pair[sp.unit, sp.contrato[sp.nat]])
            sp.transfer(self.data.total_supply, sp.tez(0), sp.snd(param)) 

 @sp.offchain_view() 
 def token_metadata(self, token_id): 
 """Retorna o URI de metadados do token para o token fornecido. (token_id deve ser 0)."""
            sp.cast(token_id, sp.nat) 
 retorna 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

    ########## 
 # Testes # 
 ########### 

 classe Fa1_2TestFull (Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(self, administrador, metadados, razão, token_metadata): 
 ChangeMetadata.__init__(próprio)
            Queimar.__init__(próprio)
            Hortelã.__init__(próprio)
            Fa1_2.__init__(próprio, metadados, razão, token_metadata) 
 Pausa.__init__(próprio)
            Administrador.__init__(próprio, administrador) 

 classe Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(None, sp.opção[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(Nenhum, sp.opção[sp.endereço])

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


 se "modelos" não estiverem em __name__: 

 @sp.add_test(name="FA12") 
 def teste(): 
 sc = sp.test_scenario(m)
        sc.h1("Modelo FA1.2 - Ativos fungíveis") 

 # sp.test_account gera pares de chaves ED25519 deterministicamente: 
 admin = sp.test_account("Administrator")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")

        # Vamos exibir as contas: 
 sc.h1("Contas") 
 sc.show([admin, alice, bob]) 

 sc.h1("Contrato") 
 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. metadados_de_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 administrador=admin.address, 
 metadata=contract_metadata, 
 token_metadata=token_metadata, 
 ledger={}, 
 ) 
 sc += c1 

 sc .h1("Visualização offchain - 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("Tentativa de atualizar metadados") 
 sc.verify( 
 c1.data.metadata[""]
            == sp.utils.bytes_of_string(
                "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 
 ) 
 c1.update_metadata(key="", valor=sp.bytes("0x00")).run(sender=admin) 
 sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00")) 

 sc.h1("Pontos de entrada") 
 sc.h2("Admin cunha algumas moedas") 
 c1.mint(address=alice.address, value=12).run (remetente = administrador)
        c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
        c1.mint(endereço=alice.endereço, valor=3).run(remetente=admin)
        sc.h2("Alice transfere para Bob") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(remetente=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob tenta transferir de Alice, mas não tem a aprovação dela") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice aprova transferências de Bob e Bob") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(remetente=bob)
        sc.h2("Bob tenta transferir demais de Alice") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Admin queima token de Bob") 
 c1.burn(address=bob.address, valor=1).run(remetente=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice tenta queimar token de Bob") 
 c1.burn(address=bob.address, valor=1).run(remetente=alice, valid=False) 
 sc.h2("Admin pausa o contrato e Alice não pode mais transferir") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.endereço, to_=bob.endereço, valor=4).run(
            remetente=alice, válido=Falso 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Transferências de administrador durante a pausa") 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=admin)
        sc.h2("Admin retoma o contrato e transferências são permitidas") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.endereço, valor=1).run(remetente=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("Visualizações") 
 sc.h2("Balance") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "destino").open_some() 
 c1.getBalance((alice.address, alvo)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrador") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAddress, view_administrator.address, "destino" 
 ).open_some() 
 c1.getAdministrator((sp.unit, alvo)) 
 sc.verify_equal(view_administrator.data.last, sp.algum(endereço.admin))

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

 sc.h2("Permissão") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 alvo = sp.contract(sp.TNat, view_allowance.address, "alvo").open_some() 
 c1.getAllowance((sp.record(proprietário=alice.address, gastador=bob.address), alvo)) 
 sc.verify_equal(view_allowance.data.last, sp.algum(1))
  1. Execute o contrato. Você verá algo assim

O contrato FA1.2 original possui funcionalidades básicas, como transferência de tokens, aprovação de transferências, verificação de saldos e visualização do fornecimento total de tokens. Agora vamos aprimorar essa funcionalidade.

  • Administrador: apresentaremos um contrato que permite que um administrador execute ações específicas, como pausar o contrato, e impede que outras contas usem essas funções.
  • Pausa: Este recurso nos permite pausar e retomar o contrato. Quando o contrato é pausado, ninguém pode utilizá-lo, exceto o administrador.
  • Mint: Este recurso de contrato permite ao administrador criar novos tokens.
  • Queimar: Este recurso de contrato permite ao administrador destruir tokens.
  • ChangeMetadata: Este recurso permite ao administrador atualizar os metadados do contrato.
    Cada uma dessas funcionalidades é definida em classes separadas.

Parabéns! Você criou seu primeiro token fungível no Tezos usando o padrão FA1.2!

Na próxima lição, aprenderemos como interagir com o contrato de token que acabamos de criar. Isso incluirá a transferência de tokens, a aprovação de transferências de tokens e a verificação do saldo de tokens e do fornecimento total. Fique atento!

Disclaimer
* Crypto investment involves significant risks. Please proceed with caution. The course is not intended as investment advice.
* The course is created by the author who has joined Gate Learn. Any opinion shared by the author does not represent Gate Learn.