Learn how to build on the decentralized web
MPC Wallet
React Native Guide

Tutorial for a wallet using MPC

The goal of this tutorial is to create a simple app that demonstrate how to use the pier MPC library:

  • ⛏️ using a simple create expo app
  • 🚧 using fix username and password for authentication
  • 📝 user can sign transactions
  • ⛓️ user can interact with ETH and BTC

Initialize a new app

npx create-expo-app -t expo-template-blank-typescript MyMPCApp
 
cd MyMPCApp
 
npm run ios
# or
npm run android

Add dependencies

Add dependencies for the MPC library

npm i @pier-wallet/mpc-lib

Add dependencies to make node stuff work on react native (polyfills)

npm i @craftzdog/react-native-buffer stream-browserify react-native-quick-crypto react-native-quick-base64 react-native-url-polyfill

Update configs

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "module-resolver",
        {
          root: ["./"],
          alias: {
            crypto: "react-native-quick-crypto",
            stream: "stream-browserify",
            buffer: "@craftzdog/react-native-buffer",
          },
        },
      ],
    ],
  };
};

Apply polyfills

App.tsx
import "react-native-url-polyfill/auto";

Generate a key share (2 out of 3)

Add pier MPC SDK

Wrap your app in pier MPC provider. Important: make sure you import PierMpcProvider before you import any Component that uses it (e.g. Mpc.tsx). This is needed because PierMpcProvider includes some polyfills.

App.tsx
import { PierMpcProvider } from "@pier-wallet/mpc-lib/dist/package/react-native";
 
import React from "react";
import Mpc from "./Mpc";
 
export default function App() {
  return (
    <PierMpcProvider>
      {/* Mpc is a simple page that we will create later */}
      <Mpc />
    </PierMpcProvider>
  );
}

Create a simple MPC wallet page

Mpc.tsx
import { KeyShare, SessionKind } from "@pier-wallet/mpc-lib";
import { PierMpcEthereumWallet } from "@pier-wallet/mpc-lib/dist/package/ethers-v5";
import { usePierMpc } from "@pier-wallet/mpc-lib/dist/package/react-native";
import { Button, View, Text } from "react-native";
import { useEffect, useState } from "react";
import { ethers } from "ethers";
 
export default function Mpc() {
  const pierMpcVaultSdk = usePierMpc();
 
  const [keyShare, setKeyShare] = useState<KeyShare | null>(null);
  const [isLoading, setIsLoading] = useState(false);
 
  const generateKeyShare = async () => {
    // we'll add this later
  };
 
  const [ethWallet, setEthWallet] = useState<PierMpcEthereumWallet | null>(
    null,
  );
  useEffect(() => {
    // we'll add this later
  }, [keyShare]);
 
  const sendEthereumTransaction = async () => {
    // we'll add this later
  };
 
  return (
    <>
      <View style={{ flex: 1, paddingTop: 100 }}>
        <Button
          title="Sign in as Test (fix)!"
          onPress={async () => {
            await pierMpc.auth.signInWithPassword({
              email: "mpc-lib-test@example.com",
              password: "123456",
            });
 
            console.log("signed in as test user");
          }}
        />
        <Button
          title="Generate Key Share"
          onPress={generateKeyShare}
          disabled={isLoading}
        />
        <Text selectable>ETH Address: {ethWallet?.address}</Text>
        <Text>PublicKey: {keyShare?.publicKey.join(",")}</Text>
        <Button
          title="Send Ethereum"
          onPress={sendEthereumTransaction}
          disabled={isLoading}
        />
      </View>
    </>
  );
}

Generate the keyshare

This involves complex math and will take around 4 seconds.

Mpc.tsx
import { KeyShare, SessionKind } from "@pier-wallet/mpc-lib";
// ...
export default function Mpc() {
  // ...
  const generateKeyShare = async () => {
    try {
      console.log("generating local key share...");
      const [mainKeyShare, backupKeyShare] = await pierMpc.generateKeyShare2of3();
 
      console.log("local key share generated.", mainKeyShare.publicKey);
      // you should save backupKeyShare as well but we will focus only on the mainKeyShare in this tutorial
      setKeyShare(mainKeyShare);
    } catch (e) {
      console.error(e);
    }
  };
 
  return (
    <>
      <View style={{ flex: 1, paddingTop: 100 }}>
        <Button
          title="Generate Key Share"
          onPress={generateKeyShare}
          disabled={isLoading}
        />
        <Text>PublicKey: {keyShare?.publicKey.join(",")}</Text>
      </View>
    </>
  );
}

Ethereum wallet

