AZTEC-SANDBOX

Aztec-Sandbox

Que es?

En el ámbito de la ciberseguridad, un "sandbox" es un entorno virtual seguro y aislado diseñado para ejecutar programas o procesos de manera controlada, permitiendo analizar su comportamiento sin riesgo de dañar el sistema principal. Su objetivo es simular un entorno real sin comprometer la integridad del sistema.

Aztec es una solución de capa 2 que aporta privacidad programable a la red Ethereum. Aztec Sandbox, por su parte, es una red de pruebas local creada para desarrolladores, enfocada en la privacidad de los contratos inteligentes.

El lanzamiento de Aztec Sandbox representa un hito importante en la historia de Aztec Labs, marcando el primer gran avance hacia la culminación de Aztec: una capa 2 totalmente descentralizada que garantiza la privacidad.

Aztec Sandbox ofrece una versión integral que proporciona a los desarrolladores un nodo Aztec local rápido y ligero, con funcionalidades similares a las ofrecidas por los paquetes de nodos locales Ganache o Anvil de Ethereum.

Para que sirve?

El Sandbox ayuda a los desarrolladores con visión de futuro a adquirir una ventaja inicial al interactuar con entornos blockchain nativos de ZK:

Gestión de estado privado Modelos de abstracción de cuentas Comprender patrones para mantener la información privada de forma segura Rutas de ejecución de funciones privadas y públicas Y los ejemplos de contratos inteligentes de Aztec Labs incluyen una amplia gama de casos de uso que estamos ansiosos por ver:

Contratos de tokens públicos y privados DeFi anónima Fuentes de precios de oráculos Distribuciones aéreas secretas Fideicomiso de dos partes Cuentas validadas de forma privada

Cual es su valor agregado?

Nuestra visión es crear un ecosistema financiero más justo y abierto, construido con la encriptación como su núcleo.

Creemos que la descentralización se basa en los derechos individuales; sin una encriptación ampliamente accesible, comprometemos nuestra capacidad para elegir cómo vivimos nuestras vidas y ganamos nuestro sustento.

Estamos construyendo la Red Aztec, un ZK-rollup completamente programable y privado en Ethereum para permitir a los desarrolladores crear aplicaciones descentralizadas con encriptación y escalabilidad.

Establece el entorno

Requisitos previos

  1. Tener instalado node >= 18

  2. Tener instalado docker

  3. Tener instalado yarn globalmente

** Nota: Ya que el lenguaje de programacion de Aztec es basado en Noir, te recomiendo que instales el plugin de Noir en tu Visual Studio Code. Ref

Establece tu entorno de desarrollo

  1. Verificamos que tenemos instalado node >= 18

    node -v
    
  2. Verificamos que tenemos instalado docker

    docker -v
    
  3. Creamos un nuevo proyecto a partir de la linea de comandos

    npm create aztec-app
    
  4. Selecionamos “just contract example” en caso que querramos crear solamente el contrato, o seleccionamos Boilerplate project with frontend en caso que necesitemos incluir el frontend.

*
*

5. Seleccionar el tipo de formato de el frontend que prefieras y escribe el nombre de tu proyecto.

*
*

6. Seguidamente si no tienes descargadas las imagenes de docker de aztec, te consultara si deseas descargarla, presiona y o enter.

** Puede tomar un par de minutos.

7. Una vez descagado las imagener de los contenedors, te preguntara si desea correr el servidar, seleccionaremos que si.

  1. ** El servidor se levantara en el puerto 8080, puedes cambiar el puerto modificando el archivo docker-compose.yml de la imagen ó puedes desocupar el puerto matando los procesos que estan en ese puerto.

    kill -9 $(lsof -ti:3000)
    

8. Instalamos dependencias y levantamos el servidor web.

8.1 Ya que la ultima version estable de yarm no es compatible con la estipulada en nuestro package.json, eliminaremos el ‘packageManager’ de nuestro archivo package json. Para que tome la version yarn global de su ordenador.

8.2 Instalamos dependencias

$ yarn install

8.3 Corremos el servidor web

$ yarn dev
*
*
$ aztec-sandbox

Ejercicio Practico: Contrato de tokens con Aztec.

Que es un contrato de token?

Un token es cualquier active digital, Ejemplos: criptomonedas, tokens de utilidad, tokens de seguridad o activos digitales fungibles y no fungibles.
Un token es cualquier active digital, Ejemplos: criptomonedas, tokens de utilidad, tokens de seguridad o activos digitales fungibles y no fungibles.
  • Modificaremos el contrato que viene por defecto en la ruta:

mod types;

// Minimal token implementation that supports `AuthWit` accounts.
// The auth message follows a similar pattern to the cross-chain message and includes a designated caller.
// The designated caller is ALWAYS used here, and not based on a flag as cross-chain.
// message hash = H([caller, contract, selector, ...args])
// To be read as `caller` calls function at `contract` defined by `selector` with `args`
// Including a nonce in the message hash ensures that the message can only be used once.

contract Token {
   
    use dep::compressed_string::FieldCompressedString;

    use dep::aztec::prelude::{
        NoteGetterOptions, NoteHeader, Map, PublicMutable, SharedImmutable, PrivateSet,
        FunctionSelector, AztecAddress
    };
    use dep::aztec::hash::compute_secret_hash;

    
    use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}};
    

    use crate::types::{transparent_note::TransparentNote, token_note::{TokenNote, TOKEN_NOTE_LEN}, balances_map::BalancesMap};

    #[aztec(storage)]
    struct Storage {
      
        admin: PublicMutable<AztecAddress>,
 
        minters: Map<AztecAddress, PublicMutable<bool>>,
      
        balances: BalancesMap<TokenNote>,
        
        total_supply: PublicMutable<U128>,
        
        pending_shields: PrivateSet<TransparentNote>,
       
        public_balances: Map<AztecAddress, PublicMutable<U128>>,
        symbol: SharedImmutable<FieldCompressedString>,
        name: SharedImmutable<FieldCompressedString>,
       
        decimals: SharedImmutable<u8>,
        
    }

    #[aztec(public)]
    #[aztec(initializer)]
    fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) {
        assert(!admin.is_zero(), "invalid admin");
        storage.admin.write(admin);
        storage.minters.at(admin).write(true);
        storage.name.initialize(FieldCompressedString::from_string(name));
        storage.symbol.initialize(FieldCompressedString::from_string(symbol));
        
        storage.decimals.initialize(decimals);
        
    }

    #[aztec(public)]
    fn set_admin(new_admin: AztecAddress) {
        assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin");
    
        storage.admin.write(new_admin);
        
    }

    #[aztec(public)]
    fn set_minter(minter: AztecAddress, approve: bool) {
        
        assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin");
       
        storage.minters.at(minter).write(approve);
        
    }
  
    #[aztec(public)]
    fn mint_public(to: AztecAddress, amount: Field) {
       
        assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter");
      
        let amount = U128::from_integer(amount);
        let new_balance = storage.public_balances.at(to).read().add(amount);
        let supply = storage.total_supply.read().add(amount);

        storage.public_balances.at(to).write(new_balance);
        storage.total_supply.write(supply);
    }

    #[aztec(public)]
    fn mint_private(amount: Field, secret_hash: Field) {
        assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter");
        let pending_shields = storage.pending_shields;
        let mut note = TransparentNote::new(amount, secret_hash);
        let supply = storage.total_supply.read().add(U128::from_integer(amount));

        storage.total_supply.write(supply);
        
        pending_shields.insert_from_public(&mut note);
       
    }

    #[aztec(public)]
    fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) {
        if (!from.eq(context.msg_sender())) {
           
            assert_current_call_valid_authwit_public(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }

        let amount = U128::from_integer(amount);
        let from_balance = storage.public_balances.at(from).read().sub(amount);

        let pending_shields = storage.pending_shields;
        let mut note = TransparentNote::new(amount.to_integer(), secret_hash);

        storage.public_balances.at(from).write(from_balance);
        pending_shields.insert_from_public(&mut note);
    }

    #[aztec(public)]
    fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
        if (!from.eq(context.msg_sender())) {
            assert_current_call_valid_authwit_public(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }

        let amount = U128::from_integer(amount);
        let from_balance = storage.public_balances.at(from).read().sub(amount);
        storage.public_balances.at(from).write(from_balance);

        let to_balance = storage.public_balances.at(to).read().add(amount);
        storage.public_balances.at(to).write(to_balance);
    }
   
    #[aztec(public)]
    fn burn_public(from: AztecAddress, amount: Field, nonce: Field) {
    
        if (!from.eq(context.msg_sender())) {
            assert_current_call_valid_authwit_public(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }
        

        let amount = U128::from_integer(amount);
        let from_balance = storage.public_balances.at(from).read().sub(amount);
        storage.public_balances.at(from).write(from_balance);

        let new_supply = storage.total_supply.read().sub(amount);
        storage.total_supply.write(new_supply);
    }

    #[aztec(private)]
    fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) {
        let pending_shields = storage.pending_shields;
        let secret_hash = compute_secret_hash(secret);
 
        let mut options = NoteGetterOptions::new();
        options = options.select(TransparentNote::properties().amount, amount, Option::none()).select(
            TransparentNote::properties().secret_hash,
            secret_hash,
            Option::none()
        ).set_limit(1);
        let notes = pending_shields.get_notes(options);
        let note = notes[0].unwrap_unchecked();
        
        pending_shields.remove(note);

        storage.balances.add(to, U128::from_integer(amount));
    }

    #[aztec(private)]
    fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
        if (!from.eq(context.msg_sender())) {
            assert_current_call_valid_authwit(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }

        storage.balances.sub(from, U128::from_integer(amount));

        let selector = FunctionSelector::from_signature("_increase_public_balance((Field),Field)");
        let _void = context.call_public_function(context.this_address(), selector, [to.to_field(), amount]);
    }
  
    #[aztec(private)]
    fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
       
        if (!from.eq(context.msg_sender())) {
            assert_current_call_valid_authwit(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }
       
        let amount = U128::from_integer(amount);
        storage.balances.sub(from, amount);
       
        storage.balances.add(to, amount);
     
    }

    #[aztec(private)]
    fn burn(from: AztecAddress, amount: Field, nonce: Field) {
        if (!from.eq(context.msg_sender())) {
            assert_current_call_valid_authwit(&mut context, from);
        } else {
            assert(nonce == 0, "invalid nonce");
        }

        storage.balances.sub(from, U128::from_integer(amount));

        let selector = FunctionSelector::from_signature("_reduce_total_supply(Field)");
        let _void = context.call_public_function(context.this_address(), selector, [amount]);
    }

    #[aztec(public)]
    #[aztec(internal)]
    fn _increase_public_balance(to: AztecAddress, amount: Field) {
        let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount));
        storage.public_balances.at(to).write(new_balance);
    }
  
    #[aztec(public)]
    #[aztec(internal)]
    fn _reduce_total_supply(amount: Field) {
   
        let new_supply = storage.total_supply.read().sub(U128::from_integer(amount));
        storage.total_supply.write(new_supply);
    }

    unconstrained fn admin() -> pub Field {
        storage.admin.read().to_field()
    }

    unconstrained fn is_minter(minter: AztecAddress) -> pub bool {
        storage.minters.at(minter).read()
    }

    unconstrained fn total_supply() -> pub Field {
        storage.total_supply.read().to_integer()
    }

    unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field {
        storage.balances.balance_of(owner).to_integer()
    }
   
    unconstrained fn balance_of_public(owner: AztecAddress) -> pub Field {
        storage.public_balances.at(owner).read().to_integer()
    }

    // Function fails
     #[aztec(public)]
    fn public_get_name()  {
        storage.name.read_public()
    }

    #[aztec(private)]
    fn private_get_name()  {
        storage.name.read_private()
    }

    #[aztec(public)]
    fn public_get_symbol()  {
        storage.symbol.read_public()
    }

    #[aztec(private)]
    fn private_get_symbol()  {
        storage.symbol.read_private()
    }

        #[aztec(public)]
    fn public_get_decimals() {
        // docs:start:read_decimals_public
        storage.decimals.read_public()
        // docs:end:read_decimals_public
    }

    #[aztec(private)]
    fn private_get_decimals() {
        // docs:start:read_decimals_private
        storage.decimals.read_private()
        // docs:end:read_decimals_private
    }

    unconstrained fn un_get_symbol() -> pub [u8; 31] {
        storage.symbol.read_public().to_bytes()
    }

   unconstrained fn un_get_name() -> pub [u8; 31] {
        storage.name.read_public().to_bytes()
    }
    unconstrained fn un_get_decimals() -> pub u8 {
        storage.decimals.read_public()
    }

}
  • Creamos una carpeta donde definiremos el tipado de las clases, funciones y variables.

    • Archivo src/types/balances_map.nr
    use dep::aztec::prelude::{
        AztecAddress, NoteGetterOptions, NoteViewerOptions, NoteHeader, NoteInterface, PrivateContext,
        PrivateSet, Map, emit_encrypted_log
    };
    use dep::aztec::{
        context::{PublicContext, Context}, hash::pedersen_hash,
        protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
        note::{note_getter::view_notes, note_getter_options::SortOrder}
    };
    use crate::types::token_note::{TokenNote, OwnedNote};
    
    struct BalancesMap<T> {
        map: Map<AztecAddress, PrivateSet<T>>
    }
    
    impl<T> BalancesMap<T> {
        pub fn new(context: Context, storage_slot: Field) -> Self {
            assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
            Self {
                map: Map::new(
                    context,
                    storage_slot,
                    |context, slot| PrivateSet::new(context, slot)
                )
            }
        }
    
        unconstrained pub fn balance_of<T_SERIALIZED_LEN>(
            self: Self,
            owner: AztecAddress
        ) -> U128 where T: NoteInterface<T_SERIALIZED_LEN> + OwnedNote {
            self.balance_of_with_offset(owner, 0)
        }
    
        unconstrained pub fn balance_of_with_offset<T_SERIALIZED_LEN>(
            self: Self,
            owner: AztecAddress,
            offset: u32
        ) -> U128 where T: NoteInterface<T_SERIALIZED_LEN> + OwnedNote {
            let mut balance = U128::from_integer(0);
            // docs:start:view_notes
            let mut options = NoteViewerOptions::new();
            let opt_notes = self.map.at(owner).view_notes(options.set_offset(offset));
            // docs:end:view_notes
            let len = opt_notes.len();
            for i in 0..len {
                if opt_notes[i].is_some() {
                    balance = balance + opt_notes[i].unwrap_unchecked().get_amount();
                }
            }
            if (opt_notes[len - 1].is_some()) {
                balance = balance + self.balance_of_with_offset(owner, offset + opt_notes.len() as u32);
            }
    
            balance
        }
    
        pub fn add<T_SERIALIZED_LEN>(
            self: Self,
            owner: AztecAddress,
            addend: U128
        ) where T: NoteInterface<T_SERIALIZED_LEN> + OwnedNote {
            let mut addend_note = T::new(addend, owner);
    
            // docs:start:insert
            self.map.at(owner).insert(&mut addend_note, true);
            // docs:end:insert
        }
    
        pub fn sub<T_SERIALIZED_LEN>(
            self: Self,
            owner: AztecAddress,
            subtrahend: U128
        ) where T: NoteInterface<T_SERIALIZED_LEN> + OwnedNote {
            // docs:start:get_notes
            let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend);
            let maybe_notes = self.map.at(owner).get_notes(options);
            // docs:end:get_notes
    
            let mut minuend: U128 = U128::from_integer(0);
            for i in 0..maybe_notes.len() {
                if maybe_notes[i].is_some() {
                    let note = maybe_notes[i].unwrap_unchecked();
    
                    // Removes the note from the owner's set of notes.
                    // This will call the the `compute_nullifer` function of the `token_note`
                    // which require knowledge of the secret key (currently the users encryption key).
                    // The contract logic must ensure that the spending key is used as well.
                    // docs:start:remove
                    self.map.at(owner).remove(note);
                    // docs:end:remove
    
                    minuend = minuend + note.get_amount();
                }
            }
    
            // This is to provide a nicer error msg,
            // without it minuend-subtrahend would still catch it, but more generic error then.
            // without the == true, it includes 'minuend.ge(subtrahend)' as part of the error.
            assert(minuend >= subtrahend, "Balance too low");
    
            self.add(owner, minuend - subtrahend);
        }
    }
    
    pub fn filter_notes_min_sum<T, T_SERIALIZED_LEN>(
        notes: [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL],
        min_sum: U128
    ) -> [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface<T_SERIALIZED_LEN> + OwnedNote {
        let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL];
        let mut sum = U128::from_integer(0);
        for i in 0..notes.len() {
            if notes[i].is_some() & sum < min_sum {
                let note = notes[i].unwrap_unchecked();
                selected[i] = Option::some(note);
                sum = sum.add(note.get_amount());
            }
        }
        selected
    }
    
    
    • Archivo src/types/token_note.nr

      use dep::aztec::prelude::{
          AztecAddress, NoteInterface, NoteGetterOptions, NoteViewerOptions, NoteHeader, PrivateContext,
          PrivateSet, Map, emit_encrypted_log
      };
      use dep::aztec::{note::utils::compute_note_hash_for_consumption, hash::pedersen_hash};
      use dep::aztec::oracle::{unsafe_rand::unsafe_rand, nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key};
      
      trait OwnedNote {
          fn new(amount: U128, owner: AztecAddress) -> Self;
          fn get_amount(self) -> U128;
          fn get_owner(self) -> AztecAddress;
      }
      
      global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header.
      
      #[aztec(note)]
      struct TokenNote {
          // the amount of tokens in the note
          amount: U128,
          // the provider of secrets for the nullifier. The owner (recipient) to ensure that the note 
          // can be privately spent. When nullifier secret and encryption private key is same 
          // we can simply use the owner for this one.
          owner: AztecAddress,
          // randomness of the note to hide contents.
          randomness: Field,
      }
      
      impl NoteInterface<TOKEN_NOTE_LEN> for TokenNote {
          // docs:start:nullifier
          fn compute_nullifier(self, context: &mut PrivateContext) -> Field {
              let note_hash_for_nullify = compute_note_hash_for_consumption(self);
              let secret = context.request_nullifier_secret_key(self.owner);
              // TODO(#1205) Should use a non-zero generator index.
              pedersen_hash([
                  note_hash_for_nullify,
                  secret.low,
                  secret.high,
              ],0)
          }
          // docs:end:nullifier
      
          fn compute_nullifier_without_context(self) -> Field {
              let note_hash_for_nullify = compute_note_hash_for_consumption(self);
              let secret = get_nullifier_secret_key(self.owner);
              // TODO(#1205) Should use a non-zero generator index.
              pedersen_hash([
                  note_hash_for_nullify,
                  secret.low,
                  secret.high,
              ],0)
          }
      
          // Broadcasts the note as an encrypted log on L1.
          fn broadcast(self, context: &mut PrivateContext, slot: Field) {
            // We only bother inserting the note if non-empty to save funds on gas.
            if !(self.amount == U128::from_integer(0)) {
                let encryption_pub_key = get_public_key(self.owner);
                emit_encrypted_log(
                    context,
                    (*context).this_address(),
                    slot,
                    Self::get_note_type_id(),
                    encryption_pub_key,
                    self.serialize_content(),
                );
            }
          }
       }
      
      impl OwnedNote for TokenNote {
          fn new(amount: U128, owner: AztecAddress) -> Self {
              Self {
                  amount,
                  owner,
                  randomness: unsafe_rand(),
                  header: NoteHeader::empty(),
              }
          }
      
          fn get_amount(self) -> U128 {
              self.amount
          }
      
          fn get_owner(self) -> AztecAddress {
              self.owner
          }
      
      }
      
      
    • Archivo src/types/transparent_note.nr

      // docs:start:token_types_all
      use dep::aztec::prelude::{NoteHeader, NoteInterface, PrivateContext};
      use dep::aztec::{
          note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption},
          hash::{compute_secret_hash, pedersen_hash}
      };
      
      global TRANSPARENT_NOTE_LEN: Field = 2;
      
      // Transparent note represents a note that is created in the clear (public execution),
      // but can only be spent by those that know the preimage of the "secret_hash"
      #[aztec(note)]
      struct TransparentNote {
          amount: Field,
          secret_hash: Field,
          // the secret is just here for ease of use and won't be (de)serialized
          secret: Field
      }
      
      struct TransparentNoteProperties {
          amount: PropertySelector,
          secret_hash: PropertySelector,
      }
      
      impl NoteInterface<TRANSPARENT_NOTE_LEN> for TransparentNote {
      
          // Custom serialization to avoid disclosing the secret field
          fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] {
              [self.amount, self.secret_hash]
          }
      
          // Custom deserialization since we don't have access to the secret plaintext
          fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self {
              TransparentNote {
                  amount: serialized_note[0],
                  secret_hash: serialized_note[1],
                  secret: 0,
                  header: NoteHeader::empty(),
              }
          }
      
          fn compute_nullifier(self, _context: &mut PrivateContext) -> Field {
              self.compute_nullifier_without_context()
          }
      
          fn compute_nullifier_without_context(self) -> Field {
              let siloed_note_hash = compute_note_hash_for_consumption(self);
              // TODO(#1205) Should use a non-zero generator index.
              pedersen_hash([self.secret, siloed_note_hash],0)
          }
      
          fn broadcast(self, context: &mut PrivateContext, slot: Field) {
              assert(false, "TransparentNote does not support broadcast");
          }
      }
      
      impl TransparentNote {
      
          // CONSTRUCTORS
      
          pub fn new(amount: Field, secret_hash: Field) -> Self {
              TransparentNote { amount, secret_hash, secret: 0, header: NoteHeader::empty() }
          }
      
          // new oracle call primitive
          // get me the secret corresponding to this hash
          pub fn new_from_secret(amount: Field, secret: Field) -> Self {
              TransparentNote { amount, secret_hash: compute_secret_hash(secret), secret, header: NoteHeader::empty() }
          }
      
          // CUSTOM FUNCTIONS FOR THIS NOTE TYPE
      
          pub fn knows_secret(self, secret: Field) {
              let hash = compute_secret_hash(secret);
              assert(self.secret_hash == hash);
          }
      
          // Custom serialization forces us to manually create the metadata struct and its getter
          pub fn properties() -> TransparentNoteProperties {
              TransparentNoteProperties {
                  amount: PropertySelector { index: 0, offset: 0, length: 32 },
                  secret_hash: PropertySelector { index: 1, offset: 0, length: 32 }
              }
          }
      }
      // docs:end:token_types_all
      
      
  • Creamos un archivo types.nr donde inportaremos y a su vez exportaremos los types que definimos en la carpeta, esto con el objetivo de centralizar el tipado atravez de un solo archivo

    • Archivo src/types.nr
    mod transparent_note;
    mod balances_map;
    mod token_note;
    
  • Actualizamos el archivo Nargo.toml con las dependencias

    [package]
    name = "token_contract"
    authors = [ "" ]
    compiler_version = ">=0.25.0"
    type = "contract"
    
    [dependencies]
    aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="aztec-packages-v0.35.1", directory="noir-projects/aztec-nr/aztec" }
    authwit={ git="https://github.com/AztecProtocol/aztec-packages/", tag="aztec-packages-v0.35.1", directory="noir-projects/aztec-nr/authwit"}
    compressed_string = {git="https://github.com/AztecProtocol/aztec-packages/", tag="aztec-packages-v0.35.1", directory="noir-projects/aztec-nr/compressed-string"}
    
  • Compile the smart contract.

    yarn compile
    

