Cómo Hice Una Interfaz Uniswap Desde Cero

Con el objetivo de permitir a los usuarios intercambiar fácilmente entre diferentes monedas e implementar o eliminar liquidez en la red de Autonity con una interfaz de usuario, en lugar de usar scripts, decidí crear una interfaz para los contratos de Uniswap implementados en la red.

La interfaz oficial de Uniswap resultó difícil de bifurcar para la red privada, debido a su base de código muy expansiva y problemas para conectarse a una billetera. Como quería tener una base de código más pequeña que entendiera de arriba a abajo, decidí escribir mi propia aplicación más simple, con la ayuda de mi increíble pasante Matt .

Utilizamos ReactJS para el proyecto, con el módulo EthersJS para conectarse a la cadena de bloques a través de metamask en el navegador y Material-UI para la interfaz. Como era un sitio estático, usamos páginas de github para alojar la aplicación.

Este primer blog describe el código para la parte de intercambio de la aplicación. Primero repasaré las funciones necesarias para conectarse y realizar llamadas y transacciones con el backend de la cadena de bloques de Ethereum y, en segundo lugar, explicaré el frontend de React, que hace un uso extensivo de los ganchos de React y Material-UI. Esto está escrito con la expectativa de que el lector ya tenga una buena comprensión de ReactJS, así como un conocimiento de cómo funcionan los creadores de mercado automatizados (AMM) Uniswap V2 (consulte este artículo ). No explicaré los componentes genéricos de React, como la barra de navegación, o la página de redirección para cuando no se pueda encontrar una billetera Ethereum, ¡ya que este blog será lo suficientemente largo sin eso!

Consulte la interfaz aquí y el código sin formato en el repositorio aquí .

Tenga en cuenta que este blog describe el estado de la interfaz a partir de este compromiso . Desde entonces, se actualizó para tener soporte para múltiples redes y muestra diferentes monedas predeterminadas según la red, así como algunos otros pequeños cambios.

Funciones de Ethereum

Conexión a la cadena de bloques

Estas funciones son necesarias para conectarse a la cadena de bloques a través de una billetera en el navegador, para obtener información de la cadena de bloques a través de llamadas y para usar contratos en la red para realizar intercambios a través de transacciones.

Mi plan original era usar el módulo de javascript Web3 para conectarme a la cadena de bloques con metamask en el navegador, pero metamask dejó de admitirlo en enero de 2021, así que en su lugar usé EthersJS. EthersJS es un módulo mucho más pequeño que Web3, lo que significa que su aplicación se cargará más rápido.

En el archivo ethereumFunctions.js, la primera función es getProvider, que se conecta al proveedor de Ethereum (metamask u otra billetera) en el navegador, luego la función getSigner, que se utiliza para firmar transacciones. Usando la clase de contrato EthersJS, hay funciones que devuelven objetos de contrato para los contratos de enrutador, Weth y fábrica que había implementado en la cadena de bloques anteriormente. Para estos, la clase Contract tomó como parámetros la dirección, ABI del contrato inteligente implementado y el firmante EthersJS.

También se define la función getAccount, que solicita al usuario que seleccione cuentas para usar desde la billetera conectada.

EthereumFunctions.js

import { Contract, ethers } from "ethers";
import * as COINS from "./constants/coins";

const ROUTER = require("./build/UniswapV2Router02.json");
const ERC20 = require("./build/ERC20.json");
const FACTORY = require("./build/IUniswapV2Factory.json");
const PAIR = require("./build/IUniswapV2Pair.json");

export function getProvider() {
  return new ethers.providers.Web3Provider(window.ethereum);
}

export function getSigner(provider) {
  return provider.getSigner();
}

export function getRouter(address, signer) {
  return new Contract(address, ROUTER.abi, signer);
}

export function getWeth(address, signer) {
  return new Contract(address, ERC20.abi, signer);
}

export function getFactory(address, signer) {
  return new Contract(address, FACTORY.abi, signer);
}

export async function getAccount() {
  const accounts = await window.ethereum.request({
    method: "eth_requestAccounts",
  });

  return accounts[0];
}

Todas estas funciones se importaron al CoinSwapper/CoinSwapper.jsarchivo y se usaron para establecer sus variables de estado correspondientes usando React.useStateganchos, desde la línea 70:

CoinSwapper/CoinSwapper.js

const [provider, setProvider] = React.useState(getProvider());
const [signer, setSigner] = React.useState(getSigner(provider));
const [account, setAccount] = React.useState(undefined); // This is populated in a react hook
const [router, setRouter] = React.useState(
getRouter("0x4489D87C8440B19f11d63FA2246f943F492F3F5F", signer)
);
const [weth, setWeth] = React.useState(
getWeth("0x3f0D1FAA13cbE43D662a37690f0e8027f9D89eBF", signer)
);
const [factory, setFactory] = React.useState(
getFactory("0x4EDFE8706Cefab9DCd52630adFFd00E9b93FF116", signer)
);

Funciones del token ERC20

Las siguientes dos funciones en el ethereumFunctions.jsarchivo hacen llamadas a la red para obtener información sobre las direcciones de token elegidas o proporcionadas por el usuario.

doesTokenExisthace una verificación para asegurarse de que la dirección proporcionada corresponda a un token implementado en la cadena de bloques:

EthereumFunctions.js

export function doesTokenExist(address, signer) {
  try {
    return new Contract(address, ERC20.abi, signer);
  } catch (err) {
    return false;
  }
}

getBalanceAndSymbolcomprueba si una dirección proporcionada es la dirección del contrato de Weth en la cadena de bloques, en cuyo caso devuelve el saldo de AUT (la moneda nativa de la cadena de bloques de Autonity, equivalente a ETH) del usuario. De lo contrario, devuelve el saldo del token ERC20 del usuario, junto con el símbolo del token:

export async function getBalanceAndSymbol(
  accountAddress,
  address,
  provider,
  signer
) {
  try {
    if (address === COINS.AUTONITY.address) {
      const balanceRaw = await provider.getBalance(accountAddress);
return {
        balance: ethers.utils.formatEther(balanceRaw),
        symbol: COINS.AUTONITY.abbr,
      };
    } else {
      const token = new Contract(address, ERC20.abi, signer);
      const balanceRaw = await token.balanceOf(accountAddress);
      const symbol = await token.symbol();
return {
        balance: ethers.utils.formatEther(balanceRaw),
        symbol: symbol,
      };
    }
  } catch (err) {
    return false;
  }
}

Esta función verifica si la dirección es la dirección Weth comparándola con COINS.AUTONITY.addressuna matriz de tokens predeterminados exportados desde constants/coins.js.

La función de intercambio

La swapTokensfunción en ethereumFunctions.jsrealiza una transacción al contrato del enrutador en la cadena de bloques para realizar uno de los tres intercambios diferentes (AUT a token, token a AUT, token a token), según las direcciones de token que se le proporcionen, address1y address2.

  • Si address1es la dirección del contrato Weth, llama a la función RouterswapExactETHForTokens
  • Si address2es la dirección del contrato Weth, llama a la función RouterswapExactTokensForETH
  • Si ni address1o address2es la dirección del contrato Weth, la swapTokensfunción llama a la función de enrutadorswapExactTokensForTokens
export async function swapTokens(
  address1,
  address2,
  amount,
  routerContract,
  accountAddress,
  signer
) {
  const tokens = [address1, address2];
  const time = Math.floor(Date.now() / 1000) + 200000;
  const deadline = ethers.BigNumber.from(time);
const amountIn = ethers.utils.parseEther(amount.toString());
  const amountOut = await routerContract.callStatic.getAmountsOut(
    amountIn,
    tokens
  );
const token1 = new Contract(address1, ERC20.abi, signer);
  await token1.approve(routerContract.address, amountIn);
if (address1 === COINS.AUTONITY.address) {
    // Eth -> Token
    await routerContract.swapExactETHForTokens(
      amountOut[1],
      tokens,
      accountAddress,
      deadline,
      { value: amountIn }
    );
  } else if (address2 === COINS.AUTONITY.address) {
    // Token -> Eth
    await routerContract.swapExactTokensForETH(
      amountIn,
      amountOut[1],
      tokens,
      accountAddress,
      deadline
    );
  } else {
    await routerContract.swapExactTokensForTokens(
      amountIn,
      amountOut[1],
      tokens,
      accountAddress,
      deadline
    );
  }
}

La función getAmountOutse utiliza para obtener una vista previa de un intercambio. Llama a la función del enrutador getAmountsOutcon la cantidad del primer token y una matriz de las direcciones de los tokens que se intercambiarán como parámetros. Devuelve la cantidad del segundo token.

export async function getAmountOut(
  address1,
  address2,
  amountIn,
  routerContract
) {
  try {
    const values_out = await routerContract.getAmountsOut(
      ethers.utils.parseEther(amountIn),
      [address1, address2]
    );
    const amount_out = ethers.utils.formatEther(values_out[1]);
    return Number(amount_out);
  } catch {
    return false;
  }
}

La funcion de reservas

