Lição 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!

Isenção de responsabilidade
* O investimento em criptomoedas envolve grandes riscos. Prossiga com cautela. O curso não se destina a servir de orientação para investimentos.
* O curso foi criado pelo autor que entrou para o Gate Learn. As opiniões compartilhadas pelo autor não representam o Gate Learn.
Catálogo
Lição 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!

Isenção de responsabilidade
* O investimento em criptomoedas envolve grandes riscos. Prossiga com cautela. O curso não se destina a servir de orientação para investimentos.
* O curso foi criado pelo autor que entrou para o Gate Learn. As opiniões compartilhadas pelo autor não representam o Gate Learn.
It seems that you are attempting to access our services from a Restricted Location where Gate.io is unable to provide services. We apologize for any inconvenience this may cause. Currently, the Restricted Locations include but not limited to: the United States of America, Canada, Cambodia, Thailand, Cuba, Iran, North Korea and so on. For more information regarding the Restricted Locations, please refer to the User Agreement. Should you have any other questions, please contact our Customer Support Team.