Create a simple Starknet dapp
In this tutorial, you'll learn how to set up a React TypeScript dapp that uses the get-starknet
library to connect to MetaMask and display the user's wallet address.
You'll also display the balance of an ERC-20 token and perform a token transfer.
Prerequisites
- MetaMask installed
- A text editor (for example, VS Code)
- Node version 20.11 or later
- Yarn
This tutorial uses get-starknet
version 3.3.0
and starknet.js
version 6.11.0
.
1. Set up the project
Use Create React App to set up a new React project with TypeScript.
Create a new project named get-starknet-tutorial
:
yarn create react-app get-starknet-tutorial --template typescript
Change into the project directory:
cd get-starknet-tutorial
Configure Yarn to use the node-module
linker instead of its default linking strategy:
yarn config set nodeLinker node-modules
2. Add get-starknet
and starknet.js
Add get-starknet
version 3.3.0
and starknet.js
version 6.11.0
to your project's dependencies:
yarn add get-starknet@3.3.0 starknet@6.11.0
Your file structure should look similar to the following:
get-starknet-tutorial/
├── public/
│ ├── index.html
│ └── ...
├── src/
│ ├── App.tsx
│ ├── index.tsx
│ ├── App.css
│ └── ...
└── ...
3. Configure the wallet connection
3.1. Connect to MetaMask
The connect
function from get-starknet
is the primary way to connect your dapp to a user's MetaMask wallet.
It opens a connection to MetaMask and returns an object containing important details about the wallet, including:
name
: The name of the wallet.icon
: The wallet's icon, which displays the wallet's logo.account
: The account object of typeAccountInterface
fromstarknet.js
, which contains the wallet's address and provides access to account-specific operations.
To import the necessary functions and connect to a wallet, add the following code to src/App.tsx
:
import {
type ConnectOptions,
type DisconnectOptions,
connect,
disconnect,
} from "get-starknet"
import { AccountInterface } from "starknet"
import { useState } from "react"
function App() {
const [walletName, setWalletName] = useState("")
const [walletAddress, setWalletAddress] = useState("")
const [walletIcon, setWalletIcon] = useState("")
const [walletAccount, setWalletAccount] = useState(null)
async function handleConnect(options?: ConnectOptions) {
const res = await connect(options)
setWalletName(res?.name || "")
setWalletAddress(res?.account?.address || "")
setWalletIcon(res?.icon || "")
setWalletAccount(res?.account)
}
async function handleDisconnect(options?: DisconnectOptions) {
await disconnect(options)
setWalletName("")
setWalletAddress("")
setWalletAccount(null)
}
}
3.2. Display connection options
The connect
function accepts an optional ConnectOptions
object.
This object can control the connection process, including:
modalMode
: Determines how the connection modal behaves. The options are:alwaysAsk
: Prompts the user every time a connection is initiated.neverAsk
: Attempts to connect without showing the modal.
modalTheme
: Sets the visual theme of the connection modal. The options are"dark"
and"light"
.
The disconnect
function allows users to disconnect their wallet.
You can enable clearLastWallet
to clear the last connected wallet information.
In App.tsx
, you can display connect and disconnect buttons with various options as follows:
function App() {
// ...
return (
<div className="App">
<h1>get-starknet</h1>
<div className="card">
// Default connection:
<button onClick={() => handleConnect()}>Connect</button>
// Always show modal:
<button onClick={() => handleConnect({ modalMode: "alwaysAsk" })}>Connect (always ask)</button>
// Never show modal:
<button onClick={() => handleConnect({ modalMode: "neverAsk" })}>Connect (never ask)</button>
// Dark theme modal:
<button onClick={() => handleConnect({ modalMode: "alwaysAsk", modalTheme: "dark" })}>
Connect (dark theme)
</button>
// Light theme modal:
<button onClick={() => handleConnect({ modalMode: "alwaysAsk", modalTheme: "light" })}>
Connect (light theme)
</button>
// Default disconnect:
<button onClick={() => handleDisconnect()}>Disconnect</button>
// Disconnect and clear last wallet:
<button onClick={() => handleDisconnect({ clearLastWallet: true })}>Disconnect and reset</button>
</div>
</div>
)
};
3.3. Display wallet information
Update App.tsx
with the following code to display the name and icon of the connected wallet, and
the connected address.
This provides visual feedback to the user, confirming which wallet and account they are using.
function App() {
// ...
return (
<div className="App">
// ...
{walletName && (
<div>
<h2>
Selected Wallet: <pre>{walletName}</pre>
<img src={walletIcon} alt="Wallet icon"/>
</h2>
<ul>
<li>Wallet address: <pre>{walletAddress}</pre></li>
</ul>
</div>
)}
</div>
)
};
3.4. Full example
The following is a full example of configuring the wallet connection. It displays basic connect and disconnect buttons, and the connected wallet's information.
import "./App.css"
import {
type ConnectOptions,
type DisconnectOptions,
connect,
disconnect,
} from "get-starknet"
import { AccountInterface } from "starknet";
import { useState } from "react"
function App() {
const [walletName, setWalletName] = useState("")
const [walletAddress, setWalletAddress] = useState("")
const [walletIcon, setWalletIcon] = useState("")
const [walletAccount, setWalletAccount] = useState<AccountInterface | null>(null)
async function handleConnect(options?: ConnectOptions) {
const res = await connect(options)
setWalletName(res?.name || "")
setWalletAddress(res?.account?.address || "")
setWalletIcon(res?.icon || "")
setWalletAccount(res?.account)
}
async function handleDisconnect(options?: DisconnectOptions) {
await disconnect(options)
setWalletName("")
setWalletAddress("")
setWalletAccount(null)
}
return (
<div className="App">
<h1>get-starknet</h1>
<div className="card">
<button onClick={() => handleConnect({ modalMode: "alwaysAsk" })}>Connect</button>
<button onClick={() => handleDisconnect()}>Disconnect</button>
</div>
{walletName && (
<div>
<h2>
Selected Wallet: <pre>{walletName}</pre>
<img src={walletIcon} alt="Wallet icon"/>
</h2>
<ul>
<li>Wallet address: <pre>{walletAddress}</pre></li>
</ul>
</div>
)}
</div>
)
};
export default App
3.5. Start the dapp
Start the dapp and navigate to it in your browser.
yarn start
You are directed to the default dapp display.
When you select Connect, get-starknet
displays a modal that detects MetaMask and allows you to
choose which Starknet wallet to connect to.
Follow the on-screen prompts to connect your MetaMask wallet to Starknet.
After you accept the terms in the prompts, your wallet is connected and its information is displayed.
4. Display the balance of and transfer an ERC-20 token
Now that you have configured the wallet connection, you can display the balance of a specific ERC-20
token, such as STRK, and perform a transfer using the AccountInterface
instance.
4.1. Obtain tokens and switch to testnet
Use the Starknet Snap companion dapp to generate a Starknet address and switch to the Starknet Sepolia testnet.
Obtain testnet ETH (for gas) and at least 1 STRK token from the Starknet faucet.
4.2. Configure the TypeScript compiler
In the tsconfig.json
file in the root directory of your project, update the compilerOptions
with
the target
version set to es2022
, and jsx
set to react-jsx
:
{
"compilerOptions": {
"target": "es2022",
"jsx": "react-jsx",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true
}
}
4.3. Set up the contract
Create a src/components/
directory and add the following files to it:
erc20Abi.json
: A JSON file containing the ERC-20 token contract's application binary interface (ABI).TokenBalanceAndTransfer.tsx
: A React component file for handling token balance display and transfer operations.
The contract address for STRK (an ERC-20 token) on Sepolia testnet is 0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7
.
You can find the ABI of the ERC-20 contract on the Code tab in Voyager.
Add the following code to TokenBalanceAndTransfer.tsx
to load the ABI from erc20Abi.json
:
import { useEffect, useState } from "react";
import { AccountInterface, Call, Contract } from "starknet";
import erc20Abi from "./erc20Abi.json";
interface TokenBalanceAndTransferProps {
account: AccountInterface;
tokenAddress: string;
}
export function TokenBalanceAndTransfer({ account, tokenAddress }: TokenBalanceAndTransferProps) {
const [balance, setBalance] = useState<number | null>(null);
}