Finalmente, la GetReservesfunción en ethereumFunctions.jsdevuelve las reservas del fondo de liquidez para un par de tokens dado, así como el saldo de token de liquidez para el usuario. Internamente, esta función llama a otra función fetchReserves, que obtiene las reservas haciendo una llamada al contrato de par y luego asegurándose de que las reservas se devuelvan en el orden correcto.

export async function fetchReserves(address1, address2, pair) {
  try {
    const reservesRaw = await pair.getReserves();
    let results = [
      Number(ethers.utils.formatEther(reservesRaw[0])),
      Number(ethers.utils.formatEther(reservesRaw[1])),
    ];

    return [
      (await pair.token0()) === address1 ? results[0] : results[1],
      (await pair.token1()) === address2 ? results[1] : results[0],
    ];
  } catch (err) {
    console.log("no reserves yet");
    return [0, 0];
  }
}
export async function getReserves(
  address1,
  address2,
  factory,
  signer,
  accountAddress
) {
  const pairAddress = await factory.getPair(address1, address2);
  const pair = new Contract(pairAddress, PAIR.abi, signer);

  const reservesRaw = await fetchReserves(address1, address2, pair);
  const liquidityTokens_BN = await pair.balanceOf(accountAddress);
  const liquidityTokens = Number(
    ethers.utils.formatEther(liquidityTokens_BN)
  ).toFixed(2);

  return [
    reservesRaw[0].toFixed(2),
    reservesRaw[1].toFixed(2),
    liquidityTokens,
  ];
}

interfaz de reacción

La interfaz de la aplicación hace un uso extensivo de los componentes Material-UI como Grid, Container, Paper, Typography, así como varios botones y más. En lugar de explicar el funcionamiento de cada componente de la aplicación, intentaré ofrecer una descripción general de alto nivel, que tendrá más sentido si está leyendo el código al mismo tiempo. Si no está familiarizado con Material-UI, le recomiendo leer algunos de sus excelentes documentos aquí .

El archivo CoinSwapper.jsexporta una función CoinSwapper, que devuelve el componente React utilizado para seleccionar tokens y realizar intercambios. Esta función en sí misma hace uso de algunos otros componentes personalizados de React, que explicaré primero antes de analizar las funciones internas y los ganchos que hacen que el CoinSwappercomponente funcione.

Componente de diálogo de moneda

El CoinDialogcomponente presenta el menú de monedas que se abre al hacer clic en uno de los botones 'SELECCIONAR'. Esto amplía el Dialogcomponente React, que muestra una ventana que se abre frente al resto de la aplicación, que se utiliza para solicitar una decisión. se define enCoinSwapper/CoinDialog.js

Dentro del CoinDialogcomponente, primero hay una versión modificada del DialogTitlecomponente de Material-UI, con la adición de un botón de cierre que, al hacer clic, llama a la exitfunción que cierra CoinDialog.

A continuación, hay un TextFieldcomponente React, que permite al usuario pegar la dirección de un token que se utilizará. Al cambiar, esto establece la variable addressde estado en la entrada del usuario.

La siguiente parte es una asignación que asigna cada uno de los tokens predeterminados Constants/coinsa un CoinButtoncomponente personalizado (ver más abajo), que cuando se hace clic llama a la exitfunción que cierra y CoinDialogdevuelve la dirección de los tokens seleccionados.

Finalmente, está el Enterbotón, que al hacer clic llama a la submitfunción, que verifica que existe un token con la dirección en la TextFieldfunción Ethereum doesTokenExist, antes de llamar exitpara cerrar y CoinDialogdevolver la dirección.

CoinSwapper/CoinDialog.js

const submit = () => {
    if (doesTokenExist(address, signer)) {
      exit(address);
    } else {
      setError("This address is not valid");
    }
};
// Resets any fields in the dialog (in case it's opened in the future) and calls the `onClose` prop
const exit = (value) => {
    setError("");
    setAddress("");
    onClose(value);
};
return (
    <Dialog
      open={open}
      onClose={() => exit(undefined)}
      fullWidth
      maxWidth="sm"
      classes={{ paper: classes.dialogContainer }}
    >
      <DialogTitle onClose={() => exit(undefined)}>Select Coin</DialogTitle>
<hr className={classes.hr} />
<div className={classes.coinContainer}>
        <Grid container direction="column" spacing={1} alignContent="center">
          <TextField
            value={address}
            onChange={(e) => setAddress(e.target.value)}
            variant="outlined"
            placeholder="Paste Address"
            error={error !== ""}
            helperText={error}
            fullWidth
            className={classes.address}
          />
<hr className={classes.hr} />
<Grid item className={classes.coinList}>
            <Grid container direction="column">
              {/* Maps all of the tokens in the constants file to buttons */}
              {coins.map((coin, index) => (
                <Grid item key={index} xs={12}>
                  <CoinButton
                    coinName={coin.name}
                    coinAbbr={coin.abbr}
                    onClick={() => exit(coin.address)}
                  />
                </Grid>
              ))}
            </Grid>
          </Grid>
        </Grid>
      </div>
<hr className={classes.hr} />
<DialogActions>
        <Button autoFocus onClick={submit} color="primary">
          Enter
        </Button>
      </DialogActions>
    </Dialog>
);

Este componente, al igual que todos los demás, utiliza la makeStylesfunción Material-UI para el estilo, una solución CSS en JS que es fácil de usar con otros componentes de Material-UI y permite anidar temas y estilos dinámicos.

Componente de botón de moneda

El CoinButtoncomponente, definido en CoinSwapper/CoinButton.js, amplía el componente Material-UI ButtonBase, que contiene el texto del símbolo del token (coinAbbr) y el nombre del token (coinName), que se pasan a través de accesorios.

CoinSwapper/CoinButton.js

export default function CoinButton(props) {
    const {coinName, coinAbbr, onClick, ...other} = props;
    const classes = useStyles();
return (
        <ButtonBase
            focusRipple
            className={classes.button}
            onClick={onClick}
        >
            <Grid container direction="column">
                <Typography variant="h6">{coinAbbr}</Typography>
                <Typography variant="body2" className={classes.coinName}>{coinName}</Typography>
            </Grid>
        </ButtonBase>
    )
}

Componente de campo de monedas

El CoinFieldcomponente, definido en CoinSwapper/CoinField, representa la barra de entrada con un botón "SELECCIONAR" que se usa para seleccionar cada token e ingresar una cantidad para el intercambio. El componente es relativamente simple y consta de componentes Material-UI Fab(botón de acción flotante) y InputBase(un campo de texto), ambos envueltos en Gridcomponentes para el espaciado. Las propiedades relativas de los componentes Faby InputBasese pasan al CoinFieldcomponente a través de props.

CoinSwapper/CoinField.js

const classes = useStyles();
const { onClick, symbol, value, onChange, activeField } = props;
return (
    <div className={classes.container}>
      <Grid
        container
        direction="row"
        justifyContent="space-between"
        alignItems="center"
        className={classes.grid}
      >
        {/* Button */}
        <Grid item xs={3}>
          <Fab
            size="small"
            variant="extended"
            onClick={onClick}
            className={classes.fab}
          >
            {symbol}
            <ExpandMoreIcon />
          </Fab>
        </Grid>
{/* Text Field */}
        <Grid item xs={9}>
          <InputBase
            value={value}
            onChange={onChange}
            placeholder="0.0"
            disabled={!activeField}
            classes={{ root: classes.input, input: classes.inputBase }}
          />
        </Grid>
      </Grid>
    </div>
);

Botón de carga

El LoadingButtoncomponente, definido en Components/LoadingButton.js, muestra el botón 'CAMBIAR' en la parte inferior del CoinSwapper componente principal. Extiende el Buttoncomponente Material-UI, por lo que se deshabilitará según la validpropiedad, y muestra un ícono de carga giratorio al hacer clic hasta que se complete la transacción de intercambio.

Componentes/LoadingButton.js

export default function LoadingButton(props) {
  const classes = useStyles();
  const { children, loading, valid, success, fail, onClick, ...other } = props;
  return (
    <div className={classes.wrapper}>
      <Button
        variant="contained"
        color="primary"
        fullWidth
        disabled={loading || !valid}
        type="submit"
        onClick={onClick}
        {...other}
      >
        {children}
      </Button>
      {loading && <CircularProgress size={24} className={classes.progress} />}
    </div>
  );
}

Función de intercambio de monedas

La declaración de devolución

La función principal de la aplicación, CoinSwapper, CoinSwapper/CoinSwapper.jsdevuelve un componente en caja que contiene dos CoinDialogcomponentes, que solo se abren cuando CoinFieldse selecciona uno de los dos componentes. A continuación, hay Typographycomponentes Material-UI que muestran los saldos y las reservas de los dos tokens seleccionados.

Finalmente, está el LoadingButtoncomponente en la parte inferior y un breve párrafo instructivo con un enlace al grifo AUT.

CoinSwapper/CoinSwapper.js

