伊藤  直子

伊藤 直子

1654503060

ユニスワップインターフェイスを最初から作成した

スクリプトを使用するのではなく、ユーザーが異なるコイン間で簡単にスワップを行い、Autonityネットワークで流動性を展開または削除できるようにすることを目的として、ネットワークに展開されたUniswapコントラクトのインターフェイスを作成することにしました。

公式のUniswapインターフェースは、その非常に広範なコードベースとウォレットへの接続の問題のために、プライベートネットワークをフォークするのが難しいことがわかりました。上から下まで理解できる小さなコードベースが欲しかったので、素晴らしいインターンのMattの助けを借りて、独自のより単純なアプリケーションを作成することにしました。

プロジェクトにはReactJSを使用し、 EthersJSモジュールを使用してブラウザーのメタマスクを介してブロックチェーンに接続し、フロントエンドにはMaterial-UIを使用しました。静的サイトであるため、アプリケーションをホストするためにgithubページを使用しました。

この最初のブログでは、アプリケーションのスワップ部分のコードについて説明しています。まず、Ethereumブロックチェーンバックエンドに接続して呼び出しやトランザクションを行うために必要な機能について説明し、次に、ReactフックとMaterial-UIを多用するReactフロントエンドについて説明します。これは、読者がすでにReactJSを十分に理解していること、およびUniswap V2自動マーケットメーカー(AMM)がどのように機能するかについての知識があることを期待して書かれています(この記事を参照)。NavBarなどの一般的なReactコンポーネントや、Ethereumウォレットが見つからない場合のリダイレクトページについては説明しません。このブログはそれがないと十分に長くなるからです。

ここでインターフェースをチェックアウトし、ここでリポジトリの生のコードをチェックアウトします。

このブログでは、このコミットの時点でのインターフェースの状態について説明していることに注意してください。その後、複数のネットワークをサポートするように更新され、ネットワークに応じて異なるデフォルトのコインと、その他のいくつかの小さな変更が表示されます。

イーサリアム機能

ブロックチェーンに接続する

これらの関数は、ブラウザーのウォレットを介してブロックチェーンに接続し、呼び出しを介してブロックチェーンから情報をフェッチし、ネットワーク上のコントラクトを使用してトランザクションを介してスワップを行うために必要です。

私の当初の計画は、javascriptモジュールWeb3を使用して、ブラウザーでメタマスクを使用してブロックチェーンに接続することでしたが、メタマスクは2021年1月にサポートを終了したため、代わりにEthersJSを使用しました。EthersJSはWeb3よりもはるかに小さいモジュールです。つまり、アプリケーションの読み込みが速くなります。

ファイルethereumFunctions.jsでは、最初の関数はgetProvider、ブラウザのイーサリアムプロバイダー(メタマスクまたは別のウォレット)に接続し、次に関数getSignerはトランザクションの署名に使用されます。EthersJSコントラクトクラスを使用するのは、以前にブロックチェーンにデプロイしたルーター、ウェス、ファクトリーコントラクトのコントラクトオブジェクトを返す関数です。これらの場合、Contractクラスは、アドレス、デプロイされたスマートコントラクトのABI、およびEthersJS署名者をパラメーターとして受け取りました。

getAccountまた、接続されたウォレットから使用するアカウントを選択するようにユーザーに促す関数も定義されています。

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];
}

これらの関数はすべてファイルにインポートされ、70行目からフックCoinSwapper/CoinSwapper.jsを使用して対応する状態変数を設定するために使用されました。React.useState

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)
);

ERC20トークン機能

ファイル内の次の2つの関数はethereumFunctions.js、ネットワークを呼び出して、ユーザーが選択または提供したトークンアドレスに関する情報を取得します。

doesTokenExist提供されたアドレスがブロックチェーンにデプロイされたトークンに対応していることを確認するためのチェックを行います。

ethereumFunctions.js

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

getBalanceAndSymbol提供されたアドレスがブロックチェーン上のWethコントラクトのアドレスであるかどうかをチェックします。その場合、ユーザーのAUT(Autonityブロックチェーンのネイティブコイン、ETHに相当)の残高を返します。それ以外の場合は、トークンのシンボルとともに、ユーザーのERC20トークン残高を返します。

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;
  }
}