Install ethers to work with Ethereum - ethereum wallet is based on ethers v5 and implements ethers.Signer API.

npm i ethers@^5.7.2

Create a wallet

Mpc.tsx
import { ethers } from "ethers";
// REMARK: Use should use your own ethers provider - this is just for demo purposes
const ethereumProvider = new ethers.providers.JsonRpcProvider(
  "https://rpc.sepolia.org"
);
 
export default function Mpc() {
  // ...
 
  const [ethWallet, setEthWallet] = useState<PierMpcEthereumWallet | null>(
    null
  );
  useEffect(() => {
    if (!keyShare) return;
    (async () => {
      const signConnection = await pierMpc.establishConnection(
        SessionKind.SIGN,
        keyShare.partiesParameters,
      );
      const ethWallet = new PierMpcEthereumWallet(
        keyShare,
        signConnection,
        pierMpc,
        ethereumProvider,
      );
      setEthWallet(ethWallet);
    })();
  }, [keyShare]);
 
  return (
    // ...
    <Text>Text selectable>ETH Address: {ethWallet?.address}</Text>
    // ...
  );
}

Send Ethereum Transaction

Important: you need to fund the address that is shown on the screen on the ethereum sepolia blockchain. You can use https://sepoliafaucet.com/ (opens in a new tab) to do this

Important: you might expect failing transactions if you don't pick a stable RPC provider like alchemy

Important: This transaction will take around 4 seconds.

Mpc.tsx
// ...
 
export default function Mpc() {
  //  ...
  const sendEthereumTransaction = async () => {
    if (!ethWallet) return;
 
    setIsLoading(true);
    try {
      // send 1/10 of the balance to the zero address
      const receiver = ethers.constants.AddressZero;
      const balance = await ethWallet.getBalance();
      const amountToSend = balance.div(10);
 
      // sign the transaction locally & send it to the network once we have the full signature
      const tx = await ethWallet.sendTransaction({
        to: receiver,
        value: amountToSend,
      });
      console.log("tx", tx.hash);
    } catch (e) {
      console.error(e);
    } finally {
      setIsLoading(false);
    }
  };
 
  return (
    // ...
    <Button
      title="Send Ethereum"
      onPress={sendEthereumTransaction}
      disabled={isLoading}
    />
    // ...
  );
}

Bitcoin wallet

Create a Bitcoin wallet

Mpc.tsx
import {
  PierMpcBitcoinWallet,
  PierMpcBitcoinWalletNetwork,
} from "@pier-wallet/mpc-lib/dist/package/bitcoin";
// ...
 
export default function Mpc() {
  // ...
  const [btcWallet, setBtcWallet] = useState<PierMpcBitcoinWallet | null>(null);
  useEffect(() => {
    if (!keyShare) return;
    (async () => {
      const signConnection = await pierMpc.establishConnection(
        SessionKind.SIGN,
        keyShare.partiesParameters,
      );
      const btcWallet = new PierMpcBitcoinWallet(
        keyShare,
        PierMpcBitcoinWalletNetwork.Testnet,
        signConnection,
        pierMpc,
      );
      setBtcWallet(btcWallet);
    })();
  }, [keyShare]);
 
  return (
    // ...
    <Text selectable>BTC Address: {btcWallet?.address}</Text>
    // ...
  );
}

Send Bitcoin Transaction

Important: you need to fund the address that is shown on the screen on the bitcoin test blockchain. You can use https://bitcoinfaucet.uo1.net/send.php (opens in a new tab) to do this

This involves complex math and will take around 4 seconds.

Mpc.tsx
export default function Mpc() {
  // ...
 
  const sendBitcoinTransaction = async () => {
    if (!btcWallet) return;
 
    setIsLoading(true);
    try {
      const receiver = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6"; // testnet faucet
      const amountToSend = 800n; // 0.00000800 BTC = 800 satoshi
      const feePerByte = 1n; // use a fee provider to get a more accurate fee estimate - otherwise check minimum fee manually
 
      // create a transaction request
      const txRequest = await btcWallet.populateTransaction({
        to: receiver,
        value: amountToSend,
        feePerByte,
      });
 
      // sign the transaction locally & send it to the network once we have the full signature
      const tx = await btcWallet.sendTransaction(txRequest);
      console.log("tx", tx.hash);
    } catch (e) {
      console.error(e);
    } finally {
      setIsLoading(false);
    }
  };
 
  return (
    // ...
    <Button
      title="Send Bitcoin"
      onPress={sendBitcoinTransaction}
      disabled={isLoading}
    />
    // ...
  );
}