return (
    <div>
      {/* Dialog Windows */}
      <CoinDialog
        open={dialog1Open}
        onClose={onToken1Selected}
        coins={COINS.ALL}
        signer={signer}
      />
      <CoinDialog
        open={dialog2Open}
        onClose={onToken2Selected}
        coins={COINS.ALL}
        signer={signer}
      />
{/* Coin Swapper */}
      <Container maxWidth="xs">
        <Paper className={classes.paperContainer}>
          <Typography variant="h5" className={classes.title}>
            Swap Coins
          </Typography>
<Grid container direction="column" alignItems="center" spacing={2}>
            <Grid item xs={12} className={classes.fullWidth}>
              <CoinField
                activeField={true}
                value={field1Value}
                onClick={() => setDialog1Open(true)}
                onChange={handleChange.field1}
                symbol={
                  coin1.symbol !== undefined ? coin1.symbol : "Select"
                }
              />
            </Grid>
<IconButton onClick={switchFields} className={classes.switchButton}>
              <SwapVerticalCircleIcon fontSize="medium" />
            </IconButton>
<Grid item xs={12} className={classes.fullWidth}>
              <CoinField
                activeField={false}
                value={field2Value}
                onClick={() => setDialog2Open(true)}
                symbol={
                  coin2.symbol !== undefined ? coin2.symbol : "Select"
                }
              />
            </Grid>
<hr className={classes.hr} />
{/* Balance Display */}
            <Typography variant="h6">Your Balances</Typography>
            <Grid container direction="row" justifyContent="space-between">
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatBalance(coin1.balance, coin1.symbol)}
                </Typography>
              </Grid>
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatBalance(coin2.balance, coin2.symbol)}
                </Typography>
              </Grid>
            </Grid>
<hr className={classes.hr} />
{/* Reserves Display */}
            <Typography variant="h6">Reserves</Typography>
            <Grid container direction="row" justifyContent="space-between">
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatReserve(reserves[0], coin1.symbol)}
                </Typography>
              </Grid>
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatReserve(reserves[1], coin2.symbol)}
                </Typography>
              </Grid>
            </Grid>
<hr className={classes.hr} />
<LoadingButton
              loading={loading}
              valid={isButtonEnabled()}
              success={false}
              fail={false}
              onClick={swap}
            >
              <LoopIcon />
              Swap
            </LoadingButton>
          </Grid>
        </Paper>
      </Container>
<Grid
        container
        className={classes.footer}
        direction="row"
        justifyContent="center"
        alignItems="flex-end"
      >
        <p>
          Clearmatics Autonity Uniswap | Get AUT for use in the bakerloo testnet{" "}
          <a href="https://faucet.bakerloo.autonity.network/">here</a>
        </p>
      </Grid>
    </div>
);

Cuando LoadingButtonse hace clic en , llama a la función interna swap(ver más abajo), que luego llama a la swapTokensfunción Ethereum con las variables coin1.addressde estado coin2.addressy field1Valuecomo argumentos. Esto primero solicitará al usuario que permita el gasto de la moneda 1 con una notificación de metamáscara, luego, con otra notificación, solicitará al usuario que confirme la transacción. Cuando haya terminado, los valores en field1 y field2 se restablecerán.

Variables de estado

La declaración de retorno anterior hace referencia a varias variables de estado que realizan un seguimiento del estado de la aplicación. Estos son:

  • dialog1Open— Mantiene un registro del clima la primera ventana de diálogo está abierta
  • dialog2Open— Mantiene un registro del clima la primera ventana de diálogo está abierta
  • coin1— matriz de dirección, símbolo, saldo para coin1
  • coin2— matriz de dirección, símbolo, saldo para coin2
  • reserves— Almacena las reservas de liquidez en el pool para coin1 y coin2.
  • field1Value— Almacena la entrada del usuario en el campo 1 (el valor de la moneda 1 que se intercambiará)
  • field2Value— Almacena la entrada del usuario en el campo2 (el valor de la moneda2 que se intercambiará)
  • loading— booleano para controlar el botón de carga

Estos se definen con React.useStateganchos de la línea 83:

CoinSwapper/CoinSwapper.js

// Stores a record of whether their respective dialog window is open
const [dialog1Open, setDialog1Open] = React.useState(false);
const [dialog2Open, setDialog2Open] = React.useState(false);
// Stores data about their respective coin
const [coin1, setCoin1] = React.useState({
address: undefined,
symbol: undefined,
balance: undefined,
});
const [coin2, setCoin2] = React.useState({
address: undefined,
symbol: undefined,
balance: undefined,
});
// Stores the current reserves in the liquidity pool between coin1 and coin2
const [reserves, setReserves] = React.useState(["0.0", "0.0"]);
// Stores the current value of their respective text box
const [field1Value, setField1Value] = React.useState("");
const [field2Value, setField2Value] = React.useState("");
// Controls the loading button
const [loading, setLoading] = React.useState(false);

Estas variables de estado se utilizan en las siguientes funciones y también se utilizan para mostrar información al usuario en la declaración de devolución mencionada anteriormente.

Funciones internas

También se hace referencia en la declaración de devolución anterior a varias funciones internas, estas son:

  • switchFields
  • handleChange
  • formatBalance
  • formatReserve
  • isButtonEnabled
  • onToken1Selected
  • onToken2Selected
  • swap

switchFieldscambia las monedas superior e inferior. Esto se llama cuando los usuarios presionan el botón de intercambio o seleccionan el token opuesto en el cuadro de diálogo (por ejemplo, si coin1 es TokenA y el usuario selecciona TokenB al elegir coin2):

const switchFields = () => {
    setCoin1(coin2);
    setCoin2(coin1);
    setField1Value(field2Value);
    setReserves(reserves.reverse());
};

handleChangees una función interna común utilizada en ReactJS, que toma un evento HTML, extrae los datos y los coloca en una variable de estado. Aquí solía establecer el valor del campo 1 cuando coinFiedcambia el valor en el primero:

const handleChange = {
    field1: (e) => {
      setField1Value(e.target.value);
    },
};

formatBalance/ formatReserveconvertir el saldo / las reservas de la cuenta en algo agradable y legible:

const formatBalance = (balance, symbol) => {
    if (balance && symbol)
      return parseFloat(balance).toPrecision(8) + " " + symbol;
    else return "0.0";
};

isButtonEnableddetermina si el botón debe estar habilitado o no:

const isButtonEnabled = () => {
    let validFloat = new RegExp("^[0-9]*[.,]?[0-9]*$");
// If both coins have been selected, and a valid float has been entered which is less than the user's balance, then return true
    return (
      coin1.address &&
      coin2.address &&
      validFloat.test(field1Value) &&
      parseFloat(field1Value) <= coin1.balance
    );
};

onToken1Selected/ onToken2Selectedse llaman cuando sale la ventana de diálogo para coin1 / coin2, y establece las variables de estado relevantes:

const onToken1Selected = (address) => {
    // Close the dialog window
    setDialog1Open(false);
// If the user inputs the same token, we want to switch the data in the fields
    if (address === coin2.address) {
      switchFields();
    }
    // We only update the values if the user provides a token
    else if (address) {
      // Getting some token data is async, so we need to wait for the data to return, hence the promise
      getBalanceAndSymbol(account, address, provider, signer).then((data) => {
        setCoin1({
          address: address,
          symbol: data.symbol,
          balance: data.balance,
        });
      });
    }
};

swapllama a la swapTokensfunción Ethereum para realizar el intercambio, luego restablece las variables de estado necesarias:

const swap = () => {
    console.log("Attempting to swap tokens...");
    setLoading(true);
swapTokens(
      coin1.address,
      coin2.address,
      parseFloat(field1Value),
      router,
      account,
      signer
    )
      .then(() => {
        setLoading(false);
// If the transaction was successful, we clear to input to make sure the user doesn't accidental redo the transfer
        setField1Value("");
        enqueueSnackbar("Transaction Successful", { variant: "success" });
      })
      .catch((e) => {
        setLoading(false);
        enqueueSnackbar("Transaction Failed (" + e.message + ")", {
          variant: "error",
          autoHideDuration: 10000,
        });
      });
};

La línea 16 en la función de intercambio usa enqueueSnackbar, un componente del módulo de nodo Notistack. Notistack es una gran biblioteca para hacer notificaciones temporales. Consulta el repositorio aquí .

usar Ganchos de efecto

Finalmente, en CoinSwapper/CoinSwapper.js, hay cuatro useEffectganchos, que se utilizan para mantener la aplicación actualizada con los últimos cambios. El lambda (código) dentro de cada enlace se ejecuta cuando cambia una de las dependencias. Las dependencias se definen en la matriz de variables que se pasan a la función después de la expresión lambda.

El primero useEffectse llama cuando alguna de las variables de estado coin1.addresso coin2.addresscambia. Esto significa que cuando el usuario selecciona una moneda diferente para convertir, o se intercambian las monedas, se calcularán las nuevas reservas:

useEffect(() => {
    console.log(
      "Trying to get Reserves between:\n" +
        coin1.address +
        "\n" +
        coin2.address
    );
if (coin1.address && coin2.address) {
      getReserves(
        coin1.address,
        coin2.address,
        factory,
        signer,
        account
      ).then((data) => setReserves(data));
    }
}, [coin1.address, coin2.address, account, factory, router, signer]);

El segundo gancho se llama cuando alguna de las variables de estado field1Value coin1.addresso coin2.addresscambia. Intenta calcular y establecer la variable de estado field2Value. Esto significa que si el usuario ingresa un nuevo valor en el cuadro de conversión o la tasa de conversión cambia, el valor en el cuadro de salida cambiará:

useEffect(() => {
    if (isNaN(parseFloat(field1Value))) {
      setField2Value("");
    } else if (field1Value && coin1.address && coin2.address) {
      getAmountOut(
        coin1.address,
        coin2.address,
        field1Value,
        router
      ).then((amount) => setField2Value(amount.toFixed(7)));
    } else {
      setField2Value("");
    }
}, [field1Value, coin1.address, coin2.address]);

El tercer enlace crea un tiempo de espera que se ejecutará cada ~ 10 segundos, su función es verificar si el saldo del usuario se actualizó y cambió. Esto les permite ver cuándo se completa una transacción mirando la salida del saldo:

useEffect(() => {
    const coinTimeout = setTimeout(() => {
      console.log("Checking balances...");
if (coin1.address && coin2.address && account) {
        getReserves(
          coin1.address,
          coin2.address,
          factory,
          signer,
          account
        ).then((data) => setReserves(data));
      }
if (coin1 && account) {
        getBalanceAndSymbol(account, coin1.address, provider, signer).then(
          (data) => {
            setCoin1({
              ...coin1,
              balance: data.balance,
            });
          }
        );
      }
      if (coin2 && account) {
        getBalanceAndSymbol(account, coin2.address, provider, signer).then(
          (data) => {
            setCoin2({
              ...coin2,
              balance: data.balance,
            });
          }
        );
      }
    }, 10000);
return () => clearTimeout(coinTimeout);
});

El enlace final se ejecutará cuando el componente se monte por primera vez. Se utiliza para configurar la cuenta:

export async function quoteAddLiquidity(
  address1,
  address2,
  amountADesired,
  amountBDesired,
  factory,
  signer
) {
  const pairAddress = await factory.getPair(address1, address2);
  const pair = new Contract(pairAddress, PAIR.abi, signer);

  const reservesRaw = await fetchReserves(address1, address2, pair); // Returns the reserves already formated as ethers
  const reserveA = reservesRaw[0];
  const reserveB = reservesRaw[1];

  if (reserveA === 0 && reserveB === 0) {
    let amountOut = Math.sqrt(reserveA * reserveB);
    return [
      amountADesired.toString(),
      amountBDesired.toString(),
      amountOut.toString(),
    ];
  } else {
    let [amountBOptimal, amountOut] = quote(amountADesired, reserveA, reserveB);
    if (amountBOptimal <= amountBDesired) {
      return [
        amountADesired.toString(),
        amountBOptimal.toString(),
        amountOut.toString(),
      ];
    } else {
      let [amountAOptimal, amountOut] = quote(
        amountBDesired,
        reserveB,
        reserveA
      );
      console.log(amountAOptimal, amountOut);
      return [
        amountAOptimal.toString(),
        amountBDesired.toString(),
        amountOut.toString(),
      ];
    }
  }
}

Conclusión

La primera sección de este blog cubrió las funciones de Ethereum necesarias para realizar llamadas y transacciones a la cadena de bloques. La siguiente sección pasó por los componentes personalizados, las funciones internas y los ganchos necesarios para que la funcionalidad de intercambio principal funcione.

Todavía tengo que mencionar cualquiera de las funcionalidades de eliminación/implementación de liquidez. Ese es el tema del próximo blog, aquí .

Para dar una propina al autor, puede usar esta dirección ethereum: 0xe7EeDB184E63Fe049EebA79EfeAc72939cB1461D 

Esta historia se publicó originalmente en https://medium.com/clearmatics/how-i-made-a-uniswap-interface-from-scratch-b51e1027ca87

#uniswap #ethereum #token #bitcoin 

What is GEEK

Buddha Community

Cómo Hice Una Interfaz Uniswap Desde Cero

Cómo Hice Una Interfaz Uniswap Desde Cero

Con el objetivo de permitir a los usuarios intercambiar fácilmente entre diferentes monedas e implementar o eliminar liquidez en la red de Autonity con una interfaz de usuario, en lugar de usar scripts, decidí crear una interfaz para los contratos de Uniswap implementados en la red.

La interfaz oficial de Uniswap resultó difícil de bifurcar para la red privada, debido a su base de código muy expansiva y problemas para conectarse a una billetera. Como quería tener una base de código más pequeña que entendiera de arriba a abajo, decidí escribir mi propia aplicación más simple, con la ayuda de mi increíble pasante Matt .

Utilizamos ReactJS para el proyecto, con el módulo EthersJS para conectarse a la cadena de bloques a través de metamask en el navegador y Material-UI para la interfaz. Como era un sitio estático, usamos páginas de github para alojar la aplicación.

Este primer blog describe el código para la parte de intercambio de la aplicación. Primero repasaré las funciones necesarias para conectarse y realizar llamadas y transacciones con el backend de la cadena de bloques de Ethereum y, en segundo lugar, explicaré el frontend de React, que hace un uso extensivo de los ganchos de React y Material-UI. Esto está escrito con la expectativa de que el lector ya tenga una buena comprensión de ReactJS, así como un conocimiento de cómo funcionan los creadores de mercado automatizados (AMM) Uniswap V2 (consulte este artículo ). No explicaré los componentes genéricos de React, como la barra de navegación, o la página de redirección para cuando no se pueda encontrar una billetera Ethereum, ¡ya que este blog será lo suficientemente largo sin eso!

Consulte la interfaz aquí y el código sin formato en el repositorio aquí .

Tenga en cuenta que este blog describe el estado de la interfaz a partir de este compromiso . Desde entonces, se actualizó para tener soporte para múltiples redes y muestra diferentes monedas predeterminadas según la red, así como algunos otros pequeños cambios.

Funciones de Ethereum

Conexión a la cadena de bloques

Estas funciones son necesarias para conectarse a la cadena de bloques a través de una billetera en el navegador, para obtener información de la cadena de bloques a través de llamadas y para usar contratos en la red para realizar intercambios a través de transacciones.

Mi plan original era usar el módulo de javascript Web3 para conectarme a la cadena de bloques con metamask en el navegador, pero metamask dejó de admitirlo en enero de 2021, así que en su lugar usé EthersJS. EthersJS es un módulo mucho más pequeño que Web3, lo que significa que su aplicación se cargará más rápido.

En el archivo ethereumFunctions.js, la primera función es getProvider, que se conecta al proveedor de Ethereum (metamask u otra billetera) en el navegador, luego la función getSigner, que se utiliza para firmar transacciones. Usando la clase de contrato EthersJS, hay funciones que devuelven objetos de contrato para los contratos de enrutador, Weth y fábrica que había implementado en la cadena de bloques anteriormente. Para estos, la clase Contract tomó como parámetros la dirección, ABI del contrato inteligente implementado y el firmante EthersJS.

También se define la función getAccount, que solicita al usuario que seleccione cuentas para usar desde la billetera conectada.

EthereumFunctions.js

import { Contract, ethers } from "ethers";
import * as COINS from "./constants/coins";

const ROUTER = require("./build/UniswapV2Router02.json");
const ERC20 = require("./build/ERC20.json");
const FACTORY = require("./build/IUniswapV2Factory.json");
const PAIR = require("./build/IUniswapV2Pair.json");

export function getProvider() {
  return new ethers.providers.Web3Provider(window.ethereum);
}

export function getSigner(provider) {
  return provider.getSigner();
}

export function getRouter(address, signer) {
  return new Contract(address, ROUTER.abi, signer);
}

export function getWeth(address, signer) {
  return new Contract(address, ERC20.abi, signer);
}

export function getFactory(address, signer) {
  return new Contract(address, FACTORY.abi, signer);
}

export async function getAccount() {
  const accounts = await window.ethereum.request({
    method: "eth_requestAccounts",
  });

  return accounts[0];
}

Todas estas funciones se importaron al CoinSwapper/CoinSwapper.jsarchivo y se usaron para establecer sus variables de estado correspondientes usando React.useStateganchos, desde la línea 70:

CoinSwapper/CoinSwapper.js

const [provider, setProvider] = React.useState(getProvider());
const [signer, setSigner] = React.useState(getSigner(provider));
const [account, setAccount] = React.useState(undefined); // This is populated in a react hook
const [router, setRouter] = React.useState(
getRouter("0x4489D87C8440B19f11d63FA2246f943F492F3F5F", signer)
);
const [weth, setWeth] = React.useState(
getWeth("0x3f0D1FAA13cbE43D662a37690f0e8027f9D89eBF", signer)
);
const [factory, setFactory] = React.useState(
getFactory("0x4EDFE8706Cefab9DCd52630adFFd00E9b93FF116", signer)
);

Funciones del token ERC20

Las siguientes dos funciones en el ethereumFunctions.jsarchivo hacen llamadas a la red para obtener información sobre las direcciones de token elegidas o proporcionadas por el usuario.

doesTokenExisthace una verificación para asegurarse de que la dirección proporcionada corresponda a un token implementado en la cadena de bloques:

EthereumFunctions.js