COINS.AUTONITY.addressこの関数は、アドレスがからエクスポートされたデフォルトトークンの配列であると比較することにより、アドレスがウェスアドレスであるかどうかをチェックしますconstants/coins.js

スワップ機能

swapTokens関数はethereumFunctions.js、ブロックチェーン上のルーターコントラクトに対してトランザクションを実行し、提供されたトークンアドレスに応じて、3つの異なるスワップ(AUTからトークン、トークンからAUT、トークンからトークン)のいずれかを実行しaddress1ますaddress2

  • address1がWethコントラクトのアドレスである場合、ルーター関数を呼び出しますswapExactETHForTokens
  • address2がWethコントラクトのアドレスである場合、ルーター関数を呼び出しますswapExactTokensForETH
  • どちらでもないaddress1address2、Wethコントラクトのアドレスである場合、swapTokens関数はルーター関数を呼び出しますswapExactTokensForTokens
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
    );
  }
}

この関数getAmountOutは、スワップのプレビューを取得するために使用されます。getAmountsOut最初のトークンの量と、パラメーターとして交換されるトークンのアドレスの配列を使用して、ルーター関数を呼び出します。2番目のトークンから金額を返します。

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;
  }
}

リザーブ機能

最後に、のGetReserves関数はethereumFunctions.js、指定されたトークンのペアの流動性プールの予約と、ユーザーの流動性トークンの残高を返します。内部的に、この関数は別の関数fetchReservesを呼び出します。この関数は、ペアコントラクトを呼び出してリザーブをフェッチし、リザーブが正しい順序で返されることを確認します。

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,
  ];
}

Reactフロントエンド

アプリケーションのフロントエンドは、、、、、などのMaterial-UIコンポーネントGrid、およびさまざまなボタンなどContainerを広範囲Paperに使用します。Typographyアプリケーション内のすべてのコンポーネントの動作を説明するのではなく、コードを同時に読んでいる場合に最も意味のある、高レベルの概要を説明することを目指します。Material-UIに精通していない場合は、ここでそれらの優れたドキュメントのいくつかを読むことをお勧めします。

このファイルは、トークンの選択とスワップの作成に使用されるReactコンポーネントを返すCoinSwapper.js関数をエクスポートします。CoinSwapperこの関数自体は、他のいくつかのカスタムReactコンポーネントを利用します。これについては、コンポーネントを機能させる内部関数とフックを説明する前に最初に説明しCoinSwapperます。

コインダイアログコンポーネント

CoinDialogコンポーネントは、「選択」ボタンの1つをクリックすると開くコインのメニューをレンダリングします。これにより、ReactDialogコンポーネントが拡張され、アプリの残りの部分の前に開くウィンドウがレンダリングされ、決定を求めるために使用されます。それはで定義されていますCoinSwapper/CoinDialog.js

コンポーネント内CoinDialogには、最初にMaterial-UIのコンポーネントの修正バージョンがあり、クリックすると閉じる関数をDialogTitle呼び出す閉じるボタンが追加されています。exitCoinDialog

次に、ReactTextFieldコンポーネントがあります。これにより、ユーザーは使用するトークンのアドレスを貼り付けることができます。変更時に、これは状態変数addressをユーザーの入力に設定します。

次の部分は、デフォルトの各トークンをConstants/coinsカスタムCoinButtonコンポーネントにマップするマッピングです(以下を参照)。クリックすると、選択したトークンのアドレスを返すexit関数を呼び出します。CoinDialog

最後に、ボタンがありEnterます。このボタンをクリックすると、関数を呼び出します。このボタンは、Ethereum関数を使用してsubmit、のアドレスにトークンが存在するかどうかを確認してから、呼び出してアドレスを返します。TextFielddoesTokenExistexitCoinDialog

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>
);

このコンポーネントは、他のすべてのコンポーネントと同様にmakeStyles、スタイリングにMaterial-UI関数を使用します。これは、他のMaterial-UIコンポーネントで簡単に使用でき、テーマのネストと動的なスタイルを可能にするJSソリューションのCSSです。

コインボタンコンポーネント

CoinButtonで定義されたコンポーネントは、propsを介して渡されるトークンシンボル(coinAbbr)とトークン名(coinName)のテキストを含むCoinSwapper/CoinButton.jsMaterial-UIコンポーネントを拡張します。ButtonBase

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>
    )
}

