第2课

Creando su primer token con el estándar FA1.2

En esta lección, analizaremos el proceso de creación de un token fungible utilizando el estándar FA1.2 en Tezos. Usaremos el IDE en línea SmartPy para escribir e implementar nuestro contrato inteligente. Tenga en cuenta que el estándar FA1.2 se utiliza principalmente para tokens fungibles, lo que significa tokens que tienen propiedades idénticas y se pueden intercambiar uno por uno.

Guía paso por paso

  1. Accediendo al IDE de SmartPy

  2. Primero, abra el IDE en línea de SmartPy en https://smartpy.io/ide/. Esta es la plataforma que usaremos para escribir, probar e implementar nuestros contratos inteligentes.

  3. Iniciando la plantilla FA1.2

  4. Haga clic en "Plantillas por tipo" en la barra lateral izquierda y seleccione "FA1.2". Se abrirá una nueva pestaña con la plantilla de contrato FA1.2. Este es un contrato listo para usar que sigue el estándar FA1.2.

  5. Comprender la plantilla FA1.2

  6. Esta plantilla tiene la funcionalidad básica de un token fungible, que incluye transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. El contrato también incluye funciones adicionales para acuñar y quemar tokens, así como para la gestión de la gobernanza.

  7. Estudie esta plantilla y asegúrese de comprender sus funcionalidades. Está bien si no comprende todo en este momento, pero intente tener una idea general de las operaciones que este contrato puede realizar.
    Por ejemplo, puede copiar el código de la plantilla en el IDE de SmartPy o aquí a continuación:

Python 
 # Activos fungibles - FA12 
 # Inspirado en https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 importar smartpy como sp 


 # Los metadatos a continuación son solo un ejemplo, sirve como base, 
 # el contenido se utiliza para construir los metadatos JSON que los usuarios 
 # pueden copiar y cargar en 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(): 
 clase AdminInterface(sp.Contract): 
 @sp.private(with_storage="solo lectura") 
 def is_administrator_(self, remitente): 
 sp .cast(sp.remitente, sp.address) 
 """No es estándar, puede redefinirse mediante herencia."""
            devuelve Verdadero 

 clase CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(yo)
            auto.datos.equilibrios = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.dirección, 
 sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadatos = 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.metadatos = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.cadena, sp.bytes], 
 ) 
 self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.dirección, 
 sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadatos = 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.metadatos = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.cadena, sp.bytes], 
 ) 

 @sp.private(with_storage="read-only") 
 def is_paused_(self): 
 """No estándar, se puede redefinir mediante herencia."""
            return False 

 clase Fa1_2(CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propuestas/tzip-12/tzip-12.md#token-metadatos
            Los metadatos específicos del token se almacenan/presentan como un valor de tipo Michelson (bytes de cadena de mapa).
            Algunas de las claves están reservadas y predefinidas: 

 - "": debe corresponder a un URI TZIP-016 que apunta a una representación JSON de los metadatos del token.
            - "nombre": debe ser una cadena UTF-8 que proporcione un "nombre para mostrar" al token.
            - "símbolo": debe ser una cadena UTF-8 para el identificador corto del token (p. ej. XTZ, EUR, …). 
 - "decimales": debe ser un número entero (convertido a una cadena UTF-8 en decimal) 
 que define la posición del punto decimal en los saldos de tokens para fines de visualización.

            especificación de metadatos de contrato: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(self)
            self.data.metadata = metadatos 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 para el propietario en ledger.items():
                auto.datos.saldos[propietario.clave] = propietario.valor 
 self.data.total_supply += propietario.valor.equilibrio 

 # TODO: Activar cuando se implemente esta función.
            # self.init_metadata("metadatos", metadatos) 

 @sp.entrypoint 
 def transfer(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, to_=sp.dirección, valor=sp.nat).diseño(
                    ("desde_ desde", ("hasta_ desde", "valor")) 
 ), 
 ) 
 balance_from = self.data.balances.get(
                parámetro.de_, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 balance_to = self.data.balances.get(
                parámetro.to_, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance - valor.param, error="FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 si no es self.is_administrator_(sp.sender):
                afirmar no self.is_paused_(), "FA1.2_Pausado"
                si param.from_ != sp.remitente: 
 balance_from.approvals[sp.remitente] = sp.as_nat(
                        balance_from.approvals[sp.sender] - valor.param,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_de 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def aprobar(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, valor=sp.nat).diseño(
                    ("gastador", "valor") 
 ), 
 ) 
 afirmar no self.is_paused_(), "FA1.2_Pausado"
            balance_gastador = self.data.balances.get(
                sp.remitente, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 yaAprobadas = gastador_equilibrio.aprobaciones.get(param.spender, default=0) 
 afirmar ( 
 yaAprobado == 0 o param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            gastador_equilibrio.aprobaciones[param.spender] = 
 auto.datos.saldos[sp.remitente] = gastador_equilibrio 

 @sp.entrypoint 
 def getBalance(self, parámetro): 
 (dirección, devolución de llamada) = parámetro 
 resultado = self.data.balances.get(
                dirección, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ).balance 
 sp.transfer(resultado, sp.tez(0), devolución de llamada) 

 @sp.entrypoint 
 def getAllowance(self, parámetro): 
 (args, devolución de llamada) = parámetro 
 resultado = self.data.balances.get(
                args.propietario, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ).aprobaciones.get(args.spender, predeterminado=0) 
 sp.transfer(resultado, sp.tez(0), devolución de llamada) 

 @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): 
 """Devuelve el URI de metadatos del token para el token dado. (token_id debe ser 0)."""
            sp.cast(token_id, sp.nat) 
 devolver 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

    ########## 
 # Pruebas # 
 ########## 

 clase Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(auto, administrador, metadatos, libro mayor, token_metadata): 
 ChangeMetadata.__init__(yo)
            Quemar.__init__(yo)
            Menta.__init__(yo)
            Fa1_2.__init__(yo, metadatos, libro mayor, token_metadata) 
 Pausa.__init__(yo)
            Administración.__init__(yo, administrador) 

 clase Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Ninguno, sp.opción[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(Ninguno, opción.sp[dirección.sp])

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


 si las "plantillas" no están en __name__: 

 @sp.add_test(name="FA12") 
 def prueba(): 
 sc = sp.test_scenario(m)
        sc.h1("Plantilla FA1.2 - Activos fungibles") 

 # sp.test_account genera pares de claves ED25519 de forma determinista: 
 admin = sp.test_account("Administrador")
        Alicia = sp.test_account ("Alicia")
        bob = sp.test_account("Roberto")

        # Mostremos las cuentas: 
 sc.h1("Cuentas") 
 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. metadatos_de_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 administrador=admin.address, 
 metadata=metadata_contrato, 
 token_metadata=token_metadata, 
 libro mayor={}, 
 ) 
 sc += c1 

 sc .h1("Vista fuera de cadena - 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("Intento de actualizar metadatos") 
 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("Entrypoints") 
 sc.h2("El administrador acuña algunas monedas") 
 c1.mint(address=alice.address, value=12).run (remitente=administrador)
        c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
        c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
        sc.h2("Alice se transfiere a Bob") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).run(remitente=alicia)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 14) 
 sc.h2("Bob intenta transferirse de Alice pero no tiene su aprobación") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
            remitente=bob, válido=Falso 
 ) 
 sc.h2("Alice aprueba las transferencias de Bob y Bob") 
 c1.approve(spender=bob.address, value=5).run(remitente=alice)
        c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).run(remitente=bob)
        sc.h2("Bob intenta realizar una transferencia excesiva desde Alice") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
            remitente=bob, válido=Falso 
 ) 
 sc.h2("El administrador quema el token de Bob") 
 c1.burn(dirección=bob.address, valor=1).ejecutar(remitente=admin)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 10) 
 sc.h2("Alice intenta quemar la ficha de Bob") 
 c1.burn(dirección=bob.dirección, valor=1).run(remitente=alicia, valid=False) 
 sc.h2("El administrador pausa el contrato y Alice ya no puede transferir") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).ejecutar(
            remitente=alicia, válido=Falso 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("El administrador transfiere mientras está en pausa") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=1).ejecutar(remitente=admin)
        sc.h2("El administrador reanuda el contrato y se permiten transferencias") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 9) 
 c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=1).run(remitente=alicia)

        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("Vistas") 
 sc.h2("Saldo") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "destino").open_some() 
 c1.getBalance((alicia.dirección, objetivo)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrador") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 destino = sp.contract(
            sp.TAddress, view_administrator.address, "destino" 
 ).open_some() 
 c1.getAdministrator((sp.unidad, objetivo)) 
 sc.verify_equal(view_administrator.data.last, sp.some(dirección.admin))

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

 sc.h2("Permiso") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "destino").open_some() 
 c1.getAllowance((sp.record(propietario=alice.dirección, gastador=bob.dirección), objetivo)) 
 sc.verify_equal(view_allowance.data.last, sp.algunos(1))
  1. Ejecute el contrato. Verás algo como esto

