Aprenda a construir um AMM básico com as funções Provide (Fornecer), Withdraw (Retirar) e Swap (Trocar)
Introdução
Neste tutorial, aprenderemos como construir um AMM muito básico com recursos como Fornecer, Retirar e Trocar sem nenhum mecanismo de incentivo, como taxas de negociação. Além disso, não negociaremos com tokens ERC20, manteremos nosso próprio mapeamento armazenando o saldo das contas para manter as coisas simples! Construiremos o contrato inteligente em Solidity e o frontend de nosso aplicativo com a ajuda do ReactJS.
Pré-requisitos
- Familiaridade básica com ReactJS e Solidity
- Deveria ter concluído o tutorial Deploy a Smart Contract on Avalanche using Remix and MetaMask
Requisitos
- Node.js v10.18.0+
- Extensão Metamask no seu navegador
O que é um AMM?
O Formador de Mercado Automatizado (AMM) é um tipo de troca descentralizada, baseada em uma fórmula matemática de preços de ativos. Ele permite que ativos digitais sejam negociados sem necessidade de permissão e automaticamente usando pools de liquidez em vez de compradores e vendedores tradicionais que usam um livro de pedidos usado na bolsa (exchange) tradicional, aqui os ativos são precificados de acordo com um algoritmo de precificação.
Por exemplo, Uniswap usa p * q = k, onde p é o valor de um token no pool de liquidez e q é o valor do outro. Aqui “k” é uma constante fixa, o que significa que a liquidez total da carteira sempre deve permanecer a mesma. Para mais explicações, vamos dar um exemplo, se um AMM tiver moeda A e moeda B, dois ativos voláteis, toda vez que A é comprado, o preço de A sobe, pois há menos A no pool do que antes da compra. Por outro lado, o preço de B diminui à medida que há mais B no pool. O pool fica em equilíbrio constante, onde o valor total de A no pool será sempre igual ao valor total de B no pool. O tamanho aumentará apenas quando novos provedores de liquidez ingressarem no pool.
Implementando o contrato inteligente
Vamos começar com o código padronizado. Criamos um contrato nomeado AMM
e importamos a biblioteca SafeMath do OpenZeppelin para realizar operações matemáticas com as devidas verificações.
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract AMM {
using SafeMath for uint256;
}
Em seguida, definimos as variáveis de estado necessárias para operar o AMM. Estaremos usando a mesma fórmula matemática usada pela Uniswap para determinar o preço dos ativos (K = totalToken1 * totalToken2). Para simplificar, estamos mantendo nosso próprio mapeamento de saldo interno (token1Balance e token2Balance) em vez de lidar com os tokens ERC-20. Como o Solidity não suporta números de ponto flutuante, reservaremos os seis primeiros dígitos de um valor inteiro para representar o valor decimal após o ponto. Isso é obtido dimensionando os números por um fator de 10^6 (PRECISÃO).
uint256 totalShares; // Armazena o montante total de ações emitidas para o pool
uint256 totalToken1; // Armazena o montante do Token1 bloqueado no pool
uint256 totalToken2; // Armazena o montante do Token2 bloqueado no pool
uint256 K; // Constante algorítmica usada para determinar o preço (K = totalToken1 * totalToken2)
uint256 constant PRECISION = 1_000_000; // Precisão de 6 casas decimais
mapping(address => uint256) shares; // Armazena a participação acionária de cada provedor
mapping(address => uint256) token1Balance; // Armazena o saldo disponível de usuários fora do AMM
mapping(address => uint256) token2Balance;
Agora vamos definir modificadores que serão usados para verificar a validade dos parâmetros passados para as funções e restringir determinadas atividades quando o pool estiver vazio.
// Garante que o _qty não é zero e que o usuário tem saldo suficiente
modifier validAmountCheck(mapping(address => uint256) storage _balance, uint256 _qty) {
require(_qty > 0, "Amount cannot be zero!");
require(_qty <= _balance[msg.sender], "Insufficient amount");
_;
}
// Restringe a retirada, o recurso de troca até que a liquidez seja adicionada ao pool
modifier activePool() {
require(totalShares > 0, "Zero Liquidity");
_;
}
As seguintes funções são usadas para obter o estado atual do contrato inteligente
// Retorna o saldo do usuário
function getMyHoldings() external view returns(uint256 amountToken1, uint256 amountToken2, uint256 myShare) {
amountToken1 = token1Balance[msg.sender];
amountToken2 = token2Balance[msg.sender];
myShare = shares[msg.sender];
}
// Retorna a quantidade total de tokens bloqueados no pool e o total de ações emitidas correspondentes a ele
function getPoolDetails() external view returns(uint256, uint256, uint256) {
return (totalToken1, totalToken2, totalShares);
}
Como não estamos usando os tokens ERC-20 e, em vez disso, mantemos um registro do saldo; precisamos de uma maneira de alocar tokens para os novos usuários para que eles possam interagir com o dApp. Os usuários podem chamar a função torneira (faucet) para obter alguns tokens para jogar!
// Envia token(s) grátis para o invocador
function faucet(uint256 _amountToken1, uint256 _amountToken2) external {
token1Balance[msg.sender] = token1Balance[msg.sender].add(_amountToken1);
token2Balance[msg.sender] = token2Balance[msg.sender].add(_amountToken2);
}
Agora vamos começar a implementar as três funcionalidades principais - Provide, Withdraw e Swap.
Provide (Fornecer)
A função provide
recebe dois parâmetros - quantidade do token1 e quantidade do token2 que o usuário deseja bloquear no pool. Se o pool estiver inicialmente vazio, a taxa de equivalência será definida como _amountToken1 : _amountToken2 e o usuário receberá 100 compartilhamentos para ele. Caso contrário, verifica-se se os dois valores informados pelo usuário possuem valor equivalente ou não. Isso é feito verificando se os dois valores estão em proporção igual ao número total de seus respectivos tokens bloqueados no pool, ou seja, _amountToken1 : totalToken1 :: _amountToken2 : totalToken2 deve manter.
// Adicionando nova liquidez ao pool
// Retorna o montante de ações emitidas para o bloqueio de determinados ativos
function provide(uint256 _amountToken1, uint256 _amountToken2) external validAmountCheck(token1Balance, _amountToken1) validAmountCheck(token2Balance, _amountToken2) returns(uint256 share) {
if(totalShares == 0) { // Liquidez Gênesis é emitida 100 Ações
share = 100*PRECISION;
} else{
uint256 share1 = totalShares.mul(_amountToken1).div(totalToken1);
uint256 share2 = totalShares.mul(_amountToken2).div(totalToken2);
require(share1 == share2, "Equivalent value of tokens not provided...");
share = share1;
}
require(share > 0, "Asset value less than threshold for contribution!");
token1Balance[msg.sender] -= _amountToken1;
token2Balance[msg.sender] -= _amountToken2;
totalToken1 += _amountToken1;
totalToken2 += _amountToken2;
K = totalToken1.mul(totalToken2);
totalShares += share;
shares[msg.sender] += share;
}
Observe com atenção a ordem de atualização de saldo que estamos realizando na função acima. Primeiro deduzimos os tokens da conta dos usuários e, na última etapa, atualizamos o saldo de suas ações. Isso é feito para evitar um ataque de reentrância.
As funções fornecidas ajudam o usuário a obter uma estimativa da quantidade do segundo token que eles precisam bloquear para o valor do token fornecido. Aqui, novamente, usamos a proporção _amountToken1 : totalToken1 :: _amountToken2 : totalToken2 para determinar o montante de token1 necessário se desejarmos bloquear determinada quantidade de token2 e vice-versa.
// Retorna o montante do Token1 necessário ao fornecer liquidez com a quantidade do Token2 _amountToken2
function getEquivalentToken1Estimate(uint256 _amountToken2) public view activePool returns(uint256 reqToken1) {
reqToken1 = totalToken1.mul(_amountToken2).div(totalToken2);
}
// Retorna o montante de Token2 necessário ao fornecer liquidez com a quantidade de Token1 _amountToken1
function getEquivalentToken2Estimate(uint256 _amountToken1) public view activePool returns(uint256 reqToken2) {
reqToken2 = totalToken2.mul(_amountToken1).div(totalToken1);
}
Withdraw (Retirar)
Withdraw é utilizado quando um usuário deseja queimar um determinado montante de ações para recuperar seus tokens. Token1 e Token2 são liberados do pool proporcionalmente as ações queimadas em relação ao total de ações emitidas, ou seja, share : totalShare :: amountTokenX : totalTokenX.
// Retorna a estimativa de Token1 e Token2 que serão liberados na queima de _share
function getWithdrawEstimate(uint256 _share) public view activePool returns(uint256 amountToken1, uint256 amountToken2) {
require(_share <= totalShares, "Share should be less than totalShare");
amountToken1 = _share.mul(totalToken1).div(totalShares);
amountToken2 = _share.mul(totalToken2).div(totalShares);
}
// Remove a liquidez do pool e libera Token1 e Token2 correspondentes ao sacador
function withdraw(uint256 _share) external activePool validAmountCheck(shares, _share) returns(uint256 amountToken1, uint256 amountToken2) {
(amountToken1, amountToken2) = getWithdrawEstimate(_share);
shares[msg.sender] -= _share;
totalShares -= _share;
totalToken1 -= amountToken1;
totalToken2 -= amountToken2;
K = totalToken1.mul(totalToken2);
token1Balance[msg.sender] += amountToken1;
token2Balance[msg.sender] += amountToken2;
}
Swap (Trocar)
Para trocar de Token1 para Token2, implementaremos três funções - getSwapToken1Estimate
, getSwapToken1EstimateGivenToken2
e swapToken1
. As duas primeiras funções apenas determinam os valores de troca para fins de estimativa enquanto a última faz a conversão.
getSwapToken1Estimate
retorna o montante de token2 que o usuário receberá ao depositar um determinado montante de token1. O montante de token2 é obtido a partir da equação K = totalToken1 * totalToken2 onde o K deve permanecer o mesmo antes/depois da operação. Isso nos dá K = (totalToken1 + amountToken1) * (totalToken2 - amountToken2) e obtemos o valor amountToken2
resolvendo esta equação. Na última linha, estamos garantindo que o pool nunca seja totalmente drenado de nenhum dos lados, o que tornaria a equação indefinida.
// Retorna o montante de Token2 que o usuário receberá ao trocar um determinado montante de Token1 por Token2
function getSwapToken1Estimate(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {
uint256 token1After = totalToken1.add(_amountToken1);
uint256 token2After = K.div(token1After);
amountToken2 = totalToken2.sub(token2After);
// Para garantir que o pool do Token2 não se esgote completamente, levando à proporção inf:0
if(amountToken2 == totalToken2) amountToken2--;
}
getSwapToken1EstimateGivenToken2
retorna o montante de token1 que o usuário deve depositar para obter uma determinada quantidade de token2. O montante de token1 é obtido de forma semelhante resolvendo a seguinte equação K = (totalToken1 + amountToken1) * (totalToken2 -mountToken2).
// Retorna o montante de Token1 que o usuário deve trocar para obter _amountToken2 em troca
function getSwapToken1EstimateGivenToken2(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {
require(_amountToken2 < totalToken2, "Insufficient pool balance");
uint256 token2After = totalToken2.sub(_amountToken2);
uint256 token1After = K.div(token2After);
amountToken1 = token1After.sub(totalToken1);
}
swapToken1
na verdade troca o valor em vez de apenas dar uma estimativa.
// Troca o montante de Token1 por Token2 usando a determinação de preço algorítmico
function swapToken1(uint256 _amountToken1) external activePool validAmountCheck(token1Balance, _amountToken1) returns(uint256 amountToken2) {
amountToken2 = getSwapToken1Estimate(_amountToken1);
token1Balance[msg.sender] -= _amountToken1;
totalToken1 += _amountToken1;
totalToken2 -= amountToken2;
token2Balance[msg.sender] += amountToken2;
}
Da mesma forma, para a troca do Token2 para o Token1, implementamos as três funções - getSwapToken2Estimate
, getSwapToken2EstimateGivenToken1
e swapToken2
conforme abaixo.
// Retorna o montante de Token2 que o usuário receberá ao trocar um determinado montante de Token1 por Token2
function getSwapToken2Estimate(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {
uint256 token2After = totalToken2.add(_amountToken2);
uint256 token1After = K.div(token2After);
amountToken1 = totalToken1.sub(token1After);
// Para garantir que o pool do Token1 não se esgote completamente, levando à proporção inf:0
if(amountToken1 == totalToken1) amountToken1--;
}
// Retorna o montante de Token2 que o usuário deve trocar para obter _amountToken1 em troca
function getSwapToken2EstimateGivenToken1(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {
require(_amountToken1 < totalToken1, "Insufficient pool balance");
uint256 token1After = totalToken1.sub(_amountToken1);
uint256 token2After = K.div(token1After);
amountToken2 = token2After.sub(totalToken2);
}
// Troca o montante de Token2 por Token1 usando a determinação de preço algorítmico
function swapToken2(uint256 _amountToken2) external activePool validAmountCheck(token2Balance, _amountToken2) returns(uint256 amountToken1) {
amountToken1 = getSwapToken2Estimate(_amountToken2);
token2Balance[msg.sender] -= _amountToken2;
totalToken2 += _amountToken2;
totalToken1 -= amountToken1;
token1Balance[msg.sender] += amountToken1;
}
Isso conclui a parte de implementação do contrato inteligente. Agora vamos implantá-lo na rede de teste Fuji C-Chain.
Implantando o contrato inteligente
Configurando a Metamask
Faça login na MetaMask, então clique no menu suspenso Network (Rede), depois selecione Custom RPC (RPC personalizado)
Configurações da rede de teste (Testnet) FUJI:
- Nome da Rede: Avalanche FUJI C-Chain
- Novo URL da RPC: https://api.avax-test.network/ext/bc/C/rpc
- ID da Cadeia:
43113
- Símbolo:
C-AVAX
- Explorer: https://cchain.explorer.avax-test.network
Financie seu endereço a partir da torneira indicada.
Implantar usando o Remix
Abra o Remix -> Selecione Solidity
Crie um arquivo AMM.sol
no explorador de arquivos Remix e cole o seguinte código:
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract AMM {
using SafeMath for uint256;
uint256 totalShares; // Armazena o montante total de ações emitidas para o pool
uint256 totalToken1; // Armazena o montante de Token1 bloqueado no pool
uint256 totalToken2; // Armazena o montante de Token2 bloqueado no pool
uint256 K; // Constante algorítmica usada para determinar o preço
uint256 constant PRECISION = 1_000_000; // Precisão de 6 dígitos
mapping(address => uint256) shares; // Armazena a participação acionária de cada provedor
// Armazena o saldo disponível de usuários fora do AMM
// Para simplificar, estamos mantendo nosso próprio
// mapeamento interno de saldo em vez de lidar com tokens ERC-20
mapping(address => uint256) token1Balance;
mapping(address => uint256) token2Balance;
// Garante que o _qty não é zero e que o usuário tem saldo suficiente
modifier validAmountCheck(mapping(address => uint256) storage _balance, uint256 _qty) {
require(_qty > 0, "Amount cannot be zero!");
require(_qty <= _balance[msg.sender], "Insufficient amount");
_;
}
// Restrições de retirada, recurso de troca até que a liquidez seja adicionada ao pool
modifier activePool() {
require(totalShares > 0, "Zero Liquidity");
_;
}
// Envia token(s) gratuito(s) ao invocador
function faucet(uint256 _amountToken1, uint256 _amountToken2) external {
token1Balance[msg.sender] = token1Balance[msg.sender].add(_amountToken1);
token2Balance[msg.sender] = token2Balance[msg.sender].add(_amountToken2);
}
// Retorna o saldo do usuário
function getMyHoldings() external view returns(uint256 amountToken1, uint256 amountToken2, uint256 myShare) {
amountToken1 = token1Balance[msg.sender];
amountToken2 = token2Balance[msg.sender];
myShare = shares[msg.sender];
}
// Retorna o montante total de tokens bloqueados no pool e o total de ações emitidas correspondentes a ele
function getPoolDetails() external view returns(uint256, uint256, uint256) {
return (totalToken1, totalToken2, totalShares);
}
// Retorna o montante de Token1 necessário ao fornecer liquidez com o montante de Token2 _amountToken2
function getEquivalentToken1Estimate(uint256 _amountToken2) public view activePool returns(uint256 reqToken1) {
reqToken1 = totalToken1.mul(_amountToken2).div(totalToken2);
}
// Retorna o montante de Token2 necessário ao fornecer liquidez com o montante de Token1 _amountToken1
function getEquivalentToken2Estimate(uint256 _amountToken1) public view activePool returns(uint256 reqToken2) {
reqToken2 = totalToken2.mul(_amountToken1).div(totalToken1);
}
// Adicionando nova liquidez no pool
// Retorna o montante de ações emitidas para o bloqueio de determinados ativos
function provide(uint256 _amountToken1, uint256 _amountToken2) external validAmountCheck(token1Balance, _amountToken1) validAmountCheck(token2Balance, _amountToken2) returns(uint256 share) {
if(totalShares == 0) { // É emitida 100 ações de liquidez Genesis
share = 100*PRECISION;
} else{
uint256 share1 = totalShares.mul(_amountToken1).div(totalToken1);
uint256 share2 = totalShares.mul(_amountToken2).div(totalToken2);
require(share1 == share2, "Equivalent value of tokens not provided...");
share = share1;
}
require(share > 0, "Asset value less than threshold for contribution!");
token1Balance[msg.sender] -= _amountToken1;
token2Balance[msg.sender] -= _amountToken2;
totalToken1 += _amountToken1;
totalToken2 += _amountToken2;
K = totalToken1.mul(totalToken2);
totalShares += share;
shares[msg.sender] += share;
}
// Retorna a estimativa de Token1 e Token2 que será liberado ao ser queimado dado _share
function getWithdrawEstimate(uint256 _share) public view activePool returns(uint256 amountToken1, uint256 amountToken2) {
require(_share <= totalShares, "Share should be less than totalShare");
amountToken1 = _share.mul(totalToken1).div(totalShares);
amountToken2 = _share.mul(totalToken2).div(totalShares);
}
// Remove liquidez do pool e libera o Token1 e Token2 correspondente para o sacador
function withdraw(uint256 _share) external activePool validAmountCheck(shares, _share) returns(uint256 amountToken1, uint256 amountToken2) {
(amountToken1, amountToken2) = getWithdrawEstimate(_share);
shares[msg.sender] -= _share;
totalShares -= _share;
totalToken1 -= amountToken1;
totalToken2 -= amountToken2;
K = totalToken1.mul(totalToken2);
token1Balance[msg.sender] += amountToken1;
token2Balance[msg.sender] += amountToken2;
}
// Retorna o montante de Token2 que o usuário receberá ao trocar um determinado montante de Token1 por Token2
function getSwapToken1Estimate(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {
uint256 token1After = totalToken1.add(_amountToken1);
uint256 token2After = K.div(token1After);
amountToken2 = totalToken2.sub(token2After);
// Para garantir que o pool do Token2 não se esgote completamente, levando à proporção inf:0
if(amountToken2 == totalToken2) amountToken2--;
}
// Retorna o montante de Token1 que o usuário deve trocar para obter _amountToken2 em troca
function getSwapToken1EstimateGivenToken2(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {
require(_amountToken2 < totalToken2, "Insufficient pool balance");
uint256 token2After = totalToken2.sub(_amountToken2);
uint256 token1After = K.div(token2After);
amountToken1 = token1After.sub(totalToken1);
}
// Troca o montante de Token1 por Token2 usando a determinação de preço algorítmico
function swapToken1(uint256 _amountToken1) external activePool validAmountCheck(token1Balance, _amountToken1) returns(uint256 amountToken2) {
amountToken2 = getSwapToken1Estimate(_amountToken1);
token1Balance[msg.sender] -= _amountToken1;
totalToken1 += _amountToken1;
totalToken2 -= amountToken2;
token2Balance[msg.sender] += amountToken2;
}
// Retorna o montante de Token2 que o usuário receberá ao trocar um determinado montante de Token1 por Token2
function getSwapToken2Estimate(uint256 _amountToken2) public view activePool returns(uint256 amountToken1) {
uint256 token2After = totalToken2.add(_amountToken2);
uint256 token1After = K.div(token2After);
amountToken1 = totalToken1.sub(token1After);
// Para garantir que o pool do Token1 não se esgote completamente, levando à proporção inf:0
if(amountToken1 == totalToken1) amountToken1--;
}
// Retorna o montante de Token2 que o usuário deve trocar para obter _amountToken1 em troca
function getSwapToken2EstimateGivenToken1(uint256 _amountToken1) public view activePool returns(uint256 amountToken2) {
require(_amountToken1 < totalToken1, "Insufficient pool balance");
uint256 token1After = totalToken1.sub(_amountToken1);
uint256 token2After = K.div(token1After);
amountToken2 = token2After.sub(totalToken2);
}
// Troca o montante de Token2 por Token1 usando a determinação de preço algorítmico
function swapToken2(uint256 _amountToken2) external activePool validAmountCheck(token2Balance, _amountToken2) returns(uint256 amountToken1) {
amountToken1 = getSwapToken2Estimate(_amountToken2);
token2Balance[msg.sender] -= _amountToken2;
totalToken2 += _amountToken2;
totalToken1 -= amountToken1;
token1Balance[msg.sender] += amountToken1;
}
}
Navegue até a guia do compilador Solidity na barra de navegação do lado esquerdo e clique no botão azul para compilar o contrato AMM.sol
. Anote o ABI
que será necessário na próxima seção.
Navegue até a guia Deploy e abra o menu suspenso "ENVIRONMENT". Selecione "Injected Web3" (certifique-se de que a Metamask esteja carregada) e clique no botão "Deploy".
Aprove a transação na interface pop-up da Metamask. Depois que nosso contrato for implantado com sucesso, anote o contract address
.
Uma interface binária de aplicação (ABI) é um objeto JSON que armazena os metadados sobre os métodos de um contrato, como tipo de dados de parâmetros de entrada, tipo de dados de retorno e propriedade do método, como pagável, exibição, puro, etc. Você pode aprender mais sobre a ABI na documentação do Solidity.
Criando um front-end no React
Agora, vamos criar um aplicativo React e configurar o front-end do aplicativo. No frontend, representamos token1 e token2 como KAR e KOTHI, respectivamente.
Abra um terminal e navegue até o diretório onde criaremos o aplicativo.
cd /path/to/directory
Agora clone o repositório github, vá para o novo diretório avalance-amm
e instale todas as dependências.
git clone https://github.com/SayanKar/avalanche-amm.git
cd avalanche-amm
npm install
Em nosso aplicativo React, mantemos todos os componentes React no diretório src/components
.
- BoxTemplate
Ele renderiza a caixa contendo o campo de entrada, seu cabeçalho e o elemento à direita da caixa, que pode ser um nome de token, um botão ou estar vazio.
- FaucetComponent
Toma a quantidade de token1 (KAR) e token2 (KOTHI) como entrada e financia o endereço do usuário com essa quantia.
- ProvideComponent
Pega o valor de um token (KAR ou KOTHI), preenche o valor estimado do outro token e ajuda a fornecer liquidez ao pool.
- SwapComponent
Ajuda a trocar um token por outro. Ele pega a quantidade de token no campo de entrada From e estima a quantidade de token no campo de entrada To e vice-versa.
- WithdrawComponent
Ajuda a retirar a ação que se tem. Também permite retirar ao seu limite máximo.
- ContainerComponent
Este componente renderiza o corpo principal de nosso aplicativo, que contém a caixa central contendo as guias para alternar entre os quatro componentes: Swap, Provide, Faucet, Withdraw. E também renderiza os detalhes da conta e os detalhes do pool.
Agora é hora de executar nosso aplicativo React. Use o seguinte comando para iniciar o aplicativo React.
npm start
Passo a passo
- Visite http://localhost:3000 para interagir com o AMM.
- Obtendo fundos da torneira para interagir com o AMM
- Adicionando liquidez no pool
- Trocando tokens
- Retirado liquidez do pool
Conclusão
Parabéns! Desenvolvemos com sucesso um modelo AMM funcional onde os usuários podem trocar tokens, fornecer e retirar liquidez. Como próximo passo, você pode brincar com a fórmula de preço, integrar o padrão ERC20, introduzir taxas como um mecanismo de incentivo para provedores ou adicionar proteção contra “escorregão” (slippage) e muito mais...
Solução de problemas
Falha na Transação
-
Verifique se sua conta tem saldo suficiente no fuji block-explorer. Você pode financiar seu endereço na torneira fornecida.
Certifique-se de ter selecionado a conta correta na Metamask se tiver mais de uma conta conectada ao site.
Sobre os autores)
O tutorial foi criado por Sayan Kar, Yash Kothari e Nimish Agrawal e traduzido por Marcelo Panegali. Você pode contatá-los no Forum Figment para qualquer consulta sobre o tutorial.
Oldest comments (0)