export function doesTokenExist(address, signer) {
  try {
    return new Contract(address, ERC20.abi, signer);
  } catch (err) {
    return false;
  }
}

getBalanceAndSymbolcomprueba si una dirección proporcionada es la dirección del contrato de Weth en la cadena de bloques, en cuyo caso devuelve el saldo de AUT (la moneda nativa de la cadena de bloques de Autonity, equivalente a ETH) del usuario. De lo contrario, devuelve el saldo del token ERC20 del usuario, junto con el símbolo del token:

export async function getBalanceAndSymbol(
  accountAddress,
  address,
  provider,
  signer
) {
  try {
    if (address === COINS.AUTONITY.address) {
      const balanceRaw = await provider.getBalance(accountAddress);
return {
        balance: ethers.utils.formatEther(balanceRaw),
        symbol: COINS.AUTONITY.abbr,
      };
    } else {
      const token = new Contract(address, ERC20.abi, signer);
      const balanceRaw = await token.balanceOf(accountAddress);
      const symbol = await token.symbol();
return {
        balance: ethers.utils.formatEther(balanceRaw),
        symbol: symbol,
      };
    }
  } catch (err) {
    return false;
  }
}

Esta función verifica si la dirección es la dirección Weth comparándola con COINS.AUTONITY.addressuna matriz de tokens predeterminados exportados desde constants/coins.js.

La función de intercambio

La swapTokensfunción en ethereumFunctions.jsrealiza una transacción al contrato del enrutador en la cadena de bloques para realizar uno de los tres intercambios diferentes (AUT a token, token a AUT, token a token), según las direcciones de token que se le proporcionen, address1y address2.

  • Si address1es la dirección del contrato Weth, llama a la función RouterswapExactETHForTokens
  • Si address2es la dirección del contrato Weth, llama a la función RouterswapExactTokensForETH
  • Si ni address1o address2es la dirección del contrato Weth, la swapTokensfunción llama a la función de enrutadorswapExactTokensForTokens
export async function swapTokens(
  address1,
  address2,
  amount,
  routerContract,
  accountAddress,
  signer
) {
  const tokens = [address1, address2];
  const time = Math.floor(Date.now() / 1000) + 200000;
  const deadline = ethers.BigNumber.from(time);
const amountIn = ethers.utils.parseEther(amount.toString());
  const amountOut = await routerContract.callStatic.getAmountsOut(
    amountIn,
    tokens
  );
const token1 = new Contract(address1, ERC20.abi, signer);
  await token1.approve(routerContract.address, amountIn);
if (address1 === COINS.AUTONITY.address) {
    // Eth -> Token
    await routerContract.swapExactETHForTokens(
      amountOut[1],
      tokens,
      accountAddress,
      deadline,
      { value: amountIn }
    );
  } else if (address2 === COINS.AUTONITY.address) {
    // Token -> Eth
    await routerContract.swapExactTokensForETH(
      amountIn,
      amountOut[1],
      tokens,
      accountAddress,
      deadline
    );
  } else {
    await routerContract.swapExactTokensForTokens(
      amountIn,
      amountOut[1],
      tokens,
      accountAddress,
      deadline
    );
  }
}

La función getAmountOutse utiliza para obtener una vista previa de un intercambio. Llama a la función del enrutador getAmountsOutcon la cantidad del primer token y una matriz de las direcciones de los tokens que se intercambiarán como parámetros. Devuelve la cantidad del segundo token.

export async function getAmountOut(
  address1,
  address2,
  amountIn,
  routerContract
) {
  try {
    const values_out = await routerContract.getAmountsOut(
      ethers.utils.parseEther(amountIn),
      [address1, address2]
    );
    const amount_out = ethers.utils.formatEther(values_out[1]);
    return Number(amount_out);
  } catch {
    return false;
  }
}

La funcion de reservas

Finalmente, la GetReservesfunción en ethereumFunctions.jsdevuelve las reservas del fondo de liquidez para un par de tokens dado, así como el saldo de token de liquidez para el usuario. Internamente, esta función llama a otra función fetchReserves, que obtiene las reservas haciendo una llamada al contrato de par y luego asegurándose de que las reservas se devuelvan en el orden correcto.

export async function fetchReserves(address1, address2, pair) {
  try {
    const reservesRaw = await pair.getReserves();
    let results = [
      Number(ethers.utils.formatEther(reservesRaw[0])),
      Number(ethers.utils.formatEther(reservesRaw[1])),
    ];

    return [
      (await pair.token0()) === address1 ? results[0] : results[1],
      (await pair.token1()) === address2 ? results[1] : results[0],
    ];
  } catch (err) {
    console.log("no reserves yet");
    return [0, 0];
  }
}
export async function getReserves(
  address1,
  address2,
  factory,
  signer,
  accountAddress
) {
  const pairAddress = await factory.getPair(address1, address2);
  const pair = new Contract(pairAddress, PAIR.abi, signer);

  const reservesRaw = await fetchReserves(address1, address2, pair);
  const liquidityTokens_BN = await pair.balanceOf(accountAddress);
  const liquidityTokens = Number(
    ethers.utils.formatEther(liquidityTokens_BN)
  ).toFixed(2);

  return [
    reservesRaw[0].toFixed(2),
    reservesRaw[1].toFixed(2),
    liquidityTokens,
  ];
}

interfaz de reacción

La interfaz de la aplicación hace un uso extensivo de los componentes Material-UI como Grid, Container, Paper, Typography, así como varios botones y más. En lugar de explicar el funcionamiento de cada componente de la aplicación, intentaré ofrecer una descripción general de alto nivel, que tendrá más sentido si está leyendo el código al mismo tiempo. Si no está familiarizado con Material-UI, le recomiendo leer algunos de sus excelentes documentos aquí .

El archivo CoinSwapper.jsexporta una función CoinSwapper, que devuelve el componente React utilizado para seleccionar tokens y realizar intercambios. Esta función en sí misma hace uso de algunos otros componentes personalizados de React, que explicaré primero antes de analizar las funciones internas y los ganchos que hacen que el CoinSwappercomponente funcione.

Componente de diálogo de moneda

El CoinDialogcomponente presenta el menú de monedas que se abre al hacer clic en uno de los botones 'SELECCIONAR'. Esto amplía el Dialogcomponente React, que muestra una ventana que se abre frente al resto de la aplicación, que se utiliza para solicitar una decisión. se define enCoinSwapper/CoinDialog.js

Dentro del CoinDialogcomponente, primero hay una versión modificada del DialogTitlecomponente de Material-UI, con la adición de un botón de cierre que, al hacer clic, llama a la exitfunción que cierra CoinDialog.

A continuación, hay un TextFieldcomponente React, que permite al usuario pegar la dirección de un token que se utilizará. Al cambiar, esto establece la variable addressde estado en la entrada del usuario.

La siguiente parte es una asignación que asigna cada uno de los tokens predeterminados Constants/coinsa un CoinButtoncomponente personalizado (ver más abajo), que cuando se hace clic llama a la exitfunción que cierra y CoinDialogdevuelve la dirección de los tokens seleccionados.

Finalmente, está el Enterbotón, que al hacer clic llama a la submitfunción, que verifica que existe un token con la dirección en la TextFieldfunción Ethereum doesTokenExist, antes de llamar exitpara cerrar y CoinDialogdevolver la dirección.

CoinSwapper/CoinDialog.js

const submit = () => {
    if (doesTokenExist(address, signer)) {
      exit(address);
    } else {
      setError("This address is not valid");
    }
};
// Resets any fields in the dialog (in case it's opened in the future) and calls the `onClose` prop
const exit = (value) => {
    setError("");
    setAddress("");
    onClose(value);
};
return (
    <Dialog
      open={open}
      onClose={() => exit(undefined)}
      fullWidth
      maxWidth="sm"
      classes={{ paper: classes.dialogContainer }}
    >
      <DialogTitle onClose={() => exit(undefined)}>Select Coin</DialogTitle>
<hr className={classes.hr} />
<div className={classes.coinContainer}>
        <Grid container direction="column" spacing={1} alignContent="center">
          <TextField
            value={address}
            onChange={(e) => setAddress(e.target.value)}
            variant="outlined"
            placeholder="Paste Address"
            error={error !== ""}
            helperText={error}
            fullWidth
            className={classes.address}
          />
<hr className={classes.hr} />
<Grid item className={classes.coinList}>
            <Grid container direction="column">
              {/* Maps all of the tokens in the constants file to buttons */}
              {coins.map((coin, index) => (
                <Grid item key={index} xs={12}>
                  <CoinButton
                    coinName={coin.name}
                    coinAbbr={coin.abbr}
                    onClick={() => exit(coin.address)}
                  />
                </Grid>
              ))}
            </Grid>
          </Grid>
        </Grid>
      </div>
<hr className={classes.hr} />
<DialogActions>
        <Button autoFocus onClick={submit} color="primary">
          Enter
        </Button>
      </DialogActions>
    </Dialog>
);

Este componente, al igual que todos los demás, utiliza la makeStylesfunción Material-UI para el estilo, una solución CSS en JS que es fácil de usar con otros componentes de Material-UI y permite anidar temas y estilos dinámicos.

Componente de botón de moneda

