@renproject/ren-react

Easily create and track RenVM transactions in React

Usage no npm install needed!

<script type="module">
  import renprojectRenReact from 'https://cdn.skypack.dev/@renproject/ren-react';
</script>

README

Ren React

ren-react provides React Hooks and configurable components for initiating and processing ren-js LockAndMint & BurnAndRelease transactions.

Installation

Install ren-react and its peer dependencies:

yarn add @renproject/ren @renproject/ren-react @renproject/ren-tx @xstate/react xstate
# Or
npm install --save @renproject/ren @renproject/ren-react @renproject/ren-tx @xstate/react xstate

Usage

See the /example folder for a working demo. Check /example/index.html to swap between the different examples

With predefined components

import * as React from "react";
import * as ReactDOM from "react-dom";
import { useEffect, useMemo, useState } from "react";

import RenJS from "@renproject/ren";
import { BasicMint, BasicBurn, BurnProps } from "@renproject/ren-react";
import { Ethereum } from "@renproject/chains-ethereum";
import { Zcash } from "@renproject/chains-bitcoin";
import ethers from "ethers";
import { RenNetwork } from "@renproject/interfaces";

const BasicBurnApp = ({ account, provider, destinationAddress, balance }) => {
    const parameters = useMemo(
        () => ({
            sdk: new RenJS("testnet"),
            burnParams: {
                sourceAsset: "ZEC",
                network: RenNetwork.Testnet,
                targetAmount: balance,
                destinationAddress,
            },
            from: Ethereum(provider, "testnet").Account({
                address: account,
                value: balance,
            }),
            to: Zcash().Address(destinationAddress),
        }),
        [provider, account, balance],
    );
    return <BasicBurn parameters={parameters} />;
};

const BasicMintApp = ({ account, provider }) => {
    const parameters = useMemo(
        () => ({
            sdk: new RenJS("testnet"),
            mintParams: {
                sourceAsset: "ZEC",
                network: RenNetwork.Testnet,
                destinationAddress: account,
            },
            to: Ethereum(provider).Account({ address: account }),
            from: Zcash(),
        }),
        [provider, account],
    );
    return <BasicMint parameters={parameters} />;
};

const WithProvider = () => {
    const [provider, setProvider] = useState<any>();
    const [account, setAccount] = useState<string>();
    useEffect(() => {
        (window as any).ethereum.enable().then(async () => {
            const ethProvider = new ethers.providers.Web3Provider(
                (window as any).ethereum,
            );
            setAccount((await ethProvider.listAccounts())[0]);
            setProvider((window as any).ethereum);
        });
    }, []);

    const [balance, setBalance] = useState<string>();
    useEffect(() => {
        if (!provider) return;
        Ethereum(provider, "testnet")
            .getBalance("ZEC", account)
            .then((v) => setBalance(v.minus(1000).toString()));
    }, [provider, setBalance]);

    if (!provider || !account || !balance) {
        return <div>Connect Wallet</div>;
    }

    return (
        <div>
            <div>
                <h2>Basic Mint</h2>
                <BasicMintApp provider={provider} account={account} />
            </div>
            <div>
                <h2>Basic Burn</h2>
                <div>Zec Balance: {balance}</div>
                <BasicBurnApp
                    provider={provider}
                    account={account}
                    destinationAddress={"tmCZ74c41byQKyVsA6xc8jMwXbQxKU16nJT"}
                    balance={200000}
                />
            </div>
        </div>
    );
};

ReactDOM.render(<WithProvider />, document.getElementById("root"));

Sample with custom components using hooks

import * as React from "react";
import * as ReactDOM from "react-dom";

import {
    useDeposit,
    useLockAndMint,
    useBurnAndRelease,
    BurnStates,
    DepositStates,
    isBurnErroring,
    isOpen,
} from "@renproject/ren-react";
import RenJS from "@renproject/ren";
import { Ethereum } from "@renproject/chains-ethereum";
import { Zcash } from "@renproject/chains-bitcoin";
import ethers from "ethers";
import { useEffect, useMemo, useState } from "react";
import { RenNetwork } from "@renproject/interfaces";

const BurnApp = ({ account, provider, destinationAddress, balance }) => {
    const parameters = useMemo(
        () => ({
            sdk: new RenJS("testnet"),
            burnParams: {
                sourceAsset: "ZEC",
                network: RenNetwork.Testnet,
                destinationAddress,
                targetAmount: balance,
            },
            from: Ethereum(provider, "testnet").Account({
                address: account,
                value: balance,
            }),
            to: Zcash().Address(destinationAddress),
        }),
        [provider, account, balance],
    );

    const { value, session, burn, tx } = useBurnAndRelease(parameters);
    switch (value) {
        case BurnStates.CREATED:
            return (
                <button onClick={burn}>
                    Burn and release {Number(balance) / 10 ** 8}{" "}
                    {session.sourceAsset} to
                    {destinationAddress}
                </button>
            );
        case BurnStates.CONFIRMING_BURN:
            if (!tx) return <div>loading</div>;
            return (
                <div>
                    Waiting for burn confirmation {tx.sourceTxConfs} /
                    {tx.sourceTxConfTarget}
                </div>
            );
        case BurnStates.RENVM_RELEASING:
            return <div>Submitting to RenVM</div>;
        case BurnStates.RENVM_ACCEPTED:
            return <div>Releasing</div>;
        case BurnStates.RELEASED:
            return <div>Released</div>;
        case BurnStates.ERROR_BURNING:
            if (!isBurnErroring(session)) return <div>loading</div>;
            return <div>Couldn't burn: {session.error.message}</div>;
        case BurnStates.ERROR_RELEASING:
            return <div>Rejected</div>;
        default:
            return <div>Loading</div>;
    }
};

const MintApp = ({ account, provider }) => {
    const parameters = useMemo(
        () => ({
            sdk: new RenJS("testnet"),
            mintParams: {
                sourceAsset: "ZEC",
                network: RenNetwork.Testnet,
                destinationAddress: account,
            },
            to: Ethereum(provider).Account({ address: account }),
            from: Zcash(),
        }),
        [provider, account],
    );
    const mint = useLockAndMint(parameters);
    if (!isOpen(mint.session)) return <div>Loading</div>;
    return (
        <div>
            Deposit {mint.session.sourceAsset} at {mint.session.gatewayAddress}
            {mint.deposits.map((x) => (
                <Deposit
                    key={x}
                    session={mint}
                    depositId={x}
                    currency={mint.session.sourceAsset}
                />
            ))}
        </div>
    );
};

const Deposit: React.FC<{
    session: ReturnType<typeof useLockAndMint>;
    depositId: string;
    currency: string;
}> = ({ session, depositId, currency }) => {
    const machine = useDeposit(session, depositId);
    if (!machine) return <div>Missing deposit...</div>;
    const { state, mint } = machine;
    if (state.matches(DepositStates.CONFIRMING_DEPOSIT)) {
        const deposit = state.context.deposit;
        return (
            <div>
                Waiting for deposit confirmation {deposit.sourceTxConfs}/
                {deposit.sourceTxConfTarget}
            </div>
        );
    }
    if (state.matches(DepositStates.RENVM_SIGNING)) {
        return <div>`Submitting to RenVM`</div>;
    }
    if (state.matches(DepositStates.RENVM_ACCEPTED)) {
        return (
            <button onClick={mint}>
                Mint {state.context.deposit.sourceTxAmount} {currency}?
            </button>
        );
    }
    if (state.matches(DepositStates.SUBMITTING_MINT)) {
        return <div>Minting...</div>;
    }
    if (
        state.matches(DepositStates.MINTING) ||
        state.matches(DepositStates.COMPLETED)
    ) {
        return (
            <div>Successfully minted: {state.context.deposit.destTxHash}</div>
        );
    }
    if (
        state.matches(DepositStates.ERROR_MINTING) ||
        state.matches(DepositStates.ERROR_SIGNING) ||
        state.matches(DepositStates.ERROR_RESTORING)
    ) {
        return (
            <div>
                Error processing: {state.context.deposit.error}; please refresh
            </div>
        );
    }
    if (state.matches(DepositStates.REJECTED)) {
        return <div>Deposit rejected {state.context.deposit.error}</div>;
    }
    return (
        <div>
            State: {state} id: {state.context.deposit.sourceTxHash}
        </div>
    );
};

const WithProvider = () => {
    const [provider, setProvider] = useState<any>();
    const [account, setAccount] = useState<string>();
    useEffect(() => {
        (window as any).ethereum.enable().then(async () => {
            const ethProvider = new ethers.providers.Web3Provider(
                (window as any).ethereum,
            );
            setAccount((await ethProvider.listAccounts())[0]);
            setProvider((window as any).ethereum);
        });
    }, []);

    const [balance, setBalance] = useState<string>();
    useEffect(() => {
        if (!provider) return;
        Ethereum(provider, "testnet")
            .getBalance("ZEC", account)
            .then((v) => setBalance(v.minus(1000).toString()));
    }, [provider, setBalance]);

    if (!provider || !account || !balance) {
        return <div>Connect Wallet</div>;
    }

    return (
        <div>
            <div>
                <h2>Mint</h2>
                <MintApp provider={provider} account={account} />
            </div>
            <div>
                <h2>Burn</h2>
                <div>Zec Balance: {balance}</div>
                <BurnApp
                    provider={provider}
                    account={account}
                    destinationAddress={"tmCZ74c41byQKyVsA6xc8jMwXbQxKU16nJT"}
                    balance={200000}
                />
            </div>
        </div>
    );
};

ReactDOM.render(<WithProvider />, document.getElementById("root"));