コインフィールドコンポーネント

CoinFieldで定義されたコンポーネントは、各CoinSwapper/CoinFieldトークンを選択し、スワッピングの金額を入力するために使用される「選択」ボタンを使用して入力バーをレンダリングします。コンポーネントは比較的単純で、Material-UIコンポーネントFab(フローティングアクションボタン)とInputBase(テキストフィールド)で構成されており、どちらもGrid間隔を空けるためにコンポーネントでラップされています。Fabおよびコンポーネントの相対プロパティは、小道具を介しInputBaseてコンポーネントに渡されます。CoinField

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>
);

読み込みボタン

LoadingButtonで定義されたコンポーネントはComponents/LoadingButton.js、メインコンポーネントの下部にある[SWAP]ボタンをレンダリングしCoinSwapper ます。Material-UIButtonコンポーネントを拡張するため、小道具によっては無効になりvalid、スワップトランザクションが完了するまでクリックすると回転する読み込みアイコンが表示されます。

Components / 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>
  );
}

コインスワッパー機能

Returnステートメント

アプリケーションの主な関数であるCoinSwapperinCoinSwapper/CoinSwapper.jsは、2つのコンポーネントを含むボックス化されたコンポーネントを返します。このコンポーネントは、2つのコンポーネントのいずれかが選択されCoinDialogた場合にのみ開きます。次に、選択した2つのトークンの残高と予約を表示するCoinFieldMaterial-UIコンポーネントがあります。Typography

最後にLoadingButton、下部にコンポーネントがあり、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>
);

LoadingButtonをクリックすると、内部関数(swap以下を参照)が呼び出さswapTokensれ、状態変数coin1.addressを使用coin2.addressfield1Valueて、引数としてイーサリアム関数が呼び出されます。これにより、最初にメタマスク通知でコイン1の使用を許可するようにユーザーに促し、次に別の通知でユーザーにトランザクションの確認を促します。これが完了すると、field1とfield2の値がリセットされます。

状態変数

上記のreturnステートメントは、アプリケーションの状態を追跡するいくつかの状態変数を参照します。これらは:

  • dialog1Open—最初のダイアログウィンドウが開いている天気の記録を保持します
  • dialog2Open—最初のダイアログウィンドウが開いている天気の記録を保持します
  • coin1—コイン1のアドレス、シンボル、バランスの配列
  • coin2—コイン2のアドレス、シンボル、バランスの配列
  • reserves—coin1およびcoin2の流動性準備金をプールに保管します。
  • field1Value—ユーザーの入力をfield1(交換されるcoin1の値)に格納します
  • field2Value—ユーザーの入力をfield2(交換されるcoin2の値)に格納します
  • loading—ロードボタンを制御するブール値

これらは、React.useState83行目のフックで定義されています。

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);

これらの状態変数は、次の関数で使用され、前述のreturnステートメントでユーザーに情報を表示するためにも使用されます。

内部機能

上記のreturnステートメントでは、いくつかの内部関数も参照されています。これらは次のとおりです。

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

switchFields上部と下部のコインを切り替えます。これは、ユーザーがスワップボタンを押すか、ダイアログで反対のトークンを選択したときに呼び出されます(たとえば、coin1がTokenAで、ユーザーがcoin2を選択するときにTokenBを選択した場合)。

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

handleChangeはReactJSで使用される一般的な内部関数であり、HTMLイベントを受け取り、データを引き出して状態変数に入れます。coinFiedここでは、最初の値が変更されたときにfield1の値を設定するために使用されていました。

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

formatBalance/formatReserveアカウントの残高/予約を素敵で読みやすいものに変えます:

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

isButtonEnabledボタンを有効にするかどうかを決定します。

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/onToken2Selectedは、coin1 / coin2のダイアログウィンドウが終了したときに呼び出され、関連する状態変数を設定します。

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,
        });
      });
    }
};

swapイーサリアム関数を呼び出してswapTokensスワップを作成し、必要な状態変数をリセットします。

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,
        });
      });
};

スワップ関数の16行目でenqueueSnackbarは、ノードモジュールNotistackのコンポーネントであるを使用しています。Notistackは、一時的な通知を行うための優れたライブラリです。ここでリポジトリを確認してください。