El CoinButtoncomponente, definido en CoinSwapper/CoinButton.js, amplía el componente Material-UI ButtonBase, que contiene el texto del símbolo del token (coinAbbr) y el nombre del token (coinName), que se pasan a través de accesorios.

CoinSwapper/CoinButton.js

export default function CoinButton(props) {
    const {coinName, coinAbbr, onClick, ...other} = props;
    const classes = useStyles();
return (
        <ButtonBase
            focusRipple
            className={classes.button}
            onClick={onClick}
        >
            <Grid container direction="column">
                <Typography variant="h6">{coinAbbr}</Typography>
                <Typography variant="body2" className={classes.coinName}>{coinName}</Typography>
            </Grid>
        </ButtonBase>
    )
}

Componente de campo de monedas

El CoinFieldcomponente, definido en CoinSwapper/CoinField, representa la barra de entrada con un botón "SELECCIONAR" que se usa para seleccionar cada token e ingresar una cantidad para el intercambio. El componente es relativamente simple y consta de componentes Material-UI Fab(botón de acción flotante) y InputBase(un campo de texto), ambos envueltos en Gridcomponentes para el espaciado. Las propiedades relativas de los componentes Faby InputBasese pasan al CoinFieldcomponente a través de props.

CoinSwapper/CoinField.js

const classes = useStyles();
const { onClick, symbol, value, onChange, activeField } = props;
return (
    <div className={classes.container}>
      <Grid
        container
        direction="row"
        justifyContent="space-between"
        alignItems="center"
        className={classes.grid}
      >
        {/* Button */}
        <Grid item xs={3}>
          <Fab
            size="small"
            variant="extended"
            onClick={onClick}
            className={classes.fab}
          >
            {symbol}
            <ExpandMoreIcon />
          </Fab>
        </Grid>
{/* Text Field */}
        <Grid item xs={9}>
          <InputBase
            value={value}
            onChange={onChange}
            placeholder="0.0"
            disabled={!activeField}
            classes={{ root: classes.input, input: classes.inputBase }}
          />
        </Grid>
      </Grid>
    </div>
);

Botón de carga

El LoadingButtoncomponente, definido en Components/LoadingButton.js, muestra el botón 'CAMBIAR' en la parte inferior del CoinSwapper componente principal. Extiende el Buttoncomponente Material-UI, por lo que se deshabilitará según la validpropiedad, y muestra un ícono de carga giratorio al hacer clic hasta que se complete la transacción de intercambio.

Componentes/LoadingButton.js

export default function LoadingButton(props) {
  const classes = useStyles();
  const { children, loading, valid, success, fail, onClick, ...other } = props;
  return (
    <div className={classes.wrapper}>
      <Button
        variant="contained"
        color="primary"
        fullWidth
        disabled={loading || !valid}
        type="submit"
        onClick={onClick}
        {...other}
      >
        {children}
      </Button>
      {loading && <CircularProgress size={24} className={classes.progress} />}
    </div>
  );
}

Función de intercambio de monedas

La declaración de devolución

La función principal de la aplicación, CoinSwapper, CoinSwapper/CoinSwapper.jsdevuelve un componente en caja que contiene dos CoinDialogcomponentes, que solo se abren cuando CoinFieldse selecciona uno de los dos componentes. A continuación, hay Typographycomponentes Material-UI que muestran los saldos y las reservas de los dos tokens seleccionados.

Finalmente, está el LoadingButtoncomponente en la parte inferior y un breve párrafo instructivo con un enlace al grifo AUT.

CoinSwapper/CoinSwapper.js

return (
    <div>
      {/* Dialog Windows */}
      <CoinDialog
        open={dialog1Open}
        onClose={onToken1Selected}
        coins={COINS.ALL}
        signer={signer}
      />
      <CoinDialog
        open={dialog2Open}
        onClose={onToken2Selected}
        coins={COINS.ALL}
        signer={signer}
      />
{/* Coin Swapper */}
      <Container maxWidth="xs">
        <Paper className={classes.paperContainer}>
          <Typography variant="h5" className={classes.title}>
            Swap Coins
          </Typography>
<Grid container direction="column" alignItems="center" spacing={2}>
            <Grid item xs={12} className={classes.fullWidth}>
              <CoinField
                activeField={true}
                value={field1Value}
                onClick={() => setDialog1Open(true)}
                onChange={handleChange.field1}
                symbol={
                  coin1.symbol !== undefined ? coin1.symbol : "Select"
                }
              />
            </Grid>
<IconButton onClick={switchFields} className={classes.switchButton}>
              <SwapVerticalCircleIcon fontSize="medium" />
            </IconButton>
<Grid item xs={12} className={classes.fullWidth}>
              <CoinField
                activeField={false}
                value={field2Value}
                onClick={() => setDialog2Open(true)}
                symbol={
                  coin2.symbol !== undefined ? coin2.symbol : "Select"
                }
              />
            </Grid>
<hr className={classes.hr} />
{/* Balance Display */}
            <Typography variant="h6">Your Balances</Typography>
            <Grid container direction="row" justifyContent="space-between">
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatBalance(coin1.balance, coin1.symbol)}
                </Typography>
              </Grid>
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatBalance(coin2.balance, coin2.symbol)}
                </Typography>
              </Grid>
            </Grid>
<hr className={classes.hr} />
{/* Reserves Display */}
            <Typography variant="h6">Reserves</Typography>
            <Grid container direction="row" justifyContent="space-between">
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatReserve(reserves[0], coin1.symbol)}
                </Typography>
              </Grid>
              <Grid item xs={6}>
                <Typography variant="body1" className={classes.balance}>
                  {formatReserve(reserves[1], coin2.symbol)}
                </Typography>
              </Grid>
            </Grid>
<hr className={classes.hr} />
<LoadingButton
              loading={loading}
              valid={isButtonEnabled()}
              success={false}
              fail={false}
              onClick={swap}
            >
              <LoopIcon />
              Swap
            </LoadingButton>
          </Grid>
        </Paper>
      </Container>
<Grid
        container
        className={classes.footer}
        direction="row"
        justifyContent="center"
        alignItems="flex-end"
      >
        <p>
          Clearmatics Autonity Uniswap | Get AUT for use in the bakerloo testnet{" "}
          <a href="https://faucet.bakerloo.autonity.network/">here</a>
        </p>
      </Grid>
    </div>
);

Cuando LoadingButtonse hace clic en , llama a la función interna swap(ver más abajo), que luego llama a la swapTokensfunción Ethereum con las variables coin1.addressde estado coin2.addressy field1Valuecomo argumentos. Esto primero solicitará al usuario que permita el gasto de la moneda 1 con una notificación de metamáscara, luego, con otra notificación, solicitará al usuario que confirme la transacción. Cuando haya terminado, los valores en field1 y field2 se restablecerán.

Variables de estado

La declaración de retorno anterior hace referencia a varias variables de estado que realizan un seguimiento del estado de la aplicación. Estos son:

  • dialog1Open— Mantiene un registro del clima la primera ventana de diálogo está abierta
  • dialog2Open— Mantiene un registro del clima la primera ventana de diálogo está abierta
  • coin1— matriz de dirección, símbolo, saldo para coin1
  • coin2— matriz de dirección, símbolo, saldo para coin2
  • reserves— Almacena las reservas de liquidez en el pool para coin1 y coin2.
  • field1Value— Almacena la entrada del usuario en el campo 1 (el valor de la moneda 1 que se intercambiará)
  • field2Value— Almacena la entrada del usuario en el campo2 (el valor de la moneda2 que se intercambiará)
  • loading— booleano para controlar el botón de carga

Estos se definen con React.useStateganchos de la línea 83:

CoinSwapper/CoinSwapper.js

// Stores a record of whether their respective dialog window is open
const [dialog1Open, setDialog1Open] = React.useState(false);
const [dialog2Open, setDialog2Open] = React.useState(false);
// Stores data about their respective coin
const [coin1, setCoin1] = React.useState({
address: undefined,
symbol: undefined,
balance: undefined,
});
const [coin2, setCoin2] = React.useState({
address: undefined,
symbol: undefined,
balance: undefined,
});
// Stores the current reserves in the liquidity pool between coin1 and coin2
const [reserves, setReserves] = React.useState(["0.0", "0.0"]);
// Stores the current value of their respective text box
const [field1Value, setField1Value] = React.useState("");
const [field2Value, setField2Value] = React.useState("");
// Controls the loading button
const [loading, setLoading] = React.useState(false);

Estas variables de estado se utilizan en las siguientes funciones y también se utilizan para mostrar información al usuario en la declaración de devolución mencionada anteriormente.

Funciones internas

También se hace referencia en la declaración de devolución anterior a varias funciones internas, estas son:

  • switchFields
  • handleChange
  • formatBalance
  • formatReserve
  • isButtonEnabled
  • onToken1Selected
  • onToken2Selected
  • swap

switchFieldscambia las monedas superior e inferior. Esto se llama cuando los usuarios presionan el botón de intercambio o seleccionan el token opuesto en el cuadro de diálogo (por ejemplo, si coin1 es TokenA y el usuario selecciona TokenB al elegir coin2):

