Build A Berachain Mobile dApp With WalletConnect & Expo
How To Build A Mobile Web3 dApp With WalletConnect & React Native Using Expo
🛠️ Let’s Build A Mobile dApp On Berachain
Most web3 dApps are typically built for web and don’t have much love for mobile. In this tutorial we’re going to change that by building an iOS app with Expo that works with Berachain.
Our mobile dApp will be able to connect our MetaMask mobile wallet, sign a message, and deploy the bytecode for a HelloWorld contract to Berachain Artio (Berachain Testnet).
Why Expo?
Expo has been gaining popularity in the React Native space for mobile development and three main reasons why developers are choosing Expo over Native development or just React Native are:
- Support for multiple platforms — iOS and Android
- Prebuilt components and functionality
- Expo EAS (Expo Application Service) which allows for immediate updates versus going through AppStore approvals for each update
WalletConnect & Mobile
WalletConnect has been a staple for most web3 dApps and making sure projects are setup to support has many wallets, while giving easy tooling for developers to get out-of-the-box wallet connection integration with their SDKs.
WalletConnect also has a mobile specific SDK that allows for wallet connections and interactions to happen directly on your mobile device. This is the SDK that we’ll be using for this tutorial.
📋 Requirements
Make sure to have the following installed or setup prior to continuing.
NOTE: For the purposes of this tutorial we’ll be focusing on iOS
- NVM or Node
v20.11.0
- Expo Go
- Xcode with iOS Simulator
- iOS MetaMask Mobile Wallet
- Berachain
$BERA
tokens — (Need Berachain faucet tokens?)
💻 Pre-Step iOS Simulator On Sonoma
With the latest Mac OS Sonoma and Xcode, Apple has decided to create an extra step that would require developers to download a specific runtime simulator that may not be pre-installed.
To start, run Xcode, and go to Window > Devices & Simulators. This will open up a new window. Select Simulators in the top left, and from the dropdown for Device Type select iPhone 15 Pro Max
and for OS Version choose Download more simulator runtimes…
.
This will open another window to then download iOS 17.2
.
Wait until this download completes and double check that your Simulator.app has successfully downloaded by running:
open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app;
📱Building A Berachain Mobile dApp
Now that we our iOS Simulator setup, we should be good to build out our entire mobile application.
WalletConnect Project ID
Before we start coding, we’ll need to make sure that we setup a WalletConnect Project ID at https://cloud.walletconnect.com/app.
If you already have a project id, you can skip this step.
Go the WalletConnect Cloud Sign-In to create a new account.
When in the dashboard, create a new project.
Enter the details of your project.
Get your project id, we’ll need this for later.
Project Setup
Now that we have our WalletConnect project id, let’s create a base expo project and add our needed dependencies.
npx create-expo-app berachain-walletconnect-expo -t;
# [Expected Prompt & Output]:
# ? Choose a template: › - Use arrow-keys. Return to submit.
# Blank
# ❯ Blank (TypeScript) - blank app with TypeScript enabled
# Navigation (TypeScript)
# Blank (Bare)
# ...
# ✅ Your project is ready!
#
# To run your project, navigate to the directory and run one of the following npm commands.
#
# - cd berachain-walletconnect-expo
# - npm run android
# - npm run ios
# - npm run web
Install our needed dependencies.
NOTE: We’ll be using
pnpm
which has been known to have some issues with expo. We also recommend usingyarn
with expo whenever possible.
cd berachain-walletconnect-expo;
pnpm add @web3modal/wagmi-react-native wagmi@1.4.13 viem@1.21.4 @react-native-async-storage/async-storage react-native-get-random-values react-native-svg react-native-modal @react-native-community/netinfo @walletconnect/react-native-compat expo-application expo-linking;
# If in a monorepo / turbo repo, run the following right after in the dir
# pnpm install --ignore-workspace;
# @web3modal/wagmi-react-native - walletconnect react native web3modal
# wagmi@1.4.13 - web3 react hooks (currently only supports v1)
# viem@1.21.4 # js library for interacting with EVM (currently only support
# @react-native-async-storage/async-storage - for local device storage
# react-native-get-random-values - local random val generator
# react-native-svg - native svg support
# react-native-modal - modals support
# @react-native-community/netinfo - api for network info for device
# @walletconnect/react-native-compat - shims / polyfills for additional support
# expo-application - for native app information
# expo-linking - allows for external links to open the browser
Let’s test that our app is working correctly — hint it won’t 😅.
Note: This is an issue specifically with
pnpm
. If you did everything withyarn
then this won’t happen.
# FROM: ./berachain-walletconnect-expo;
pnpm ios;
# [Expected Output]:
#
⚠️ The PNPM Expo Fix
If you’re using yarn
you can still make these modifications and it’ll work for all package managers.
To fix this specific pnpm
issue, we’ll need to make 3 modifications to our project.
1 — Install Babel Runtime Dependency
# FROM: ./berachain-walletconnect-expo;
pnpm add -D @babel/runtime;
2 — Modify Package JSON App Entry
File: ./package.json
{
"name": "berachain-walletconnect-expo",
"version": "1.0.0",
- "main": "node_modules/expo/AppEntry.js",
+ "main": "index.ts",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.22.1",
"@react-native-community/netinfo": "^11.3.0",
"@walletconnect/react-native-compat": "^2.11.1",
"@web3modal/wagmi-react-native": "^1.2.0",
"expo": "~50.0.7",
"expo-application": "^5.8.3",
"expo-status-bar": "~1.11.1",
"react": "18.2.0",
"react-native": "0.73.4",
"react-native-get-random-values": "^1.10.0",
"react-native-modal": "^13.0.1",
"react-native-svg": "^14.1.0",
"viem": "1.21.4",
"wagmi": "1.4.13"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "~18.2.45",
"typescript": "^5.1.3"
},
"private": true
}
3 — Add New Entry File & Move Our App.tsx
# FROM: ./berachain-walletconnect-expo;
touch index.ts;
mkdir app;
mv App.tsx app;
File: ./index.ts
// Imports
// ========================================================
import { registerRootComponent } from "expo";
// Main App
// ========================================================
import App from "./app/App";
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
Let’s try this again, with pnpm ios
and we should get the following working. This should also work with yarn
or npm
as well.
Styling Our Project
For styling, we’re going to use Tailwind with Nativewind to keep that same consistent styling with all of our app.
These next few steps, are a few but once it’s setup, it’ll make sure that we have Tailwind configured correctly.
# FROM: ./berachain-walletconnect-expo;
pnpm add nativewind@^4.0.1 tailwindcss react-native-css-interop;
npx tailwindcss init;
# Move our main App.tsx to an app directory for better tailwind changes
mkdir app;
mv App.tsx app/;
Make the appropriate modification to our newly generated tailwind.config.js
file.
File: ./tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}"
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
}
Create a new global.css
and add Tailwind styles.
# FROM: ./berachain-walletconnect-expo;
touch global.css;
File: ./global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
.App {
@apply bg-[#F47226] text-[#2E1E1A] flex w-full h-full items-center justify-center;
}
.H1 {
@apply text-lg mb-2 block font-semibold text-[#121312] text-center;
}
.Text {
@apply mb-2 text-[#2E1E1A] block;
}
.TextInput {
@apply bg-white text-base h-12 px-2 align-text-top rounded w-full mb-4;
}
.TextError {
@apply text-red-800 bg-red-200 mb-2 text-base p-4;
}
.Button {
@apply bg-[#2E1E1A] h-12 flex items-center justify-center rounded-lg mb-4 w-full;
}
.Code {
@apply block bg-[#ff843d] whitespace-nowrap overflow-scroll mb-4 text-[#874c2a] text-base h-12 leading-[3rem] px-2 rounded w-full;
}
.Connect {
@apply my-4 block;
}
.Balance {
@apply block w-full px-8;
}
.SignMessage {
@apply block w-full px-8;
}
.Deploy {
@apply block w-full px-8;
}
}
Modify our babel.config.js
to support nativewind
.
File: ./babel.config.js
module.exports = (api) => {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
Generate a new metro.config.js
file which will help with build.
# FROM: ./berachain-walletconnect-expo;
npx expo customize metro.config.js;
Make some modifications to that config file.
File: ./metro.config.js
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
+ const { withNativeWind } = require('nativewind/metro');
/** @type {import('expo/metro-config').MetroConfig} */
- const config = getDefaultConfig(__dirname);
+ const config = getDefaultConfig(__dirname, { isCSSEnabled: true })
- module.exports = config;
+ module.exports = withNativeWind(config, { input: './global.css' });
Let’s modify our main App.tsx
and replace everything with the following code.
File: ./app/App.tsx
// Imports
// ========================================================
import { StatusBar } from 'expo-status-bar';
import { Text, View } from 'react-native';
import '../global.css';
// Main App Component
// ========================================================
export default function App() {
return (
<View className="App">
<StatusBar style="auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
</View>
);
};
Run our app again and we should see the following.
Let’s also add an SVG logo to our app as a separate component.
# FROM: ./berachain-walletconnect-expo;
mkdir components;
mkdir components/Icons;
touch components/Icons/index.tsx;
Add the following code to our new icons file.
File: ./components/Icons/index.tsx
// Imports
// ========================================================
import Svg, { Path } from "react-native-svg";
// Icons
// ========================================================
export const Logo = ({ className = '', width = 128, height = 128 }) => <Svg className={className} width={width} height={height} viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<Path d="M477.298 474.489C476.946 472.605 476.506 470.802 475.801 469.084C476.066 468.675 534.55 392.929 480.381 346.092C426.299 299.252 363.145 360.339 362.793 360.667C352.751 357.718 342.622 355.918 332.581 355.099C332.581 355.099 332.581 355.099 332.493 355.099C311.882 351.906 282.99 355.099 282.99 355.099C273.037 355.918 262.996 357.718 253.042 360.585C252.69 360.258 189.536 299.17 135.454 346.01C81.3722 392.847 139.77 468.675 140.034 469.002C139.418 470.802 138.889 472.605 138.537 474.407C132.724 506.833 92.8228 516.823 92.8228 573.325C92.8228 630.891 134.485 676.256 219.572 676.256H254.452C254.628 676.42 268.106 694.27 295.938 695.335C295.938 695.335 302.369 695.99 317.165 695.499C346.673 695.499 361.031 676.583 361.12 676.338H395.999C481.085 676.338 522.748 630.972 522.748 573.407C523.012 516.987 483.111 506.915 477.298 474.489Z" fill="#F9F4D5" />
<Path d="M692.83 628.84V584.622C720.048 575.287 739.867 546.3 739.867 511.99C739.867 477.679 720.048 448.61 692.83 439.355V395.137C718.463 386.376 737.577 360.092 739.69 328.319H708.51V341.83C708.51 352.229 702.167 361.4 692.83 365.986V359.682C692.83 350.429 684.726 342.894 674.773 342.894H673.188C663.234 342.894 655.133 350.429 655.133 359.682V365.986C645.796 361.4 639.453 352.311 639.453 341.83V328.319H608.272C610.386 360.092 629.5 386.376 655.133 395.137V439.355C628.003 448.61 608.096 477.598 608.096 511.99C608.096 546.382 627.915 575.369 655.133 584.622V628.84C629.5 637.602 610.386 663.888 608.272 695.658H639.453V682.148C639.453 671.748 645.796 662.577 655.133 657.992V664.297C655.133 673.55 663.234 681.083 673.188 681.083H674.773C684.726 681.083 692.83 673.55 692.83 664.297V657.992C702.167 662.577 708.51 671.666 708.51 682.148V695.658H739.69C737.577 663.888 718.463 637.602 692.83 628.84ZM639.453 531.315V492.583C639.453 482.183 645.796 473.012 655.133 468.427V474.73C655.133 483.983 663.234 491.518 673.188 491.518H674.773C684.726 491.518 692.83 483.983 692.83 474.73V468.427C702.167 473.012 708.51 482.101 708.51 492.583V531.315C708.51 541.714 702.167 550.885 692.83 555.471V549.165C692.83 539.912 684.726 532.38 674.773 532.38H673.188C663.234 532.38 655.133 539.912 655.133 549.165V555.471C645.884 550.885 639.453 541.796 639.453 531.315Z" fill="#F9F4D5" />
<Path d="M884.142 535.49V488.407C911.358 479.07 931.176 450.083 931.176 415.773C931.176 381.462 911.358 352.393 884.142 343.14V328.319H846.53V343.14C819.315 352.475 799.496 381.462 799.496 415.773C799.496 450.083 819.315 479.152 846.53 488.407V535.49C819.315 544.825 799.496 573.813 799.496 608.123C799.496 642.433 819.315 671.502 846.53 680.755V695.576H884.142V680.755C911.358 671.42 931.176 642.433 931.176 608.123C931.176 573.813 911.358 544.825 884.142 535.49ZM830.765 435.097V396.366C830.765 385.966 837.106 376.795 846.442 372.21V378.515C846.442 387.768 854.546 395.301 864.5 395.301H865.997C875.95 395.301 884.054 387.768 884.054 378.515V372.21C893.391 376.795 899.731 385.884 899.731 396.366V435.097C899.731 445.497 893.391 454.668 884.054 459.254V452.95C884.054 443.697 875.95 436.162 865.997 436.162H864.412C854.458 436.162 846.354 443.697 846.354 452.95V459.254C837.194 454.668 830.765 445.579 830.765 435.097ZM899.819 627.53C899.819 637.929 893.479 647.1 884.142 651.686V642.433C884.142 633.18 876.038 625.648 866.085 625.648H864.588C854.634 625.648 846.53 633.18 846.53 642.433V651.686C837.194 647.1 830.853 638.011 830.853 627.53V588.798C830.853 578.398 837.194 569.227 846.53 564.642V567.998C846.53 577.253 854.634 584.786 864.588 584.786H866.173C876.126 584.786 884.23 577.253 884.23 567.998V564.642C893.567 569.227 899.907 578.316 899.907 588.798V627.53H899.819Z" fill="#F9F4D5" />
</Svg>
Next we just need to modify our main App.tsx
file to include the logo.
File: ./app/App.tsx
// Imports
// ========================================================
import { StatusBar } from 'expo-status-bar';
import { Text, View } from 'react-native';
import '../global.css';
+ import { Logo } from '../components/Icons';
// Main App Component
// ========================================================
export default function App() {
return (
<View className="App">
<StatusBar style="auto" />
+ <Logo className="w-auto h-10 mx-auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
</View>
);
};
WalletConnect Web3Modal
The next step we’re going to do is add WalletConnect’s Web3Modal support into our Expo app.
The first step will be to configure our environment variables.
Note: Make sure not to commit this to our git repository
# FROM: ./berachain-walletconnect-expo;
touch .env;
Add the following contents and remember to replace the WALLET_CONNECT_PROJECT_ID
with the newly created project from WalletConnect Cloud directly.
File: ./.env
# Expo Metadata
EXPO_PUBLIC_METADATA_NAME="Berachain WalletConnect Expo"
EXPO_PUBLIC_METADATA_DESCRIPTION="Berachain WalletConnect Expo Example"
EXPO_PUBLIC_METADATA_URL="https://berachain.com"
EXPO_PUBLIC_METADATA_ICONS="https://avatars.githubusercontent.com/u/96059542"
EXPO_PUBLIC_METADATA_REDIRECT_NAME="YOUR_APP_SCHEME://"
EXPO_PUBLIC_METADATA_REDIRECT_UNIVERSAL="YOUR_APP_UNIVERSAL_LINK.com"
# WalletConnect - See https://cloud.walletconnect.com
EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID="YOUR_PROJECT_ID"
# Chain
EXPO_PUBLIC_CHAIN_ID=80085
EXPO_PUBLIC_CHAIN_NAME="berachainTestnet"
EXPO_PUBLIC_CHAIN_NETWORK="Berachain"
EXPO_PUBLIC_CHAIN_NATIVECURRENCY_DECIMALS=18
EXPO_PUBLIC_CHAIN_NATIVECURRENCY_NAME="Bera Token"
EXPO_PUBLIC_CHAIN_NATIVECURRENCY_SYMBOL="BERA"
EXPO_PUBLIC_CHAIN_RPC_URL="https://rpc.ankr.com/berachain_testnet"
EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_NAME="Beratrail"
EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL="https://artio.beratrail.io"
Now that we have this setup, we’re going wrap our main App.tsx
with some of the configured providers from WalletConnect that adds its own configuration to the wagmi
library.
# FROM: ./berachain-walletconnect-expo;
mkdir providers;
touch providers/index.tsx;
touch providers/wagmi.tsx;
First up is configuring our wagmi provider. This will ensure that we can take advantage of all of wagmi’s hooks but also some default functionality built into WalletConnet Web3Modal.
File: ./providers/wagmi.tsx
// Imports
// ========================================================
import "@walletconnect/react-native-compat";
import {
createWeb3Modal,
defaultWagmiConfig,
} from "@web3modal/wagmi-react-native";
import { defineChain } from "viem";
import { WagmiConfig } from "wagmi";
// Config
// ========================================================
// 1. Get projectId at https://cloud.walletconnect.com
const projectId = `${process.env.EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID}`;
if (!projectId) throw Error('Error: Missing `EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID`.');
// 2. Create config for our app - defined by our env vars
const metadata = {
name: `${process.env.EXPO_PUBLIC_METADATA_NAME}`,
description: `${process.env.EXPO_PUBLIC_METADATA_DESCRIPTION}`,
url: `${process.env.EXPO_PUBLIC_METADATA_URL}`,
icons: [`${process.env.EXPO_PUBLIC_METADATA_ICONS}`],
redirect: {
native: `${process.env.EXPO_PUBLIC_METADATA_REDIRECT_NATIVE}`,
universal: `${process.env.EXPO_PUBLIC_METADATA_REDIRECT_UNIVERSAL}`,
},
};
// 3. Configure our custom chain - Note this is needed for wagmi and viem v1
/**
* @dev Custom chain configuration
*/
const chainConfiguration = defineChain({
id: parseInt(`${process.env.EXPO_PUBLIC_CHAIN_ID}`),
name:`${process.env.EXPO_PUBLIC_CHAIN_NAME}`,
network: `${process.env.EXPO_PUBLIC_CHAIN_NETWORK}`,
nativeCurrency: {
decimals: parseInt(`${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_DECIMALS}`),
name: `${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_NAME}`,
symbol:`${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_SYMBOL}`,
},
rpcUrls: {
default: {
http: [`${process.env.EXPO_PUBLIC_CHAIN_RPC_URL}`],
},
public: {
http: [`${process.env.EXPO_PUBLIC_CHAIN_RPC_URL}`],
},
},
blockExplorers: {
default: {
name: `${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_NAME}`,
url: `${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL}`
},
},
});
/**
* @dev supported chains
*/
const chains = [chainConfiguration];
/**
*
*/
const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata });
// 4. Create modal configuration
createWeb3Modal({
projectId,
chains,
wagmiConfig,
});
// Provider
// ========================================================
export default function Wagmi({ children }: { children: React.ReactNode }) {
return <WagmiConfig config={wagmiConfig}>{children}</WagmiConfig>
};
Next, to keep things clean, we’ll modify our main provider file that will wrap our entire application. This helps to keep our App.tsx
file clean, and allows us to add more providers easily with our main provider wrapper.
File: ./providers/index.tsx
// Imports
// ========================================================
import WagmiProvider from "./wagmi";
// Root Provider
// ========================================================
export default function RootProvider({ children }: { children: React.ReactNode }) {
return <WagmiProvider>{children}</WagmiProvider>
};
Then we’ll want this root provider to wrap our App.tsx
and we’ll also add our web3modal button.
File: ./app/App.tsx
// Imports
// ========================================================
import { StatusBar } from "expo-status-bar";
import { Text, View } from "react-native";
import "../global.css";
import { Logo } from "../components/Icons";
+ import RootProvider from "../providers";
+ import { Web3Modal } from "@web3modal/wagmi-react-native";
// Main App Component
// ========================================================
export default function App() {
return (
+ <RootProvider>
<View className="App">
<StatusBar style="auto" />
+ <Web3Modal />
<Logo className="w-auto h-10 mx-auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
</View>
+ </RootProvider>
);
}
Connect Button
Now that the app fully supports WalletConnect, we can now add support for a connect button. We’re going to put this in a separate file to make it easier for us to modify by itself.
# FROM: ./berachain-walletconnect-expo;
mkdir components/Connect;
touch components/Connect/index.tsx;
File: ./components/Connect/index.tsx
// Imports
// ========================================================
import { W3mButton } from "@web3modal/wagmi-react-native";
import { View } from "react-native";
// Component
// ========================================================
export default function Connect() {
return (
<View className="Connect">
{/* Customizing the web3modal button requires passing it certain props */}
<W3mButton
connectStyle={{
backgroundColor: "#2E1E1A",
}}
accountStyle={{
backgroundColor: "#2E1E1A",
}}
/>
</View>
);
}
Let’s add this button to our App.tsx
file.
// Imports
// ========================================================
import { StatusBar } from "expo-status-bar";
import { Text, View } from "react-native";
import "../global.css";
import { Logo } from "../components/Icons";
+ import Connect from "../components/Connect";
import RootProvider from "../providers";
import { Web3Modal } from "@web3modal/wagmi-react-native";
// Main App Component
// ========================================================
export default function App() {
return (
<RootProvider>
<View className="App">
<StatusBar style="auto" />
<Web3Modal />
<Logo className="w-auto h-10 mx-auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
+ <Connect />
</View>
</RootProvider>
);
}
We should now have a our connect button supported, but you’ll quickly notice there isn’t a way to connect with our wallet on our mobile because MetaMask isn’t installed on Simulator.
When you click the Connect button, we’ll see that there are zero wallets available.
This is the part where we can take advantage of Expo Go on our phone to connect with our existing wallets.
Note: Make sure that you have MetaMask Mobile installed on your phone and configure your MetaMask mobile to have a new network with Berachain.
If you notice when we run pnpm ios
a QR code shows up. We should be able to use that with the Expo Go app to load the app directly into our phone.
Now when we use our iPhone’s camera, we can connect and open up our app in Expo Go.
When the app loads on our phone, we should be able to tap Connect, be presented with MetaMask as an option, select MetaMask, confirm our connection via the MetaMask app, and then be pushed back to the app to confirm that we’re connected.
Display $BERA Balance
Now that we have successfully connected our account with MetaMask, let’s add a basic component that displays our current balance on Berachain.
Note: Make sure to switch your MetaMask wallet to the Berachain Artio Testnet Network before hand.
Our component will check if the account is already connected and the make a request to show the current balance.
# FROM: ./berachain-walletconnect-expo;
mkdir components/Balance;
touch components/Balance/index.tsx;
Let’s add the following code for our new Balance
component.
File: ./components/Balance/index.tsx
// Imports
// ========================================================
import { View, Text } from "react-native";
import { useAccount, useBalance } from "wagmi";
// Component
// ========================================================
export default function Balance() {
// Hooks
const { isConnected, address } = useAccount();
const { data, isError, isLoading } = useBalance({
address
});
// Return
/**
* If still loading, show a loading state
*/
if (isLoading)
return <Text className="Text">Fetching balance...</Text>;
/**
* Show error if having a problem fetching the balance
*/
if (isError)
return (
<Text className="mText">Error fetching balance</Text>
);
/**
* If not connected don't show anything
*/
if (!isConnected) return null;
/**
* Successfully connected
*/
return (
<View className="Balance">
<Text className="Text">Balance</Text>
<Text className="Code">{(parseInt((data?.value ?? '').toString()) / 1000000000000000000).toFixed(2)} $BERA</Text>
</View>
);
}
Then we’ll add the Balance component to our App.tsx
.
File: ./app/App.tsx
// Imports
// ========================================================
import { StatusBar } from "expo-status-bar";
import { Text, View } from "react-native";
import "../global.css";
import { Logo } from "../components/Icons";
import Connect from "../components/Connect";
+ import Balance from "../components/Balance";
import RootProvider from "../providers";
import { Web3Modal } from "@web3modal/wagmi-react-native";
// Main App Component
// ========================================================
export default function App() {
return (
<RootProvider>
<View className="App">
<StatusBar style="auto" />
<Web3Modal />
<Logo className="w-auto h-10 mx-auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
<Connect />
+ <Balance />
</View>
</RootProvider>
);
}
Now when we connect with MetaMask, we should see our $BERA
balance.
Wallet Sign Message
On top of every basic RPC request, we also want to get some basic wallet interactions going. The simplest version would be signing a message with your MetaMask wallet and getting a signature.
We’re going to build an input that accepts a message, prompts the user’s MetaMask wallet to sign the message, and output the signature.
We’ll first create the file.
# FROM: ./berachain-walletconnect-expo;
mkdir components/SignMessage;
touch components/SignMessage/index.tsx;
Add the functionality to it.
File: ./components/SignMessage/index.tsx
// Imports
// ========================================================
import { useState } from "react";
import { View, Pressable, Text, TextInput } from "react-native";
import { useAccount, useSignMessage } from "wagmi";
// Component
// ========================================================
export default function SignMessage() {
// Hooks
const [message, setMessage] = useState("");
const [signature, setSignature] = useState("(Signature will show up here)");
const [error, setError] = useState("");
const { isConnected, address } = useAccount();
const { signMessageAsync } = useSignMessage();
// Functions
/**
* @dev Handles signing message
*/
const onPressSignMessage = async () => {
console.group("onPressSignMessage");
setError("");
try {
const signature = await signMessageAsync({
message,
});
setSignature(signature);
} catch (error: unknown) {
console.error({ error });
setError("Error signing message.");
}
console.groupEnd();
};
// Return
/**
* If not connected and no address, then don't show anything
*/
if (!isConnected || !address) return null;
return (
<View className="SignMessage">
<Text className="Text">Sign Message</Text>
<TextInput
className="TextInput"
placeholder="Message to sign"
onChangeText={setMessage}
value={message}
/>
<Text className="Text">Signature Generated</Text>
<Text
className="Code"
>{signature}</Text>
<Pressable
className="Button"
onPress={onPressSignMessage}
>
<Text className="text-white text-base">Sign Message</Text>
</Pressable>
{error ? (
<Text className="TextError">
{error}
</Text>
) : null}
</View>
);
}
We’ll add the Sign Message functionality to our App.tsx
.
File: ./app/App.tsx
// Imports
// ========================================================
import { StatusBar } from "expo-status-bar";
import { Text, View } from "react-native";
import "../global.css";
import { Logo } from "../components/Icons";
import Connect from "../components/Connect";
import Balance from "../components/Balance";
+ import SignMessage from "../components/SignMessage";
import RootProvider from "../providers";
import { Web3Modal } from "@web3modal/wagmi-react-native";
// Main App Component
// ========================================================
export default function App() {
return (
<RootProvider>
<View className="App">
<StatusBar style="auto" />
<Web3Modal />
<Logo className="w-auto h-10 mx-auto" />
<Text className="H1">Berachain WalletConnect Expo Example</Text>
<Text className="Text">Demonstrating how to build mobile dApps</Text>
<Connect />
<Balance />
+ <SignMessage />
</View>
</RootProvider>
);
}
We should now see the functionality of inputting a message, prompting our wallet to sign the message, and seeing the generated signature.
Deploy Contract
The last piece of functionality I want to add to this app is the ability to deploy a contract’s bytecode directly from the mobile app itself. This would essentially prompt our wallet to initiate a transaction that would deploy a contract to Berachain.
To start, let’s create a new Deploy component.
# FROM: ./berachain-walletconnect-expo;
mkdir components/Deploy;
touch components/Deploy/index.tsx;
We’ll add the following functionality to our Deploy component.
File: ./components/Deploy/index.tsx
// Imports
// ========================================================
import { useState } from "react";
import { Pressable, Text } from "react-native";
import { useAccount, useWaitForTransaction } from "wagmi";
import { encodeAbiParameters } from "viem";
import { openURL } from "expo-linking";
// Constants
// ========================================================
/**
* @dev ByteCode for HelloWorld Contract see https://github.com/berachain/guides/blob/main/apps/hardhat-viem-helloworld/contracts/HelloWorld.sol
*/
const CONTRACT_BYTECODE =
"0x60806040523480156200001157600080fd5b5060405162000da238038062000da283398181016040528101906200003791906200021e565b8060009081620000489190620004ba565b507fcbc299eeb7a1a982d3674880645107c4fe48c3227163794e48540a752272235433826040516200007c92919062000638565b60405180910390a1506200066c565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620000f482620000a9565b810181811067ffffffffffffffff82111715620001165762000115620000ba565b5b80604052505050565b60006200012b6200008b565b9050620001398282620000e9565b919050565b600067ffffffffffffffff8211156200015c576200015b620000ba565b5b6200016782620000a9565b9050602081019050919050565b60005b838110156200019457808201518184015260208101905062000177565b60008484015250505050565b6000620001b7620001b1846200013e565b6200011f565b905082815260208101848484011115620001d657620001d5620000a4565b5b620001e384828562000174565b509392505050565b600082601f8301126200020357620002026200009f565b5b815162000215848260208601620001a0565b91505092915050565b60006020828403121562000237576200023662000095565b5b600082015167ffffffffffffffff8111156200025857620002576200009a565b5b6200026684828501620001eb565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620002c257607f821691505b602082108103620002d857620002d76200027a565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620003427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000303565b6200034e868362000303565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200039b620003956200038f8462000366565b62000370565b62000366565b9050919050565b6000819050919050565b620003b7836200037a565b620003cf620003c682620003a2565b84845462000310565b825550505050565b600090565b620003e6620003d7565b620003f3818484620003ac565b505050565b5b818110156200041b576200040f600082620003dc565b600181019050620003f9565b5050565b601f8211156200046a576200043481620002de565b6200043f84620002f3565b810160208510156200044f578190505b620004676200045e85620002f3565b830182620003f8565b50505b505050565b600082821c905092915050565b60006200048f600019846008026200046f565b1980831691505092915050565b6000620004aa83836200047c565b9150826002028217905092915050565b620004c5826200026f565b67ffffffffffffffff811115620004e157620004e0620000ba565b5b620004ed8254620002a9565b620004fa8282856200041f565b600060209050601f8311600181146200053257600084156200051d578287015190505b6200052985826200049c565b86555062000599565b601f1984166200054286620002de565b60005b828110156200056c5784890151825560018201915060208501945060208101905062000545565b868310156200058c578489015162000588601f8916826200047c565b8355505b6001600288020188555050505b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620005ce82620005a1565b9050919050565b620005e081620005c1565b82525050565b600082825260208201905092915050565b600062000604826200026f565b620006108185620005e6565b93506200062281856020860162000174565b6200062d81620000a9565b840191505092915050565b60006040820190506200064f6000830185620005d5565b8181036020830152620006638184620005f7565b90509392505050565b610726806200067c6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063fe50cc7214610057575b600080fd5b610055600480360381019061005091906102ad565b610075565b005b61005f6100c1565b60405161006c9190610375565b60405180910390f35b806000908161008491906105ad565b507fcbc299eeb7a1a982d3674880645107c4fe48c3227163794e48540a752272235433826040516100b69291906106c0565b60405180910390a150565b6060600080546100d0906103c6565b80601f01602080910402602001604051908101604052809291908181526020018280546100fc906103c6565b80156101495780601f1061011e57610100808354040283529160200191610149565b820191906000526020600020905b81548152906001019060200180831161012c57829003601f168201915b5050505050905090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101ba82610171565b810181811067ffffffffffffffff821117156101d9576101d8610182565b5b80604052505050565b60006101ec610153565b90506101f882826101b1565b919050565b600067ffffffffffffffff82111561021857610217610182565b5b61022182610171565b9050602081019050919050565b82818337600083830152505050565b600061025061024b846101fd565b6101e2565b90508281526020810184848401111561026c5761026b61016c565b5b61027784828561022e565b509392505050565b600082601f83011261029457610293610167565b5b81356102a484826020860161023d565b91505092915050565b6000602082840312156102c3576102c261015d565b5b600082013567ffffffffffffffff8111156102e1576102e0610162565b5b6102ed8482850161027f565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610330578082015181840152602081019050610315565b60008484015250505050565b6000610347826102f6565b6103518185610301565b9350610361818560208601610312565b61036a81610171565b840191505092915050565b6000602082019050818103600083015261038f818461033c565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806103de57607f821691505b6020821081036103f1576103f0610397565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104597fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261041c565b610463868361041c565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006104aa6104a56104a08461047b565b610485565b61047b565b9050919050565b6000819050919050565b6104c48361048f565b6104d86104d0826104b1565b848454610429565b825550505050565b600090565b6104ed6104e0565b6104f88184846104bb565b505050565b5b8181101561051c576105116000826104e5565b6001810190506104fe565b5050565b601f82111561056157610532816103f7565b61053b8461040c565b8101602085101561054a578190505b61055e6105568561040c565b8301826104fd565b50505b505050565b600082821c905092915050565b600061058460001984600802610566565b1980831691505092915050565b600061059d8383610573565b9150826002028217905092915050565b6105b6826102f6565b67ffffffffffffffff8111156105cf576105ce610182565b5b6105d982546103c6565b6105e4828285610520565b600060209050601f8311600181146106175760008415610605578287015190505b61060f8582610591565b865550610677565b601f198416610625866103f7565b60005b8281101561064d57848901518255600182019150602085019450602081019050610628565b8683101561066a5784890151610666601f891682610573565b8355505b6001600288020188555050505b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106aa8261067f565b9050919050565b6106ba8161069f565b82525050565b60006040820190506106d560008301856106b1565b81810360208301526106e7818461033c565b9050939250505056fea264697066735822122051a137f3f2f370792efdafdfd52aa1721451dfaa2e804a5236730d97a26f237664736f6c63430008110033";
// Component
// ========================================================
export default function Deploy() {
// Hooks
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [transactionHash, setTransactionHash] = useState<`0x${string}` | undefined>();
const { isConnected, address, connector } = useAccount();
// Functions
/**
* @dev hook that waits for a transaction hash
*/
const txResult = useWaitForTransaction({
hash: transactionHash
});
/**
* @dev handles deploying the contract
*/
const onPressDeployContract = async () => {
console.group('onPressDeployContract');
setError('');
setIsLoading(true);
try {
const provider = await connector?.getProvider(); // get's the provider from wagmi directly - needed for the walletconnection
console.log({ provider });
console.log({ request: provider?.request });
// Based on constructor - constructor(string memory _greeting) {
// encodes the function name and the input of `Hello World!`
const encodedData = encodeAbiParameters(
[{ name: "_greeting", type: "string" }],
["Hello World!"]
);
// Need slide(2) to remove 0x from encodedData at the beginning
const fullByteCode = `${CONTRACT_BYTECODE}${encodedData.slice(2)}`;
// Send eth transsaction
const tx = await provider.request({
method: "eth_sendTransaction",
params: [
{
from: address,
data: fullByteCode,
},
],
});
console.log({ tx });
// Set the state transaction hash for `useWaitForTransaction` to wait for it
setTransactionHash(tx);
} catch (error: unknown) {
console.error(error);
setError('Error could not deploy contract.')
}
setIsLoading(false);
console.groupEnd();
};
/**
* @dev function that handles opening url to the final transaction hash in a block explorer
*/
const onPressSeeTransaction = () => {
openURL(`${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL}/tx/${txResult?.data?.transactionHash}`);
};
/**
* If not connected and no address, then don't show anything
*/
if (!isConnected || !address) return null;
return (
<>
<Text className="Text">Deploy Contract</Text>
<Pressable
disabled={isLoading}
className={"Button"}
onPress={onPressDeployContract}>
<Text className="text-white text-base">
Deploy
</Text>
</Pressable>
{isLoading && <Text className="Code">Loading...</Text>}
{!isLoading && txResult?.data?.transactionHash
? <Pressable
className="Button"
onPress={onPressSeeTransaction}>
<Text className="text-white text-base">
See Successful Transaction
</Text>
</Pressable>
: null}
{error ? <Text className="TextError">{error}</Text> : null}
</>
);
}
Now if we go through the process, we should be able to tap Deploy, be prompted by our wallet to confirm the transaction, wait for the transaction to complete and see the transaction on the block explorer.
🐻 Full Code Repository
If you want to see the final code and see other guides, check out Berachain WalletConnect Expo Guide Code.
🛠️ Want To Build More?
Want to build more on Berachain and see more examples. Take a look at our Berachain GitHub Guides Repo for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.
If you’re looking to dive deeper into the details, take a look at our Berachain Docs.
Looking For Dev Support?
Make sure to join our Berachain Discord server and check out our developer channels to ask questions.
❤️ Don’t forget to show some love for this article 👏🏼.