useEffectフック

最後にCoinSwapper/CoinSwapper.js、には4つのuseEffectフックがあり、これらはアプリケーションを最新の変更で最新の状態に保つために使用されます。各フック内のラムダ(コード)は、依存関係の1つが変更されたときに実行されます。依存関係は、ラムダ式の後に関数に渡される変数の配列で定義されます。

1つ目useEffectは、状態変数coin1.addressまたはcoin2.address変更のいずれかが発生したときに呼び出されます。これは、ユーザーが変換する別のコインを選択した場合、またはコインが交換された場合に、新しい準備金が計算されることを意味します。

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]);

2番目のフックは、状態変数field1Value coin1.addressまたはcoin2.address変更のいずれかが発生したときに呼び出されます。状態変数の計算と設定を試みますfield2Value。これは、ユーザーが変換ボックスに新しい値を入力した場合、または変換率が変更された場合、出力ボックスの値が変更されることを意味します。

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]);

3番目のフックは、約10秒ごとに実行されるタイムアウトを作成します。その役割は、ユーザーの残高が更新および変更されたかどうかを確認することです。これにより、残高の出力を確認することで、トランザクションがいつ完了するかを確認できます。

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);
});

最後のフックは、コンポーネントが最初にマウントされたときに実行されます。アカウントを設定するために使用されます。

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(),
      ];
    }
  }
}

結論

このブログの最初のセクションでは、ブロックチェーンへの呼び出しとトランザクションを行うために必要なイーサリアム関数について説明しました。次のセクションでは、メインのスワップ機能を機能させるために必要なカスタムコンポーネント、内部関数、およびフックについて説明しました。

流動性の削除/展開機能についてはまだ触れていません。それが次のブログのトピックです

著者にチップを渡すには、次のイーサリアムアドレスを使用できます:0xe7EeDB184E63Fe049EebA79EfeAc72939cB1461D 

このストーリーは、もともとhttps://medium.com/clearmatics/how-i-made-a-uniswap-interface-from-scratch-b51e1027ca87で公開されました

#uniswap #ethereum #token #bitcoin 

What is GEEK

Buddha Community

ユニスワップインターフェイスを最初から作成した
伊藤  直子

伊藤 直子

1654503060

ユニスワップインターフェイスを最初から作成した

スクリプトを使用するのではなく、ユーザーが異なるコイン間で簡単にスワップを行い、Autonityネットワークで流動性を展開または削除できるようにすることを目的として、ネットワークに展開されたUniswapコントラクトのインターフェイスを作成することにしました。

公式のUniswapインターフェースは、その非常に広範なコードベースとウォレットへの接続の問題のために、プライベートネットワークをフォークするのが難しいことがわかりました。上から下まで理解できる小さなコードベースが欲しかったので、素晴らしいインターンのMattの助けを借りて、独自のより単純なアプリケーションを作成することにしました。

プロジェクトにはReactJSを使用し、 EthersJSモジュールを使用してブラウザーのメタマスクを介してブロックチェーンに接続し、フロントエンドにはMaterial-UIを使用しました。静的サイトであるため、アプリケーションをホストするためにgithubページを使用しました。

この最初のブログでは、アプリケーションのスワップ部分のコードについて説明しています。まず、Ethereumブロックチェーンバックエンドに接続して呼び出しやトランザクションを行うために必要な機能について説明し、次に、ReactフックとMaterial-UIを多用するReactフロントエンドについて説明します。これは、読者がすでにReactJSを十分に理解していること、およびUniswap V2自動マーケットメーカー(AMM)がどのように機能するかについての知識があることを期待して書かれています(この記事を参照)。NavBarなどの一般的なReactコンポーネントや、Ethereumウォレットが見つからない場合のリダイレクトページについては説明しません。このブログはそれがないと十分に長くなるからです。

ここでインターフェースをチェックアウトし、ここでリポジトリの生のコードをチェックアウトします。

このブログでは、このコミットの時点でのインターフェースの状態について説明していることに注意してください。その後、複数のネットワークをサポートするように更新され、ネットワークに応じて異なるデフォルトのコインと、その他のいくつかの小さな変更が表示されます。

イーサリアム機能

ブロックチェーンに接続する