const switchFields = () => {
    setCoin1(coin2);
    setCoin2(coin1);
    setField1Value(field2Value);
    setReserves(reserves.reverse());
};

handleChangees una función interna común utilizada en ReactJS, que toma un evento HTML, extrae los datos y los coloca en una variable de estado. Aquí solía establecer el valor del campo 1 cuando coinFiedcambia el valor en el primero:

const handleChange = {
    field1: (e) => {
      setField1Value(e.target.value);
    },
};

formatBalance/ formatReserveconvertir el saldo / las reservas de la cuenta en algo agradable y legible:

const formatBalance = (balance, symbol) => {
    if (balance && symbol)
      return parseFloat(balance).toPrecision(8) + " " + symbol;
    else return "0.0";
};

isButtonEnableddetermina si el botón debe estar habilitado o no:

const isButtonEnabled = () => {
    let validFloat = new RegExp("^[0-9]*[.,]?[0-9]*$");
// If both coins have been selected, and a valid float has been entered which is less than the user's balance, then return true
    return (
      coin1.address &&
      coin2.address &&
      validFloat.test(field1Value) &&
      parseFloat(field1Value) <= coin1.balance
    );
};

onToken1Selected/ onToken2Selectedse llaman cuando sale la ventana de diálogo para coin1 / coin2, y establece las variables de estado relevantes:

const onToken1Selected = (address) => {
    // Close the dialog window
    setDialog1Open(false);
// If the user inputs the same token, we want to switch the data in the fields
    if (address === coin2.address) {
      switchFields();
    }
    // We only update the values if the user provides a token
    else if (address) {
      // Getting some token data is async, so we need to wait for the data to return, hence the promise
      getBalanceAndSymbol(account, address, provider, signer).then((data) => {
        setCoin1({
          address: address,
          symbol: data.symbol,
          balance: data.balance,
        });
      });
    }
};

swapllama a la swapTokensfunción Ethereum para realizar el intercambio, luego restablece las variables de estado necesarias:

const swap = () => {
    console.log("Attempting to swap tokens...");
    setLoading(true);
swapTokens(
      coin1.address,
      coin2.address,
      parseFloat(field1Value),
      router,
      account,
      signer
    )
      .then(() => {
        setLoading(false);
// If the transaction was successful, we clear to input to make sure the user doesn't accidental redo the transfer
        setField1Value("");
        enqueueSnackbar("Transaction Successful", { variant: "success" });
      })
      .catch((e) => {
        setLoading(false);
        enqueueSnackbar("Transaction Failed (" + e.message + ")", {
          variant: "error",
          autoHideDuration: 10000,
        });
      });
};

La línea 16 en la función de intercambio usa enqueueSnackbar, un componente del módulo de nodo Notistack. Notistack es una gran biblioteca para hacer notificaciones temporales. Consulta el repositorio aquí .

usar Ganchos de efecto

Finalmente, en CoinSwapper/CoinSwapper.js, hay cuatro useEffectganchos, que se utilizan para mantener la aplicación actualizada con los últimos cambios. El lambda (código) dentro de cada enlace se ejecuta cuando cambia una de las dependencias. Las dependencias se definen en la matriz de variables que se pasan a la función después de la expresión lambda.

El primero useEffectse llama cuando alguna de las variables de estado coin1.addresso coin2.addresscambia. Esto significa que cuando el usuario selecciona una moneda diferente para convertir, o se intercambian las monedas, se calcularán las nuevas reservas:

useEffect(() => {
    console.log(
      "Trying to get Reserves between:\n" +
        coin1.address +
        "\n" +
        coin2.address
    );
if (coin1.address && coin2.address) {
      getReserves(
        coin1.address,
        coin2.address,
        factory,
        signer,
        account
      ).then((data) => setReserves(data));
    }
}, [coin1.address, coin2.address, account, factory, router, signer]);

El segundo gancho se llama cuando alguna de las variables de estado field1Value coin1.addresso coin2.addresscambia. Intenta calcular y establecer la variable de estado field2Value. Esto significa que si el usuario ingresa un nuevo valor en el cuadro de conversión o la tasa de conversión cambia, el valor en el cuadro de salida cambiará:

useEffect(() => {
    if (isNaN(parseFloat(field1Value))) {
      setField2Value("");
    } else if (field1Value && coin1.address && coin2.address) {
      getAmountOut(
        coin1.address,
        coin2.address,
        field1Value,
        router
      ).then((amount) => setField2Value(amount.toFixed(7)));
    } else {
      setField2Value("");
    }
}, [field1Value, coin1.address, coin2.address]);

El tercer enlace crea un tiempo de espera que se ejecutará cada ~ 10 segundos, su función es verificar si el saldo del usuario se actualizó y cambió. Esto les permite ver cuándo se completa una transacción mirando la salida del saldo:

useEffect(() => {
    const coinTimeout = setTimeout(() => {
      console.log("Checking balances...");
if (coin1.address && coin2.address && account) {
        getReserves(
          coin1.address,
          coin2.address,
          factory,
          signer,
          account
        ).then((data) => setReserves(data));
      }
if (coin1 && account) {
        getBalanceAndSymbol(account, coin1.address, provider, signer).then(
          (data) => {
            setCoin1({
              ...coin1,
              balance: data.balance,
            });
          }
        );
      }
      if (coin2 && account) {
        getBalanceAndSymbol(account, coin2.address, provider, signer).then(
          (data) => {
            setCoin2({
              ...coin2,
              balance: data.balance,
            });
          }
        );
      }
    }, 10000);
return () => clearTimeout(coinTimeout);
});

El enlace final se ejecutará cuando el componente se monte por primera vez. Se utiliza para configurar la cuenta:

export async function quoteAddLiquidity(
  address1,
  address2,
  amountADesired,
  amountBDesired,
  factory,
  signer
) {
  const pairAddress = await factory.getPair(address1, address2);
  const pair = new Contract(pairAddress, PAIR.abi, signer);

  const reservesRaw = await fetchReserves(address1, address2, pair); // Returns the reserves already formated as ethers
  const reserveA = reservesRaw[0];
  const reserveB = reservesRaw[1];

  if (reserveA === 0 && reserveB === 0) {
    let amountOut = Math.sqrt(reserveA * reserveB);
    return [
      amountADesired.toString(),
      amountBDesired.toString(),
      amountOut.toString(),
    ];
  } else {
    let [amountBOptimal, amountOut] = quote(amountADesired, reserveA, reserveB);
    if (amountBOptimal <= amountBDesired) {
      return [
        amountADesired.toString(),
        amountBOptimal.toString(),
        amountOut.toString(),
      ];
    } else {
      let [amountAOptimal, amountOut] = quote(
        amountBDesired,
        reserveB,
        reserveA
      );
      console.log(amountAOptimal, amountOut);
      return [
        amountAOptimal.toString(),
        amountBDesired.toString(),
        amountOut.toString(),
      ];
    }
  }
}

Conclusión

La primera sección de este blog cubrió las funciones de Ethereum necesarias para realizar llamadas y transacciones a la cadena de bloques. La siguiente sección pasó por los componentes personalizados, las funciones internas y los ganchos necesarios para que la funcionalidad de intercambio principal funcione.

Todavía tengo que mencionar cualquiera de las funcionalidades de eliminación/implementación de liquidez. Ese es el tema del próximo blog, aquí .

Para dar una propina al autor, puede usar esta dirección ethereum: 0xe7EeDB184E63Fe049EebA79EfeAc72939cB1461D 

Esta historia se publicó originalmente en https://medium.com/clearmatics/how-i-made-a-uniswap-interface-from-scratch-b51e1027ca87

#uniswap #ethereum #token #bitcoin 

Amara Sophi

Amara Sophi

1623676495

How to create own defi based protocol like uniswap?

Uniswap Clone Script is ready made script embedded with functions to support margin trading and spot trading for 100+ cryptocurrencies including the widely deployed ERC 20 tokens and ETH. Uniswap exchange clone enables users to seamlessly swap their tokens by connecting the web 3 wallets such as Coinbase wallet, WalletConnect, Formatic, MetaMask, etc.

Ultimate Features of UniSwap Clone Script

Flash Swapping
Token Swapping/Exchange
Price Oracles
Path to Sustainability
Anonymous Trading
Own Custody of Tokens
Flexibility Token Exchanges
Highly Secure
Highly Confidential

Benefits of Uniswap Clone Script

Uniswap exchange clone script promotes your decentralized ethereum based protocol like uniswap. Check the benefits of our uniswap clone below

100% Decentralized Absence of Third Parties
Possible to create exchange for any ERC20 token
Anyone can connect wallet easily to make exchange
Enable your New tokens that can directly access liquidity
Gain more profit by putting funds into the liquidity pool.
Builds affordable liquidity based exchange than other exchanges
Can be expanded/built upon it means custom pools have also be created.
Doesn’t charge any listing fee due to decentralized

Where to create own DeFi Protocol like Uniswap?

Coinjoker is a top leading defi development company experts in creating a decentralized ethereum protocol like uniswap. We create your uniswap clone protocol which is more secure and reliable. It displays your defi based exchange platform with dead-simple UX, earning from trading fees, price determination, historical liquidity, volume and price across across a number of timeframes.

