Quickstart para desarrolladores
Esta guía ayudara a los desarrolladores a través del desarrollo de un contrato inteligente en Sway, una prueba sencilla, el despliegue en Fuel y la construcción de un frontend.
Antes de empezar, puede ser útil entender la terminología que se utilizará a lo largo de los documentos y cómo se relacionan entre sí:
Fuel: la cadena de bloques Fuel.
FuelVM: la máquina virtual de Fuel.
Sway: el lenguaje específico del dominio creado para FuelVM; está inspirado en Rust.
Forc: el sistema de construcción y el gestor de paquetes para Sway, similar a Cargo para Rust.
Tipos de programas en Sway
Hay cuatro tipos de programas Sway:
contrato
predicado
script
biblioteca
Los contratos, predicados y scripts pueden producir artefactos utilizables en la blockchain, mientras que una biblioteca es simplemente un proyecto diseñado para la reutilización del código y no es directamente desplegable.
Las principales características de un contrato inteligente que lo diferencian de los scripts o predicados son que es invocable y con estado.
Un script es un bytecode ejecutable en la cadena que puede llamar a los contratos para realizar alguna tarea. No representa la propiedad de ningún recurso y no puede ser llamado por un contrato.
Instalación
Comience por instalar la Rust toolchain.
A continuación, instala el toolchain de Fuel.
Tu primer proyecto Sway
Construiremos un simple contrato de contador con dos funciones: una para incrementar el contador, y otra para devolver el valor del contador.
Algunos datos que serán útiles antes de continuar:
Esta guía fue creada usando VSCode como editor de código.
Descarga la extensión de lenguaje Sway en VSCode para obtener resaltado de sintaxis, atajos de teclado y más.
Descarga la extensión rust-analyzer en VSCode para obtener resaltado de sintaxis, completado de código y más.
Comienza creando una nueva carpeta vacía. La llamaremos proyecto-en-fuel.
Escribir el contrato
Luego de que instale forc cree un proyecto de contrato dentro de su carpeta proyecto-en-fuel:
$ cd proyecto-en-fuel
$ forc new contador
To compile, use `forc build`, and to run tests use `forc test`
----
Read the Docs:
- Sway Book: https://fuellabs.github.io/sway/latest
- Rust SDK Book: https://fuellabs.github.io/fuels-rs/latest
- TypeScript SDK: https://github.com/FuelLabs/fuels-ts
Join the Community:
- Follow us @SwayLang: https://twitter.com/SwayLang
- Ask questions in dev-chat on Discord: https://discord.com/invite/xfpK4Pe
Report Bugs:
- Sway Issues: https://github.com/FuelLabs/sway/issues/new
Así se debe de ver el proyecto que has creado.
$ tree contador
contador
├── Forc.toml
└── src
└── main.sw
Forc.toml es el archivo de manifiesto (similar a Cargo.toml para Cargo o package.json para Node) y define los metadatos del proyecto como el nombre del proyecto y las dependencias.
Abre tu proyecto en un editor de código y elimina el código de la plantilla en src/main.sw para que empieces con un archivo vacío.
Cada archivo Sway debe comenzar con una declaración de qué tipo de programa contiene el archivo; aquí, hemos declarado que este archivo es un contrato.
contract;
A continuación, definiremos un valor de almacenamiento para el contador, sera de tipo entero sin signo de 64 bits y su valor inicial sera 0.
storage {
contador: u64 = 0,
}
ABI
Una ABI define una interfaz, y no hay ningún cuerpo de función en la ABI. Un contrato debe definir o importar una declaración ABI e implementarla. Se considera que la mejor práctica es definir la ABI en una biblioteca separada e importarla en el contrato porque esto permite a los que llaman al contrato importar y utilizar la ABI en los scripts para llamar a su contrato.
Para simplificar, definiremos la ABI directamente en el archivo del contrato.
abi counter{
#[storage(read, write)]
fn increment();
#[storage(read)]
fn count() -> u64;
}
Veamos línea por línea
#[storage(read, write)]
es una anotación que denota que esta función tiene permiso para leer y escribir valores en storage.
fn incrementar(); - Estamos introduciendo la funcionalidad de incrementar y denotando que no debe devolver ningún valor.
#[storage(read)] es una anotación que denota que esta función tiene permiso para leer valores en el almacenamiento.
fn contador() -> u64; - Introducimos la funcionalidad de incrementar el contador y denotamos el valor de retorno de la función.
Implementar la ABI
Después que defina la ABI escribirá la implementación de las funciones escritas en la ABI.
impl Counter for Contract {
#[storage(read)]
fn count() -> u64 {
storage.contador
}
#[storage(read, write)]
fn increment() {
storage.contador = storage.contador + 1;
}
}
What we just did
En fn contador(), leemos y devolvemos la variable counter desde el almacenamiento.
fn counter() -> u64 {
storage.counter
}
En fn incrementar(), leemos la variable contador de la memoria e incrementamos su valor en uno. A continuación, almacenamos el nuevo valor en el contador.
fn increment() {
storage.counter = storage.counter + 1;
}
Así se debería ver su código hasta ahora:
Archivo: ./contador/src/main.sw
contract;
storage {
counter: u64 = 0,
}
abi Counter {
#[storage(read, write)]
fn increment();
#[storage(read)]
fn count() -> u64;
}
impl Counter for Contract {
#[storage(read)]
fn count() -> u64 {
storage.counter
}
#[storage(read, write)]
fn increment() {
storage.counter = storage.counter + 1;
}
}
Build Al Contrato
Desde el directorio proyecto-en-fuel/contador, ejecute el siguiente comando para construir su contrato:
$ forc build
Compiled library "core".
Compiled library "std".
Compiled contract "counter-contract".
Bytecode size is 232 bytes.
Veamos el contenido de la carpeta contador después del build:
$ tree .
.
├── Forc.lock
├── Forc.toml
├── out
│ └── debug
│ ├── counter-contract-abi.json
│ ├── counter-contract-contract-id
│ ├── counter-contract-storage_slots.json
│ └── counter-contract.bin
└── src
└── main.sw
Ahora tenemos un directorio de salida que contiene nuestros artefactos de construcción como la representación JSON de nuestro ABI y el bytecode del contrato.
Probando tu contrato
Comenzaremos añadiendo un harness de pruebas de integración de Rust utilizando una plantilla de generación de Cargo. ¡Vamos a asegurarnos de que tenemos el comando cargo generate instalado!
cargo install cargo-generate
Nota: Puede obtener más información sobre cargo generate visitando su repositorio.
Ahora, vamos a generar el harness de pruebas por defecto con lo siguiente:
cargo generate --init fuellabs/sway templates/sway-test-rs --name counter-contract
Si todo va bien, el resultado debería ser el siguiente:
⚠️ Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway
🤷 Project Name : contador
🔧 Destination: /home/user/path/to/contador...
🔧 Generating template ...
[1/3] Done: Cargo.toml
[2/3] Done: tests/harness.rs
[3/3] Done: tests
🔧 Moving generated files into: `/home/user/path/to/contador`...
✨ Done! New project created /home/user/path/to/contador
Veamos el resultado:
$ tree .
.
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── out
│ └── debug
│ ├── counter-contract-abi.json
│ ├── counter-contract-contract-id
│ ├── counter-contract-storage_slots.json
│ └── counter-contract.bin
├── src
│ └── main.sw
└── tests
└── harness.rs
¡Ahora tienes dos archivos nuevos!
El Cargo.toml es el manifiesto de nuestro nuevo harness de pruebas y especifica las dependencias necesarias, incluyendo los combustibles del SDK de Fuel Rust.
El tests/harness.rs contiene algo de código de prueba para empezar, aunque todavía no llama a ningún método del contrato.
Ahora que tenemos nuestro harness de pruebas por defecto, vamos a añadirle algunas pruebas útiles.
Al final de test/harness.rs, define el cuerpo de can_get_contract_id(). Este es el aspecto que debería tener su código para verificar que el valor del contador se incrementó:
Archivo: tests/harness.rs
#[tokio::test]
async fn can_get_contract_id() {
let (instance, _id) = get_contract_instance().await;
// Increment the counter
let _result = instance.methods().increment().call().await.unwrap();
// Get the current value of the counter
let result = instance.methods().count().call().await.unwrap();
// Check that the current value of the counter is 1.
// Recall that the initial value of the counter was 0.
assert_eq!(result.value, 1);
}
Ejecute cargo test en el terminal. Si todo va bien, la salida debería ser la siguiente:
$ cargo test
...
running 1 test
test can_get_contract_id ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
Desplegando el contrato
Ahora es el momento de desplegar el contrato en la red de pruebas. Mostraremos cómo hacer esto usando forc desde la línea de comandos, pero también puedes hacerlo usando el SDK de Rust o el SDK de TypeScript.
Para desplegar un contrato, necesitas tener un monedero para firmar la transacción y monedas para pagar la gasolina. Primero, crearemos un monedero.
Instalar el Wallet CLI
Sigue estos pasos para configurar un monedero y crear una cuenta.
Después de escribir una contraseña, asegúrese de guardar la frase mnemotécnica que sale.
Con esto, usted obtendrá una dirección de combustible que se parece a esto: fuel1efz7lf36w9da9jekqzyuzqsfrqrlzwtt3j3clvemm6eru8fe9nvqj5kar8. Guarde esta dirección, ya que la necesitará para firmar las transacciones cuando despleguemos el contrato.
Obtener Testnet Coins
Con la dirección de tu cuenta en la mano, dirígete al grifo de testnet para obtener algunas monedas enviadas a tu cartera.
Despliegue en Testnet
Ahora que tienes un monedero, puedes desplegarlo con forc deploy y pasando el endpoint de testnet así
forc deploy --url https://node-beta-1.fuel.network/graphql --gas-price 1
Nota: Hemos fijado el precio del gas en 1. Sin esta bandera, el precio del gas es 0 por defecto y la transacción fallará.
El terminal te pedirá la dirección del monedero con el que quieres firmar esta transacción, pega la dirección que guardaste antes, se ve así: fuel1efz7lf36w9da9jekqzyuzqsfrqrlzwtt3j3clvemm6eru8fe9nvqj5kar8
El terminal mostrará su ID de contrato de la siguiente manera:
Contract id: 0xe5dc89f7b8c62e40927a6b17f144583bf6571d2468ab1e2554d2731f4c9fc428
Asegúrate de guardar esto ya que lo necesitarás para construir un frontend con el SDK de Typescript más adelante en este tutorial.
El terminal emitirá un mensaje para firmar y le pedirá una firma. Abra una nueva pestaña de terminal y vea sus cuentas ejecutando forc wallet list. Si has seguido estos pasos, te darás cuenta de que sólo tienes una cuenta, la 0.
Toma el mensaje para firmar desde tu otro terminal y firma con tu cuenta ejecutando el siguiente comando:
forc wallet sign` + `[mensaje a firmar, sin paréntesis]` + `[el número de cuenta, sin paréntesis]`
Tu comando debería ser como el siguiente
$ forc wallet sign 16d7a8f9d15cfba1bd000d3f99cd4077dfa1fce2a6de83887afc3f739d6c84df 0
Please enter your password to decrypt initialized wallet's phrases:
Signature: 736dec3e92711da9f52bed7ad4e51e3ec1c9390f4b05caf10743229295ffd5c1c08a4ca477afa85909173af3feeda7c607af5109ef6eb72b6b40b3484db2332c
Introduce tu contraseña cuando se te pida, y obtendrás una firma. Guarde esa firma, y vuelva a su otra ventana de terminal, y péguela donde se le pide que proporcione una firma para esta transacción.
Finalmente, obtendrá un TransactionId para confirmar que su contrato fue desplegado. Con este ID, puede dirigirse al explorador de bloques y ver su contrato.
Nota: Debe anteponer el 0x a su TransactionId para poder verlo en el explorador de bloques.
Crear un frontend para interactuar con el contrato
Ahora vamos a
Inicializar un proyecto React.
Instalar las dependencias del SDK de combustibles.
Modificar la App.
Ejecutar nuestro proyecto.
Inicializar un proyecto React
Para dividir mejor nuestro proyecto vamos a crear una nueva carpeta frontend e inicializar nuestro proyecto dentro de ella.
En la terminal, retrocede un directorio e inicializa un proyecto react con Create React App.
$ cd ..
$ npx create-react-app frontend --template typescript
Success! Created frontend at proyecto-en-fuel/frontend
Ahora deberías tener tu carpeta exterior, proyecto-en-fuel, con dos carpetas dentro: contador y frontend
Instalar las dependencias del SDK de Fuel
En este paso necesitamos instalar 3 dependencias para el proyecto:
fuels: El paquete paraguas que incluye todas las herramientas principales; Wallet, Contracts, Providers y más.
fuelchain: Fuelchain es el generador de ABI TypeScript.
typechain-target-fuels: El objetivo de Fuelchain es el intérprete específico para la especificación ABI.
ABI son las siglas de Application Binary Interface. Las ABI's informan a la aplicación de la interfaz para interactuar con la VM, en otras palabras, proporcionan información a la APP como qué métodos tiene un contrato, qué params, tipos espera, etc.
Instalar
Muévete a la carpeta del frontend, luego instala las dependencias:
$ cd frontend
$ npm install fuels@0.17.0 --save
added 65 packages, and audited 1493 packages in 4s
$ npm install fuelchain@0.17.0 typechain-target-fuels@0.17.0 --save-dev
added 33 packages, and audited 1526 packages in 2s
Generación de tipos de contrato
Para facilitar la interacción con nuestro contrato utilizamos fuelchain para interpretar el ABI JSON de salida de nuestro contrato. Este JSON se creó en el momento en que ejecutamos el build de forc para compilar nuestro Sway Contract en binario.
Si ves la carpeta proyecto-en-fuel/contador/out podrás ver el ABI JSON allí. Si quieres saber más, lee la especificación ABI.
Dentro del directorio proyecto-en-fuel/frontend ejecuta:
$ npx fuelchain --target=fuels --out-dir=./src/contracts ../contador/out/debug/*-abi.json Successfully generated 4 typings!
Ahora deberías encontrar una nueva carpeta proyecto-en-fuel/frontend/src/contracts. Esta carpeta fue auto-generada por nuestro comando fuelchain, estos archivos abstraen el trabajo que tendríamos que hacer para crear una instancia de contrato, y generan una interfaz completa de TypeScript para el Contrato, facilitando el desarrollo.
Crear una cartera (de nuevo)
Para interactuar con la red de Fuel tenemos que enviar transacciones firmadas con fondos suficientes para cubrir las tarifas de la red. El SDK de Fuel TS no soporta actualmente integraciones de Wallet, lo que nos obliga a tener un wallet no seguro dentro de la WebApp usando una privateKey.
Nota: Esto debe hacerse sólo con fines de desarrollo. Nunca expongas una aplicación web con una clave privada dentro. The Fuel Wallet está en desarrollo activo, siga el progreso aquí.
En la raíz del proyecto frontend crea un archivo llamado createWallet.js y añade el siguiente código:
Archivo: ./frontend/createWallet.js
$ node createWallet.js address fuel1y2rtuzyfwxkp9wsneedexa3q2v09rszz5lpse2uzyjl3782yl8hsek9rhr private key 0x37f909843e6fcdf76cc9b5acde216f44e6a2b304c14f3fbc3054dfeb12b74599
Nota: Debe utilizar la dirección y la clave privada generadas.
Guarde la clave privada, la necesitará más tarde para establecerla como valor de cadena para una variable WALLET_SECRET en su archivo App.tsx. Más adelante se hablará de ello.
Primero, toma la dirección de tu cartera y úsala para obtener algunas monedas del grifo de testnet.
Ahora estás listo para construir y enviar ⛽
Nota: El equipo está trabajando para simplificar el proceso de creación de un monedero, y eliminar la necesidad de crear un monedero dos veces. Esté atento a estas actualizaciones.
Modificar la aplicación
Dentro de la carpeta frontend/src vamos a añadir el código que interactúa con nuestro contrato. Lee los comentarios para ayudarte a entender las partes de la App. Cambia el archivo proyecto-en-fuel/frontend/src/App.tsx por
Archivo: ./frontend/src/App.tsx
import React, { useEffect, useState } from "react";
import { Wallet } from "fuels";
import "./App.css";
// Import the contract factory -- you can find the name in index.ts.
// You can also do command + space and the compiler will suggest the correct name.
import { CounterContractAbi__factory } from "./contracts";
// The address of the contract deployed the Fuel testnet
const CONTRACT_ID = "<YOUR-CONTRACT-ID>";
//the private key from createWallet.js
const WALLET_SECRET = "<YOUR-PRIVATE-KEY>";
// Create a Wallet from given secretKey in this case
// The one we configured at the chainConfig.json
const wallet = new Wallet(
WALLET_SECRET,
"https://node-beta-1.fuel.network/graphql"
);
// Connects out Contract instance to the deployed contract
// address using the given wallet.
const contract = CounterContractAbi__factory.connect(CONTRACT_ID, wallet);
function App() {
const [counter, setCounter] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function main() {
// Executes the counter function to query the current contract state
// the `.get()` is read-only, because of this it don't expand coins.
const { value } = await contract.functions.count().get();
setCounter(Number(value));
}
main();
}, []);
async function increment() {
// a loading state
setLoading(true);
// Creates a transactions to call the increment function
// because it creates a TX and updates the contract state this requires the wallet to have enough coins to cover the costs and also to sign the Transaction
try {
await contract.functions.increment().txParams({ gasPrice: 1 }).call();
const { value } = await contract.functions.count().get();
setCounter(Number(value));
} finally {
setLoading(false);
}
}
return (
<div className="App">
<header className="App-header">
<p>Counter: {counter}</p>
<button disabled={loading} onClick={increment}>
{loading ? "Incrementing..." : "Increment"}
</button>
</header>
</div>
);
}
export default App;
Ejecuta tu proyecto
Ahora es el momento de divertirse, ejecuta el proyecto en tu navegador.
Dentro del directorio proyecto-en-fuel/frontend ejecuta:
$ npm start
Compiled successfully!
You can now view frontend in the browser.
Local: http://localhost:3001
On Your Network: http://192.168.4.48:3001
Note that the development build is not optimized.
To create a production build, use npm run build.
✨⛽✨ Felicidades has completado tu primera DApp en Fuel ✨⛽✨
Aquí está el repo para este proyecto. Si te encuentras con algún problema, un buen primer paso es comparar tu código con este repo y resolver cualquier diferencia.
Tuitéanos a @fuellabs_ haciéndonos saber que acabas de construir una dapp en Fuel, puede que te inviten a un grupo privado de constructores, que te inviten a la próxima cena de Fuel, que te den una alfa en el proyecto, o algo 👀.
Actualizar el contrato
Si haces cambios en tu contrato, estos son los pasos que debes seguir para que tu frontend y tu contrato vuelvan a estar sincronizados:
En el directorio de tu contrato, ejecuta forc build
En su directorio de contratos, vuelva a desplegar el contrato ejecutando este comando y siguiendo los mismos pasos anteriores para firmar la transacción con su cartera:
forc deploy --url https://node-beta-1.fuel.network/graphql --gas-price 1
En el directorio de tu frontend, vuelve a ejecutar este comando:
npx fuelchain --target=fuels --out-dir=./src/contracts ../counter-contract/out/debug/*-abi.json
En su directorio proyecto-en-fuel/frontend, actualice el ID del contrato en su archivo App.tsx
¿Necesitas ayuda?
Dirígete al discord de Fuel para obtener ayuda.