これらの関数は、ブラウザーのウォレットを介してブロックチェーンに接続し、呼び出しを介してブロックチェーンから情報をフェッチし、ネットワーク上のコントラクトを使用してトランザクションを介してスワップを行うために必要です。

私の当初の計画は、javascriptモジュールWeb3を使用して、ブラウザーでメタマスクを使用してブロックチェーンに接続することでしたが、メタマスクは2021年1月にサポートを終了したため、代わりにEthersJSを使用しました。EthersJSはWeb3よりもはるかに小さいモジュールです。つまり、アプリケーションの読み込みが速くなります。

ファイルethereumFunctions.jsでは、最初の関数はgetProvider、ブラウザのイーサリアムプロバイダー(メタマスクまたは別のウォレット)に接続し、次に関数getSignerはトランザクションの署名に使用されます。EthersJSコントラクトクラスを使用するのは、以前にブロックチェーンにデプロイしたルーター、ウェス、ファクトリーコントラクトのコントラクトオブジェクトを返す関数です。これらの場合、Contractクラスは、アドレス、デプロイされたスマートコントラクトのABI、およびEthersJS署名者をパラメーターとして受け取りました。

getAccountまた、接続されたウォレットから使用するアカウントを選択するようにユーザーに促す関数も定義されています。

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];
}

これらの関数はすべてファイルにインポートされ、70行目からフックCoinSwapper/CoinSwapper.jsを使用して対応する状態変数を設定するために使用されました。React.useState

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)
);

ERC20トークン機能

ファイル内の次の2つの関数はethereumFunctions.js、ネットワークを呼び出して、ユーザーが選択または提供したトークンアドレスに関する情報を取得します。

doesTokenExist提供されたアドレスがブロックチェーンにデプロイされたトークンに対応していることを確認するためのチェックを行います。

ethereumFunctions.js

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

getBalanceAndSymbol提供されたアドレスがブロックチェーン上のWethコントラクトのアドレスであるかどうかをチェックします。その場合、ユーザーのAUT(Autonityブロックチェーンのネイティブコイン、ETHに相当)の残高を返します。それ以外の場合は、トークンのシンボルとともに、ユーザーのERC20トークン残高を返します。

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;
  }
}

COINS.AUTONITY.addressこの関数は、アドレスがからエクスポートされたデフォルトトークンの配列であると比較することにより、アドレスがウェスアドレスであるかどうかをチェックしますconstants/coins.js

スワップ機能

swapTokens関数はethereumFunctions.js、ブロックチェーン上のルーターコントラクトに対してトランザクションを実行し、提供されたトークンアドレスに応じて、3つの異なるスワップ(AUTからトークン、トークンからAUT、トークンからトークン)のいずれかを実行しaddress1ますaddress2

  • address1がWethコントラクトのアドレスである場合、ルーター関数を呼び出しますswapExactETHForTokens
  • address2がWethコントラクトのアドレスである場合、ルーター関数を呼び出しますswapExactTokensForETH
  • どちらでもないaddress1address2、Wethコントラクトのアドレスである場合、swapTokens関数はルーター関数を呼び出しますswapExactTokensForTokens
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
    );
  }
}

この関数getAmountOutは、スワップのプレビューを取得するために使用されます。getAmountsOut最初のトークンの量と、パラメーターとして交換されるトークンのアドレスの配列を使用して、ルーター関数を呼び出します。2番目のトークンから金額を返します。

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;
  }
}

リザーブ機能

最後に、のGetReserves関数はethereumFunctions.js、指定されたトークンのペアの流動性プールの予約と、ユーザーの流動性トークンの残高を返します。内部的に、この関数は別の関数fetchReservesを呼び出します。この関数は、ペアコントラクトを呼び出してリザーブをフェッチし、リザーブが正しい順序で返されることを確認します。

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,
  ];
}

Reactフロントエンド

アプリケーションのフロントエンドは、、、、、などのMaterial-UIコンポーネントGrid、およびさまざまなボタンなどContainerを広範囲Paperに使用します。Typographyアプリケーション内のすべてのコンポーネントの動作を説明するのではなく、コードを同時に読んでいる場合に最も意味のある、高レベルの概要を説明することを目指します。Material-UIに精通していない場合は、ここでそれらの優れたドキュメントのいくつかを読むことをお勧めします。