Integrando la interfaz de usuario

Vamos a crear una interfaz sencilla semejante al siguiente mockup:

  • Creamos un hook src/hooks/useToken.tsx

  • import { useState } from 'react';
    import { Contract } from '@aztec/aztec.js';
    import { deployerEnv } from '../config';
    import { toast } from 'react-toastify';
    
    export function useToken({ contract }: { contract: Contract }) {
      const [wait, setWait] = useState(false);
    
      const setAdmin = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        const el = e.currentTarget.elements.namedItem('admin') as HTMLInputElement;
        if (el) {
          setWait(true);
    
          const address = el.value;
          await toast.promise(contract!.methods.set_admin(BigInt(address)).send().wait(), {
            pending: 'Setting ...',
            success: `Admin set: ${address}`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      const setMinter = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        const address = (e.currentTarget.elements.namedItem('minter') as HTMLInputElement).value;
        const approve = (e.currentTarget.elements.namedItem('approve') as HTMLInputElement).value;
        if (address && approve) {
          setWait(true);
    
          await toast.promise(contract!.methods.set_minter(BigInt(address), approve).send().wait(), {
            pending: 'Setting ...',
            success: `Minter set: ${address}`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      const mintPublic = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        const address = (e.currentTarget.elements.namedItem('minter') as HTMLInputElement).value;
        const amount = (e.currentTarget.elements.namedItem('amount') as HTMLInputElement).value;
        if (address && amount) {
          setWait(true);
    
          await toast.promise(contract!.methods.mint_public(BigInt(address), amount).send().wait(), {
            pending: 'Setting ...',
            success: `Process success`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      const mintPrivate = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        const amount = (e.currentTarget.elements.namedItem('amount') as HTMLInputElement).value;
        const secret = (e.currentTarget.elements.namedItem('secretHash') as HTMLInputElement).value;
        if (amount && secret) {
          setWait(true);
    
          await toast.promise(contract!.methods.mint_private(amount, secret).send().wait(), {
            pending: 'Setting ...',
            success: `Process Success`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      const shield = async (e: React.FormEvent<HTMLFormElement>) => {
        // from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field
        e.preventDefault();
    
        const amount = (e.currentTarget.elements.namedItem('amount') as HTMLInputElement).value;
        const from = (e.currentTarget.elements.namedItem('from') as HTMLInputElement).value;
        const secret = (e.currentTarget.elements.namedItem('secretHash') as HTMLInputElement).value;
        const nonce = (e.currentTarget.elements.namedItem('nonce') as HTMLInputElement).value;
        if (amount && secret && from && nonce) {
          setWait(true);
    
          await toast.promise(contract!.methods.shield(BigInt(from), BigInt(amount), secret, nonce).send().wait(), {
            pending: 'Setting ...',
            success: `Process Success`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      const transferPublic = async (e: React.FormEvent<HTMLFormElement>) => {
        // transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field)
    
        e.preventDefault();
    
        const amount = (e.currentTarget.elements.namedItem('amount') as HTMLInputElement).value;
        const from = (e.currentTarget.elements.namedItem('from') as HTMLInputElement).value;
        const to = (e.currentTarget.elements.namedItem('to') as HTMLInputElement).value;
        const nonce = (e.currentTarget.elements.namedItem('nonce') as HTMLInputElement).value;
        if (amount && from && to && nonce) {
          setWait(true);
    
          await toast.promise(
            contract!.methods.transfer_public(BigInt(from), BigInt(to), BigInt(amount), nonce).send().wait(),
            {
              pending: 'Setting ...',
              success: `Process Success`,
              error: 'Error setting admin',
            },
          );
          setWait(false);
        }
      };
      const burnPublic = async (e: React.FormEvent<HTMLFormElement>) => {
        // burn_public(from: AztecAddress, amount: Field, nonce: Field)
    
        e.preventDefault();
    
        const amount = (e.currentTarget.elements.namedItem('amount') as HTMLInputElement).value;
        const from = (e.currentTarget.elements.namedItem('from') as HTMLInputElement).value;
    
        const nonce = (e.currentTarget.elements.namedItem('nonce') as HTMLInputElement).value;
        if (amount && from && nonce) {
          setWait(true);
    
          await toast.promise(contract!.methods.burn_public(BigInt(from), BigInt(amount), nonce).send().wait(), {
            pending: 'Setting ...',
            success: `Process Success`,
            error: 'Error setting admin',
          });
          setWait(false);
        }
      };
      return { setAdmin, setMinter, mintPublic, mintPrivate, shield, transferPublic, burnPublic, wait };
    }
    
    
  • Creamos el UI con los formularios que nos ayudaran a interactuar con nuestro contrato de token. src/pages/contract.tsx

    import { useState } from 'react';
    import { Contract } from '@aztec/aztec.js';
    import { filteredInterface } from '../config';
    import { useToken } from '../hooks/useToken';
    
    export function ContractComponent({ contract }: { contract: Contract }) {
      const { wait, setAdmin, setMinter, mintPublic, mintPrivate, shield, transferPublic, burnPublic } = useToken({
        contract,
      });
    
      return (
        <div>
          <h1>Your Contract</h1>
    
          <label htmlFor="functions">Functions:</label>
          <select name="functions" id="functions" onChange={() => console.log('')}>
            {filteredInterface.map(
              (fn, index) =>
                fn.functionType !== 'unconstrained' && (
                  <option key={index} value={index}>
                    {fn.name}
                  </option>
                ),
            )}
          </select>
          <br />
          <h1>Set Admin</h1>
          <form onSubmit={setAdmin}>
            <label>Admin Address</label>
            <input type="text" name="admin" id="admin" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Set Minter</h1>
          <form onSubmit={setMinter}>
            <label>Admin Address</label>
            <input type="text" name="minter" id="minter" />
            <label>Approve</label>
            <input type="checkbox" name="approve" id="approve" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Mint Public</h1>
          <form onSubmit={mintPublic}>
            <label>Admin Address</label>
            <input type="text" name="to" id="to" />
            <label>Approve</label>
            <input type="number" name="amount" id="amount" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Mint Private</h1>
          <form onSubmit={mintPrivate}>
            <label>Amount</label>
            <input type="number" name="amount" id="amount" />
            <label>secret hash</label>
            <input type="text" name="text" id="text" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Shield</h1>
          <form onSubmit={shield}>
            {/* from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field */}
            <label>Address from</label>
            <input type="text" name="from" id="from" />
            <label>Amount</label>
            <input type="number" name="amount" id="amount" />
            <label>Secret</label>
            <input type="text" name="secretHash" id="secretHash" />
            <label>Nonce</label>
            <input type="text" name="nonce" id="nonce" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Transfer Public</h1>
          <form onSubmit={transferPublic}>
            {/* from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field */}
            <label>Address from</label>
            <input type="text" name="from" id="from" />
            <label>Amount</label>
            <input type="number" name="amount" id="amount" />
            <label>To Address</label>
            <input type="text" name="to" id="to" />
            <label>Nonce</label>
            <input type="text" name="nonce" id="nonce" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
          <h1>Burn Public</h1>
          <form onSubmit={burnPublic}>
            {/* from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field */}
            <label>Address from</label>
            <input type="text" name="from" id="from" />
            <label>Amount</label>
            <input type="number" name="amount" id="amount" />
            <label>Nonce</label>
            <input type="text" name="nonce" id="nonce" />
            <button type="submit" disabled={wait}>
              Set
            </button>
          </form>
          <br />
        </div>
      );
    }
    
    

Referencia:

Documentacion oficial [Aztec-sandbox](

)

Tutorial [Contrato de tokens](

)

Documentacion oficial Noir:

Pequeños retos para experimentar:

Subscribe to 0xe527…194B
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.