We build you uniswap clone software with the above mentioned functionalities as well as customized option in your exchange platform. We assist you to build your defi protocol like uniswap from the scratch with the bug free solutions and constantly helps you to support and maintenance.

Get A Free Live Demo of >> Uniswap clone script

#uniswap clone #uniswap clone script #uniswap dex clone #create defi protocol like uniswap

Abigail betty

Abigail betty

1624553760

What is Uniswap - A Beginner's Guide (2021 Updated) DO NOT MISS!!!

🦄Uniswap is a decentralized permissionless exchange that allows users to trade ERC-20 tokens directly.
Here is what’s discussed in the video:
0:57 What is Uniswap?
1:13 How Traditional Crypto Exchanges Work
1:57 Orders on Traditional Exchanges
2:28 Decentralized Exchanges (DEX)
3:40 Traditional Crypto Exchange Trades
4:47 DEX Trades
4:55 Liquidity Pools
5:28 Traditional Price Discovery
5:47 DEX Price Discovery
6:06 Uniswap AMM
6:32 Constant Product Market Maker Model
8:07 Uniswap
8:41 Uniswap Versions
9:28 Using Uniswap
9:52 Price Slippage
10:25 Beware of Scams
11:04 UNI Token
12:18 Conclusion
12:53 Bloopers

📺 The video in this post was made by 99Bitcoins
The origin of the article: https://www.youtube.com/watch?v=dIneNZTnFMw
🔺 DISCLAIMER: The article is for information sharing. The content of this video is solely the opinions of the speaker who is not a licensed financial advisor or registered investment advisor. Not investment advice or legal advice.
Cryptocurrency trading is VERY risky. Make sure you understand these risks and that you are responsible for what you do with your money
🔥 If you’re a beginner. I believe the article below will be useful to you ☞ What You Should Know Before Investing in Cryptocurrency - For Beginner
⭐ ⭐ ⭐The project is of interest to the community. Join to Get free ‘GEEK coin’ (GEEKCASH coin)!
☞ **-----CLICK HERE-----**⭐ ⭐ ⭐
Thanks for visiting and watching! Please don’t forget to leave a like, comment and share!

#bitcoin #blockchain #uniswap #what is uniswap #beginner's

Biju Augustian

Biju Augustian

1575286116

Java EE: Desde cero a Experto (EJB, JPA, Web Services, JSF) |Simpliv

Description
The Java EE course of Global Mentoring is the best and most complete course to learn to program using the Java Enterprise Edition in Spanish . We will start with an Introduction to Java Business Technology, and we will take you step by step to become a Java expert in record time and so you can create Web and Business applications, including topics and technologies such as EJB’s, JPA, Web Services, Security, Servlets and JSPs, JavaServer Faces (JSF), PrimeFaces, AJAX and many more Java EE technology topics.

The content is divided into perfectly structured levels , each level supported by the previous one, with the aim of adding Java EE knowledge incrementally so that you can focus on mastering the issues little by little and gradually. So ensure the success of your Java EE training.

We will offer support for any questions about the teaching material included in this Java EE course.

To make matters worse, we handle a new teaching methodology that we have called Speed ​​Learning. This methodology consists of concise videos that go directly to the point to study, complemented with eBooks with explanations and step-by-step images (which you can print, or search for any text you need, or use for your offline study), since As we know we cannot do text search within video. In addition, our methodology includes perfectly structured and very didactic exercises, which will allow you to accelerate your eLearning learning. Without wasting time on videos where you have to watch the instructor codify an exercise, too much theory, little practice or anything like that. Our Speed ​​Learning methodology guarantees that in the shortest possible time you will acquire the necessary knowledge for the professional and professional world of Java.

The Java EE course includes the following topics:

Java EE course:

Lesson 1 - Introduction to Java EE

Introduction to the Java Business World (Java EE)
Java EE Technologies Stack
Multilayer Architecture in Java EE
Tool Installation (Eclipse, MySql)
Glassfish Application Server Installation
Use of Maven and JavaEE
HelloWorld with JavaEE
Lesson 2 - Enterprise Java Beans (EJB)

Introduction to the EJB
Types and Configuration of an EJB
Dependency Injection in Java EE
Packaging and Business Containers
Lesson 3 - Introduction to Java Persistence API (JPA)

Introduction to Java Persistence API (JPA)
Understanding Entity classes in JPA
EntityManager and Persistence Unit Management
Use of JUnit and JPA
Lesson 4 - Consultations with JPA

Life Cycle in JPA
Types of Relationships in JPA
JPQL queries in JPA
Use of the Criteria API in JPA
Transaction Management in JPA
Lesson 5 - Role of Servlets and JSPs

Role of Servlets and JSPs in Java EE
Role of JSPs in Java EE
Servlets and EJB integration in Java EE
Lesson 6 - JSF Role

JSF role in Java EE
New features of JSF
Ajax in JSF
PrimeFaces and JSF
JSF, PrimeFaces, AJAX, EJB and JPA integration
Lesson 7 - WebServices and JavaEE

Introduction to Web Services
Types of JAX-WS and JAX-RS Web Services
What is a WSDL Document
Use of XML and XSD Documents (XML Scheme)
JAXB API Management
Web Services Generation Strategies
Web Services Deployment
Web Service Client Creation
Lesson 8 - REST Web Services

Introduction to REST Web Services
HTTP Request Analysis
JAX-RS API annotations
EJB and JAX-RS integration
Creation of a REST Client
WADL and XSD document of the REST Web Service
Deployment of a REST Web Service
REST Web Service Client Creation
Lesson 9 - Security in Java EE

Introduction to Security in Java EE
Authentication and Authorization in Java EE
Web Layer Security in Java EE
Layer EJB Security in Java EE
Client Authentication in Java EE
Web Client Authentication
SOAP Client Authentication and REST Web Service
Final Exercise with the integration of everything learned
At the end you get a certificate of having completed the Java EE course. And from that moment great job and professional opportunities await you in the real world.

But there is more!

Because several of our students have told us that they need to reinforce some basic Java concepts, we will add the courses for free to your Java EE course the following courses:

  1. Java basics

  2. Java programming

  3. Java with JDBC

  4. HTML, CSS and JavaScript

  5. Servlets and JSP’s

  6. JavaServer Faces (JSF)

This content is completely free once you enter your Java EE course, so we hope you enjoy the following courses that are a gift voucher for enrolling in our Java EE course, with the intention that you can review the topics described in case That you require it. These courses are in the same syllabus of this Java EE course, you just have to review what topics you are interested in reviewing, so you don’t have to consult this information in another course.

So don’t wait any longer and sign up now for this fabulous Java EE course from Global Mentoring. Remember that your satisfaction is guaranteed or your money back.

Greetings and see you on the other side.

Ing. Ubaldo Acosta

Founder of Global Mentoring and Java University

Who this course is for:

Any Java programmer interested in learning Java EE, and technologies such as EJB, JPA, Web Services, Security and deploying Java business applications in Glassfish
Basic knowledge
Although this course is advanced, we add several courses as a gift voucher so you can start from scratch and step by step with Java technology.
Know how to use PC at the basic level
What will you learn
Module I. Introduction to Java EE
Module II Enterprise Java Beans (EJB) 3.x
Module III Java Persistance API (JPA) 2.x
Module IV Servlets and JSPs in Java EE
Module V. JavaServer Faces (JSF) 2.x
Module VI Web Services in JEE and Security in JEE
And much more!!!
Also if you do not have knowledge of basic Java, or need to reinforce more basic topics, do not worry, we add several courses as a gift voucher that will allow you to review more basic Java themes completely free

#Java EE #Desde cero #EJB, JPA #Web Services, #JSF

Amara Sophi

Amara Sophi

1610018015

Create Decentralized Finance Protocol Like Uniswap

Uniswap Clone is a fully decentralized automated protocol offering liquidity on ethereum like uniswap. Create own protocol like uniswap helps to make your crypto exchange as more Uniswap featured ecosystem and ready to easy token exchanges.

Whitelabel uniswap clone development is 100% decentralized and defi based protocol built to focus more complete and accurate data transformaion with our customized UI/UX design and enable to you buy and sell tokens like Uniswap’s advanced iframe integration.

Coinjoker is a leading defi development company now make revolution in defi based projects like DApp, Smart contract, Lending and borrowing, Staking, Token creation, Yield Farming and Popular defi based protocols like Uniswap and Justswap.

Get A Free Demo of >>> Uniswap Clone Script

General Functionalities of Uniswap Clone
Uniswap clone is ethereum based protocol script used to swap ERC20 tokens in fully decentralized way without need of buyers and sellers. Decentralized Platforms facing liquidity problems to overcome this issue, Uniswap clone script can solve decentralized exchanges liquidity problem, by enabling the exchange to swap tokens without relying on any users for creating that liquidity.

Coinjoker’s uniswap clone script is designed for public goodness to avoid the intermediate fees between transactions. Our uniswap clone for the community trade tokens allows users to make exchange without platform fees or middlemen.

#uniswap clone script #uniswap clone software #uniswap clone app