このファイルは、トークンの選択とスワップの作成に使用されるReactコンポーネントを返すCoinSwapper.js関数をエクスポートします。CoinSwapperこの関数自体は、他のいくつかのカスタムReactコンポーネントを利用します。これについては、コンポーネントを機能させる内部関数とフックを説明する前に最初に説明しCoinSwapperます。

コインダイアログコンポーネント

CoinDialogコンポーネントは、「選択」ボタンの1つをクリックすると開くコインのメニューをレンダリングします。これにより、ReactDialogコンポーネントが拡張され、アプリの残りの部分の前に開くウィンドウがレンダリングされ、決定を求めるために使用されます。それはで定義されていますCoinSwapper/CoinDialog.js

コンポーネント内CoinDialogには、最初にMaterial-UIのコンポーネントの修正バージョンがあり、クリックすると閉じる関数をDialogTitle呼び出す閉じるボタンが追加されています。exitCoinDialog

次に、ReactTextFieldコンポーネントがあります。これにより、ユーザーは使用するトークンのアドレスを貼り付けることができます。変更時に、これは状態変数addressをユーザーの入力に設定します。

次の部分は、デフォルトの各トークンをConstants/coinsカスタムCoinButtonコンポーネントにマップするマッピングです(以下を参照)。クリックすると、選択したトークンのアドレスを返すexit関数を呼び出します。CoinDialog

最後に、ボタンがありEnterます。このボタンをクリックすると、関数を呼び出します。このボタンは、Ethereum関数を使用してsubmit、のアドレスにトークンが存在するかどうかを確認してから、呼び出してアドレスを返します。TextFielddoesTokenExistexitCoinDialog

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>
);

このコンポーネントは、他のすべてのコンポーネントと同様にmakeStyles、スタイリングにMaterial-UI関数を使用します。これは、他のMaterial-UIコンポーネントで簡単に使用でき、テーマのネストと動的なスタイルを可能にするJSソリューションのCSSです。

コインボタンコンポーネント

CoinButtonで定義されたコンポーネントは、propsを介して渡されるトークンシンボル(coinAbbr)とトークン名(coinName)のテキストを含むCoinSwapper/CoinButton.jsMaterial-UIコンポーネントを拡張します。ButtonBase

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>
    )
}

コインフィールドコンポーネント

CoinFieldで定義されたコンポーネントは、各CoinSwapper/CoinFieldトークンを選択し、スワッピングの金額を入力するために使用される「選択」ボタンを使用して入力バーをレンダリングします。コンポーネントは比較的単純で、Material-UIコンポーネントFab(フローティングアクションボタン)とInputBase(テキストフィールド)で構成されており、どちらもGrid間隔を空けるためにコンポーネントでラップされています。Fabおよびコンポーネントの相対プロパティは、小道具を介しInputBaseてコンポーネントに渡されます。CoinField

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>
);

読み込みボタン

LoadingButtonで定義されたコンポーネントはComponents/LoadingButton.js、メインコンポーネントの下部にある[SWAP]ボタンをレンダリングしCoinSwapper ます。Material-UIButtonコンポーネントを拡張するため、小道具によっては無効になりvalid、スワップトランザクションが完了するまでクリックすると回転する読み込みアイコンが表示されます。

Components / 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>
  );
}

コインスワッパー機能

Returnステートメント

アプリケーションの主な関数であるCoinSwapperinCoinSwapper/CoinSwapper.jsは、2つのコンポーネントを含むボックス化されたコンポーネントを返します。このコンポーネントは、2つのコンポーネントのいずれかが選択されCoinDialogた場合にのみ開きます。次に、選択した2つのトークンの残高と予約を表示するCoinFieldMaterial-UIコンポーネントがあります。Typography

最後にLoadingButton、下部にコンポーネントがあり、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>
);

LoadingButtonをクリックすると、内部関数(swap以下を参照)が呼び出さswapTokensれ、状態変数coin1.addressを使用coin2.addressfield1Valueて、引数としてイーサリアム関数が呼び出されます。これにより、最初にメタマスク通知でコイン1の使用を許可するようにユーザーに促し、次に別の通知でユーザーにトランザクションの確認を促します。これが完了すると、field1とfield2の値がリセットされます。

状態変数

