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.
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
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.
Requisitos previos
Tener instalado node >= 18
Tener instalado docker
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
Verificamos que tenemos instalado node >= 18
node -v
Verificamos que tenemos instalado docker
docker -v
Creamos un nuevo proyecto a partir de la linea de comandos
npm create aztec-app
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.
** 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
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.
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
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
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>
);
}
Documentacion oficial [Aztec-sandbox](
)
Tutorial [Contrato de tokens](
)
Documentacion oficial Noir: