0.3.1 • Published 9 months ago

react-ic-wallet v0.3.1

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

React IC Wallet

NPM CI

React IC Wallet is a Simplistic Context provider in order to manage Internet Computer wallets in the browser. It is heavily inspired by Metamask react.

Supported wallets

Currently, React-ic-wallet supports the following browser wallets:

Usage

Wrap your application

Wrap your application around the IcWalletProvider

import { IcWalletProvider } from 'react-ic-wallet';

<IcWalletProvider>
  <App />
</IcWalletProvider>

Use a specific wallet

import { IcWalletProvider, WalletProvider } from 'react-ic-wallet';

<IcWalletProvider provider={WalletProvider.Bitfinity}>
  <App />
</IcWalletProvider>

Create a component to handle the connection

import * as React from 'react';
import { useIcWallet } from 'react-ic-wallet';

import Logo from './ConnectButton/Logo';
import Button from './reusable/Button';
import Container from './reusable/Container';

const ConnectButton = () => {
  const { status, connect, disconnect, account, principal } = useIcWallet();

  const disabled = ['initializing', 'unavailable', 'connecting'].includes(
    status,
  );

  React.useEffect(() => {
    console.log('status from ctx', status);
  }, [status]);

  const onClick = () => {
    if (status === 'notConnected') {
      return connect();
    } else if (status === 'connected') {
      return disconnect();
    }
    return undefined;
  };

  const text = () => {
    if (status === 'initializing') return 'Initializing...';
    if (status === 'unavailable') return 'IC Wallet not available';
    if (status === 'notConnected') return 'Connect to IC';
    if (status === 'connecting') return 'Connecting...';
    if (status === 'connected') return principal;
    return undefined;
  };

  return (
    <Container.FlexRow className="items-center gap-8">
      <Button.Alternative
        className="my-0 !mb-0"
        onClick={onClick}
        disabled={disabled}
      >
        <Logo className="inline w-[32px] mr-2" />
        {text()}
      </Button.Alternative>
    </Container.FlexRow>
  );
};

export default ConnectButton;

Create actor to interact with canisters

import * as React from 'react';
import { ActorMethod, ActorSubclass } from '@dfinity/agent';
import { useIcWallet } from 'react-ic-wallet';

import { icpLedgerIdlFactory } from './IcpLedger';

interface Context {
  icpLedger?: ActorSubclass<Record<string, ActorMethod>>;
}

export const AgentContext = React.createContext<Context>({
  icpLedger: undefined,
});

export const icpLedgerIdlFactory = ({ IDL: IDL }) => {
  ...
  return IDL.Service({
    icrc1_balance_of: IDL.Func([Account], [IDL.Nat], ['query']),
    icrc1_decimals: IDL.Func([], [IDL.Nat8], ['query']),
    icrc1_fee: IDL.Func([], [IDL.Nat], ['query']),
    icrc1_metadata: IDL.Func(
      [],
      [IDL.Vec(IDL.Tuple(IDL.Text, MetadataValue))],
      ['query'],
    ),
    icrc1_name: IDL.Func([], [IDL.Text], ['query']),
    icrc1_supported_standards: IDL.Func(
      [],
      [IDL.Vec(TokenExtension)],
      ['query'],
    ),
    icrc1_symbol: IDL.Func([], [IDL.Text], ['query']),
    icrc1_total_supply: IDL.Func([], [IDL.Nat], ['query']),
    icrc1_transfer: IDL.Func([TransferArg], [Result_5], []),
    icrc2_allowance: IDL.Func([AllowanceArgs], [Allowance], ['query']),
    icrc2_approve: IDL.Func([ApproveArgs], [Result_6], []),
    icrc2_transfer_from: IDL.Func([TransferFromArgs], [Result_7], []),
  });
}

const AgentContextProvider = ({ children }: { children: React.ReactNode }) => {
  const [icpLedger, setIcpLedger] =
    React.useState<ActorSubclass<Record<string, ActorMethod>>>();
  const { createActor, status } = useIcWallet();

  React.useEffect(() => {
    if (status === 'connected') {
      createActor('ryjl3-tyaaa-aaaaa-aaaba-cai', icpLedgerIdlFactory)
        .then((actor) => {
          if (actor) {
            setIcpLedger(actor);
          }
        })
        .catch((err) => {
          console.error(err);
        });
    }
  }, [status]);

  return (
    <AgentContext.Provider
      value={{
        icpLedger,
      }}
    >
      {children}
    </AgentContext.Provider>
  );
};

export default AgentContextProvider;

Access methods once connected

import * as React from 'react';
import { useIcWallet } from 'react-ic-wallet';

const Header = () => {
  const { icpLedger } = React.useContext(AgentContext);
  const { principal } = useConnectedIcWallet();
  const [balance, setBalance] = React.useState<string>('0');

  React.useEffect(() => {
    if (icpLedger) {
      icpLedger
        .icrc1_balance_of({
          owner: Principal.fromText(principal),
          subaccount: [],
        })
        .then((balance) => {
          setBalance((balance as bigint).toString());
        })
        .catch((e) => {
          console.error(e);
        });
    }
  }, [icpLedger]);

  return (
    <Container.FlexCols>
      <span>User balance (ICP): {balance}</span>
    </Container.FlexCols>
  );
};

export default Header;

Example

Find more in the example in the examples/ directory, where we use the ic wallet context provider to query the ICP ledger canister.

License

react-ic-wallet is licensed under MIT.

See full license HERE.