上記のreturnステートメントは、アプリケーションの状態を追跡するいくつかの状態変数を参照します。これらは:

  • dialog1Open—最初のダイアログウィンドウが開いている天気の記録を保持します
  • dialog2Open—最初のダイアログウィンドウが開いている天気の記録を保持します
  • coin1—コイン1のアドレス、シンボル、バランスの配列
  • coin2—コイン2のアドレス、シンボル、バランスの配列
  • reserves—coin1およびcoin2の流動性準備金をプールに保管します。
  • field1Value—ユーザーの入力をfield1(交換されるcoin1の値)に格納します
  • field2Value—ユーザーの入力をfield2(交換されるcoin2の値)に格納します
  • loading—ロードボタンを制御するブール値

これらは、React.useState83行目のフックで定義されています。

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);

これらの状態変数は、次の関数で使用され、前述のreturnステートメントでユーザーに情報を表示するためにも使用されます。

内部機能

上記のreturnステートメントでは、いくつかの内部関数も参照されています。これらは次のとおりです。

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

switchFields上部と下部のコインを切り替えます。これは、ユーザーがスワップボタンを押すか、ダイアログで反対のトークンを選択したときに呼び出されます(たとえば、coin1がTokenAで、ユーザーがcoin2を選択するときにTokenBを選択した場合)。

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

handleChangeはReactJSで使用される一般的な内部関数であり、HTMLイベントを受け取り、データを引き出して状態変数に入れます。coinFiedここでは、最初の値が変更されたときにfield1の値を設定するために使用されていました。

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

formatBalance/formatReserveアカウントの残高/予約を素敵で読みやすいものに変えます:

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

isButtonEnabledボタンを有効にするかどうかを決定します。

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/onToken2Selectedは、coin1 / coin2のダイアログウィンドウが終了したときに呼び出され、関連する状態変数を設定します。

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,
        });
      });
    }
};

swapイーサリアム関数を呼び出してswapTokensスワップを作成し、必要な状態変数をリセットします。

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,
        });
      });
};

スワップ関数の16行目でenqueueSnackbarは、ノードモジュールNotistackのコンポーネントであるを使用しています。Notistackは、一時的な通知を行うための優れたライブラリです。ここでリポジトリを確認してください。

useEffectフック

最後にCoinSwapper/CoinSwapper.js、には4つのuseEffectフックがあり、これらはアプリケーションを最新の変更で最新の状態に保つために使用されます。各フック内のラムダ(コード)は、依存関係の1つが変更されたときに実行されます。依存関係は、ラムダ式の後に関数に渡される変数の配列で定義されます。

1つ目useEffectは、状態変数coin1.addressまたはcoin2.address変更のいずれかが発生したときに呼び出されます。これは、ユーザーが変換する別のコインを選択した場合、またはコインが交換された場合に、新しい準備金が計算されることを意味します。

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]);

2番目のフックは、状態変数field1Value coin1.addressまたはcoin2.address変更のいずれかが発生したときに呼び出されます。状態変数の計算と設定を試みますfield2Value。これは、ユーザーが変換ボックスに新しい値を入力した場合、または変換率が変更された場合、出力ボックスの値が変更されることを意味します。

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]);

3番目のフックは、約10秒ごとに実行されるタイムアウトを作成します。その役割は、ユーザーの残高が更新および変更されたかどうかを確認することです。これにより、残高の出力を確認することで、トランザクションがいつ完了するかを確認できます。

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);
});

最後のフックは、コンポーネントが最初にマウントされたときに実行されます。アカウントを設定するために使用されます。

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(),
      ];
    }
  }
}

結論

このブログの最初のセクションでは、ブロックチェーンへの呼び出しとトランザクションを行うために必要なイーサリアム関数について説明しました。次のセクションでは、メインのスワップ機能を機能させるために必要なカスタムコンポーネント、内部関数、およびフックについて説明しました。

流動性の削除/展開機能についてはまだ触れていません。それが次のブログのトピックです

著者にチップを渡すには、次のイーサリアムアドレスを使用できます:0xe7EeDB184E63Fe049EebA79EfeAc72939cB1461D 

このストーリーは、もともとhttps://medium.com/clearmatics/how-i-made-a-uniswap-interface-from-scratch-b51e1027ca87で公開されました

#uniswap #ethereum #token #bitcoin