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