El contrato FA1.2 original tiene funciones básicas como transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. Ahora mejoraremos esta funcionalidad.

  • Administrador: introduciremos un contrato que permitirá a un administrador realizar acciones específicas, como pausar el contrato, y evitará que otras cuentas utilicen estas funciones.
  • Pausa: Esta característica nos permite pausar y reanudar el contrato. Cuando el contrato está en pausa, nadie puede utilizarlo excepto el administrador.
  • Mint: esta función de contrato permite al administrador crear nuevos tokens.
  • Grabar: esta función del contrato permite al administrador destruir tokens.
  • ChangeMetadata: esta función permite al administrador actualizar los metadatos del contrato.
    Cada una de estas funcionalidades se define en clases separadas.

¡Felicidades! ¡Has creado tu primer token fungible en Tezos utilizando el estándar FA1.2!

En la próxima lección, aprenderemos cómo interactuar con el contrato de token que acabamos de crear. Esto incluirá la transferencia de tokens, la aprobación de transferencias de tokens y la verificación del saldo de tokens y el suministro total. ¡Manténganse al tanto!

免责声明
* 投资有风险,入市须谨慎。本课程不作为投资理财建议。
* 本课程由入驻Gate Learn的作者创作,观点仅代表作者本人,绝不代表Gate Learn赞同其观点或证实其描述。
目录
第2课

Creando su primer token con el estándar FA1.2

En esta lección, analizaremos el proceso de creación de un token fungible utilizando el estándar FA1.2 en Tezos. Usaremos el IDE en línea SmartPy para escribir e implementar nuestro contrato inteligente. Tenga en cuenta que el estándar FA1.2 se utiliza principalmente para tokens fungibles, lo que significa tokens que tienen propiedades idénticas y se pueden intercambiar uno por uno.

Guía paso por paso

  1. Accediendo al IDE de SmartPy

  2. Primero, abra el IDE en línea de SmartPy en https://smartpy.io/ide/. Esta es la plataforma que usaremos para escribir, probar e implementar nuestros contratos inteligentes.

  3. Iniciando la plantilla FA1.2

  4. Haga clic en "Plantillas por tipo" en la barra lateral izquierda y seleccione "FA1.2". Se abrirá una nueva pestaña con la plantilla de contrato FA1.2. Este es un contrato listo para usar que sigue el estándar FA1.2.

  5. Comprender la plantilla FA1.2

  6. Esta plantilla tiene la funcionalidad básica de un token fungible, que incluye transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. El contrato también incluye funciones adicionales para acuñar y quemar tokens, así como para la gestión de la gobernanza.

  7. Estudie esta plantilla y asegúrese de comprender sus funcionalidades. Está bien si no comprende todo en este momento, pero intente tener una idea general de las operaciones que este contrato puede realizar.
    Por ejemplo, puede copiar el código de la plantilla en el IDE de SmartPy o aquí a continuación:

Python 
 # Activos fungibles - FA12 
 # Inspirado en https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 

 importar smartpy como sp 


 # Los metadatos a continuación son solo un ejemplo, sirve como base, 
 # el contenido se utiliza para construir los metadatos JSON que los usuarios 
 # pueden copiar y cargar en 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(): 
 clase AdminInterface(sp.Contract): 
 @sp.private(with_storage="solo lectura") 
 def is_administrator_(self, remitente): 
 sp .cast(sp.remitente, sp.address) 
 """No es estándar, puede redefinirse mediante herencia."""
            devuelve Verdadero 

 clase CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(yo)
            auto.datos.equilibrios = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.dirección, 
 sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadatos = 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.metadatos = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.cadena, sp.bytes], 
 ) 
 self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.dirección, 
 sp.record(aprobaciones=sp.map[sp.dirección, sp.nat], equilibrio=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadatos = 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.metadatos = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.cadena, sp.bytes], 
 ) 

 @sp.private(with_storage="read-only") 
 def is_paused_(self): 
 """No estándar, se puede redefinir mediante herencia."""
            return False 

 clase Fa1_2(CommonInterface): 
 def __init__(self, metadata, ledger, token_metadata): 
 """ 
 token_metadata spec: https://gitlab.com/tzip/tzip/-/blob/master/ propuestas/tzip-12/tzip-12.md#token-metadatos
            Los metadatos específicos del token se almacenan/presentan como un valor de tipo Michelson (bytes de cadena de mapa).
            Algunas de las claves están reservadas y predefinidas: 

 - "": debe corresponder a un URI TZIP-016 que apunta a una representación JSON de los metadatos del token.
            - "nombre": debe ser una cadena UTF-8 que proporcione un "nombre para mostrar" al token.
            - "símbolo": debe ser una cadena UTF-8 para el identificador corto del token (p. ej. XTZ, EUR, …). 
 - "decimales": debe ser un número entero (convertido a una cadena UTF-8 en decimal) 
 que define la posición del punto decimal en los saldos de tokens para fines de visualización.

            especificación de metadatos de contrato: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(self)
            self.data.metadata = metadatos 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 

 para el propietario en ledger.items():
                auto.datos.saldos[propietario.clave] = propietario.valor 
 self.data.total_supply += propietario.valor.equilibrio 

 # TODO: Activar cuando se implemente esta función.
            # self.init_metadata("metadatos", metadatos) 

 @sp.entrypoint 
 def transfer(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, to_=sp.dirección, valor=sp.nat).diseño(
                    ("desde_ desde", ("hasta_ desde", "valor")) 
 ), 
 ) 
 balance_from = self.data.balances.get(
                parámetro.de_, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 balance_to = self.data.balances.get(
                parámetro.to_, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 balance_from.balance = sp.as_nat(
                balance_from.balance - valor.param, error="FA1.2_InsufficientBalance" 
 ) 
 balance_to.balance += param.value 
 si no es self.is_administrator_(sp.sender):
                afirmar no self.is_paused_(), "FA1.2_Pausado"
                si param.from_ != sp.remitente: 
 balance_from.approvals[sp.remitente] = sp.as_nat(
                        balance_from.approvals[sp.sender] - valor.param,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = balance_de 
 self.data.balances[param.to_] = balance_to 

 @sp.entrypoint 
 def aprobar(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, valor=sp.nat).diseño(
                    ("gastador", "valor") 
 ), 
 ) 
 afirmar no self.is_paused_(), "FA1.2_Pausado"
            balance_gastador = self.data.balances.get(
                sp.remitente, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ) 
 yaAprobadas = gastador_equilibrio.aprobaciones.get(param.spender, default=0) 
 afirmar ( 
 yaAprobado == 0 o param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            gastador_equilibrio.aprobaciones[param.spender] = 
 auto.datos.saldos[sp.remitente] = gastador_equilibrio 

 @sp.entrypoint 
 def getBalance(self, parámetro): 
 (dirección, devolución de llamada) = parámetro 
 resultado = self.data.balances.get(
                dirección, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ).balance 
 sp.transfer(resultado, sp.tez(0), devolución de llamada) 

 @sp.entrypoint 
 def getAllowance(self, parámetro): 
 (args, devolución de llamada) = parámetro 
 resultado = self.data.balances.get(
                args.propietario, predeterminado=sp.record(saldo=0, aprobaciones={}) 
 ).aprobaciones.get(args.spender, predeterminado=0) 
 sp.transfer(resultado, sp.tez(0), devolución de llamada) 

 @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): 
 """Devuelve el URI de metadatos del token para el token dado. (token_id debe ser 0)."""
            sp.cast(token_id, sp.nat) 
 devolver 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

    ########## 
 # Pruebas # 
 ########## 

 clase Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata): 
 def __init__(auto, administrador, metadatos, libro mayor, token_metadata): 
 ChangeMetadata.__init__(yo)
            Quemar.__init__(yo)
            Menta.__init__(yo)
            Fa1_2.__init__(yo, metadatos, libro mayor, token_metadata) 
 Pausa.__init__(yo)
            Administración.__init__(yo, administrador) 

 clase Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Ninguno, sp.opción[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(Ninguno, opción.sp[dirección.sp])

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


 si las "plantillas" no están en __name__: 

 @sp.add_test(name="FA12") 
 def prueba(): 
 sc = sp.test_scenario(m)
        sc.h1("Plantilla FA1.2 - Activos fungibles") 

 # sp.test_account genera pares de claves ED25519 de forma determinista: 
 admin = sp.test_account("Administrador")
        Alicia = sp.test_account ("Alicia")
        bob = sp.test_account("Roberto")

        # Mostremos las cuentas: 
 sc.h1("Cuentas") 
 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. metadatos_de_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 

 c1 = m.Fa1_2TestFull( 
 administrador=admin.address, 
 metadata=metadata_contrato, 
 token_metadata=token_metadata, 
 libro mayor={}, 
 ) 
 sc += c1 

 sc .h1("Vista fuera de cadena - 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("Intento de actualizar metadatos") 
 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("Entrypoints") 
 sc.h2("El administrador acuña algunas monedas") 
 c1.mint(address=alice.address, value=12).run (remitente=administrador)
        c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
        c1.mint(dirección=alice.dirección, valor=3).run(remitente=admin)
        sc.h2("Alice se transfiere a Bob") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).run(remitente=alicia)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 14) 
 sc.h2("Bob intenta transferirse de Alice pero no tiene su aprobación") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
            remitente=bob, válido=Falso 
 ) 
 sc.h2("Alice aprueba las transferencias de Bob y Bob") 
 c1.approve(spender=bob.address, value=5).run(remitente=alice)
        c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).run(remitente=bob)
        sc.h2("Bob intenta realizar una transferencia excesiva desde Alice") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=4).ejecutar(
            remitente=bob, válido=Falso 
 ) 
 sc.h2("El administrador quema el token de Bob") 
 c1.burn(dirección=bob.address, valor=1).ejecutar(remitente=admin)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 10) 
 sc.h2("Alice intenta quemar la ficha de Bob") 
 c1.burn(dirección=bob.dirección, valor=1).run(remitente=alicia, valid=False) 
 sc.h2("El administrador pausa el contrato y Alice ya no puede transferir") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=4).ejecutar(
            remitente=alicia, válido=Falso 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("El administrador transfiere mientras está en pausa") 
 c1.transfer(from_=alice.address, to_=bob.dirección, valor=1).ejecutar(remitente=admin)
        sc.h2("El administrador reanuda el contrato y se permiten transferencias") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.datos.saldos[alice.dirección].saldo == 9) 
 c1.transfer(desde_=alicia.dirección, to_=bob.dirección, valor=1).run(remitente=alicia)

        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("Vistas") 
 sc.h2("Saldo") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "destino").open_some() 
 c1.getBalance((alicia.dirección, objetivo)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 

 sc.h2("Administrador") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 destino = sp.contract(
            sp.TAddress, view_administrator.address, "destino" 
 ).open_some() 
 c1.getAdministrator((sp.unidad, objetivo)) 
 sc.verify_equal(view_administrator.data.last, sp.some(dirección.admin))

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

 sc.h2("Permiso") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "destino").open_some() 
 c1.getAllowance((sp.record(propietario=alice.dirección, gastador=bob.dirección), objetivo)) 
 sc.verify_equal(view_allowance.data.last, sp.algunos(1))
  1. Ejecute el contrato. Verás algo como esto

El contrato FA1.2 original tiene funciones básicas como transferir tokens, aprobar transferencias, verificar saldos y ver el suministro total de tokens. Ahora mejoraremos esta funcionalidad.

  • Administrador: introduciremos un contrato que permitirá a un administrador realizar acciones específicas, como pausar el contrato, y evitará que otras cuentas utilicen estas funciones.
  • Pausa: Esta característica nos permite pausar y reanudar el contrato. Cuando el contrato está en pausa, nadie puede utilizarlo excepto el administrador.
  • Mint: esta función de contrato permite al administrador crear nuevos tokens.
  • Grabar: esta función del contrato permite al administrador destruir tokens.
  • ChangeMetadata: esta función permite al administrador actualizar los metadatos del contrato.
    Cada una de estas funcionalidades se define en clases separadas.

¡Felicidades! ¡Has creado tu primer token fungible en Tezos utilizando el estándar FA1.2!

En la próxima lección, aprenderemos cómo interactuar con el contrato de token que acabamos de crear. Esto incluirá la transferencia de tokens, la aprobación de transferencias de tokens y la verificación del saldo de tokens y el suministro total. ¡Manténganse al tanto!

免责声明
* 投资有风险,入市须谨慎。本课程不作为投资理财建议。
* 本课程由入驻Gate Learn的作者创作,观点仅代表作者本人,绝不代表Gate Learn赞同其观点或证实其描述。