Create a dApp with StarKey
Create your first Dapp using StarKey wallet, the official wallet of Supra. For the full window.starkey.supra provider API, see the StarKey developer docs .
Setup the project
Initialize the frontend
npx create-react-app supra-dapp --template typescriptNavigate into the project directory
cd supra-dappInstall the TypeScript SDK
npm install supra-ts-sdk@devnetThe devnet, testnet and mainnet npm dist-tags point to the release that matches each network. This guide uses
Testnet; see the TypeScript SDK page for the dist-tags.
Start building
Open the App.tsx file and update it
Remove the template code and replace it with the code below. This will give us a fresh foundation to work with.
function App() {
return <div></div>;
}
export default App;Import dependencies
We’ll be utilizing the react library along with useful functions from the supra-ts-sdk.
import React, { useEffect, useState } from 'react';
import { HexString, BCS, SupraClient, Network } from 'supra-ts-sdk';Initialize the supraProvider and state hooks
supraProvider - used to interact with StarKey wallet.
isStarKeyInstalled - boolean value of StarKey extension installation status
accounts - Array of connected accounts
networkData - Chain ID for the connected network
supra - the SDK client, created once at module scope and reused. Building a new client per transaction would open a fresh RPC connection each time.
import React, {useEffect, useState} from "react";
import { HexString, BCS, SupraClient, Network } from "supra-ts-sdk";
const supra = new SupraClient({ network: Network.TESTNET });
function App() {
let supraProvider: any = typeof window !== "undefined" && (window as any)?.starkey?.supra;
const [isStarkeyInstalled, setIsStarkeyInstalled] = useState<boolean>(!!supraProvider);
const [accounts, setAccounts] = useState<string[]>([]);
const [networkData, setNetworkData] = useState<any>();
return <div></div>;
}All code from this step forward will be included within the App() function.
Check for the injected StarKey object
The following code checks for the injected StarKey object every 500ms for up to 5 seconds. If found, the function will update the StarKey installation status and obtain the connected accounts. See Detecting the Provider in the StarKey docs.
const checkForStarkey = () => {
const intervalId = setInterval(() => {
if ((window as any)?.starkey) {
supraProvider = (window as any)?.starkey.supra;
clearInterval(intervalId);
setIsStarkeyInstalled(true);
updateAccounts().then();
}
}, 500);
setTimeout(() => {
clearInterval(intervalId);
}, 5000);
};
useEffect(() => {
checkForStarkey();
}, []);Get the connected accounts
The following code obtains the connected accounts (if any) and stores them.
const updateAccounts = async () => {
if (supraProvider) {
try {
const response_acc = await supraProvider.account();
if (response_acc.length > 0) {
setAccounts(response_acc);
} else {
setAccounts([]);
}
} catch (e) {
setAccounts([]);
}
getNetworkData().then();
}
};Get the connected network data
The following code obtains the network data (chain id) of the connected wallet.
const getNetworkData = async () => {
if (supraProvider) {
const data = await supraProvider.getChainId();
console.log(data);
if (data) {
setNetworkData(data)
}
}
};Initialize additional hooks
The following code will assist in maintaining updated network/account data while also handling events emitted by the injected StarKey object.
useEffect(() => {
if (supraProvider) {
supraProvider.on("accountChanged", (acc: string[]) => {
setAccounts(acc);
});
supraProvider.on("networkChanged", (data: any) => {
setNetworkData(data);
});
supraProvider.on("disconnect", () => {
resetWalletData();
});
}
}, [supraProvider]);
useEffect(() => {
if (accounts) {
getNetworkData().then();
}
}, [accounts]);Handle wallet connections
The following code will handle the wallet connections , triggered through a button click (which we will setup later) or an emitted event from the StarKey object.
When a wallet is connected, the accounts must be updated. When a wallet is disconnected, we must clear the stored accounts and network data.
const connectWallet = async () => {
const response = await supraProvider.connect();
updateAccounts().then();
};
const disconnectWallet = async () => {
if (supraProvider) {
await supraProvider.disconnect();
}
resetWalletData();
};
const resetWalletData = ()=>{
setAccounts([]);
setNetworkData({});
}Switch between mainnet and testnet
The following code will be triggered through a button click to prompt the user to switch between mainnet and testnet
const switchToTestnet = async (id:string) => {
if (supraProvider) {
await supraProvider.changeNetwork({chainId:"6"});
await getNetworkData()
}
};
const switchToMainnet = async () => {
if (supraProvider) {
await supraProvider.changeNetwork({chainId:"8"});
await getNetworkData()
}
};Send a transaction
The following code builds a raw transaction that calls the transfer function of the supra_account module, then hands the serialized bytes to supraProvider.sendTransaction so the connected wallet signs and submits it.
It will transfer 1 supra to the address 0x8de4158b48633d853186d5fc790718e5821d7d3c4855e06bcd97b105389a7d0f .
The transaction is built with the SDK rather than StarKey’s deprecated createRawTransactionData. The SDK needs the sender’s real sequence number, so the function fetches it with supra.account.getAccountInfo first.
rawTxnObject makes no request, so it does not estimate the max gas: without maxGas it uses 20,000 units, and the node
requires the sender’s SUPRA balance to cover max gas × gas unit price (20 SUPRA at the default gas unit price). A
transfer uses far fewer units, so the code sets maxGas. The wallet signs the transaction, so the SDK’s chain ID check
before signing does not apply; the chainId passed to sendTransaction must be that of the connected network.
New recipient addresses: Supra addresses may not have on-chain account state before their first transaction. See Sending Tokens to New Accounts.
const sendRawTransaction = async () => {
const { sequence_number } = await supra.account.getAccountInfo({
accountAddress: accounts[0],
});
/** OptionalTransactionPayloadArgs {
maxGas?: bigint;
gasUnitPrice?: bigint;
txExpiryTime?: bigint;
}*/
const rawTxn = supra.transaction.build.rawTxnObject({
senderAddress: accounts[0],
senderSequenceNumber: sequence_number,
function: "0x1::supra_account::transfer",
functionTypeArgs: [],
functionArgs: [
new HexString("0x8de4158b48633d853186d5fc790718e5821d7d3c4855e06bcd97b105389a7d0f").toUint8Array(),
BCS.bcsSerializeUint64(100000000)
],
optionalTransactionPayloadArgs: {
maxGas: BigInt(5000),
txExpiryTime: BigInt(Math.ceil(Date.now() / 1000) + 30) //30 seconds
}
});
const txHash = await supraProvider.sendTransaction({
data: HexString.fromUint8Array(rawTxn.toBytes()).noPrefix(),
from: accounts[0],
to: "",
value: "",
chainId: "6",
});
console.log("Transaction hash: ", txHash);
};Create the buttons displayed on the frontend
The following code renders buttons on the frontend that will interact with the above functions.
return <div>
<button onClick={connectWallet}>Connect Wallet</button>
<button onClick={disconnectWallet}>Disconnect Wallet</button>
<p>Connected Account(s): {JSON.stringify(accounts)}</p>
<p>Network Data: {JSON.stringify(networkData)}</p>
<button onClick={switchToTestnet}>Switch to Testnet</button>
<button onClick={switchToMainnet}>Switch to Mainnet</button>
<br></br>
<button onClick={sendRawTransaction}>Send Test Transaction</button>
</div>;That’s it! Run your project
npm startFinal code for App.tsx
import React, {useEffect, useState} from "react";
import { HexString, BCS, SupraClient, Network } from "supra-ts-sdk";
const supra = new SupraClient({ network: Network.TESTNET });
function App() {
let supraProvider: any = typeof window !== "undefined" && (window as any)?.starkey?.supra;
const [isStarkeyInstalled, setIsStarkeyInstalled] = useState<boolean>(!!supraProvider);
const [accounts, setAccounts] = useState<string[]>([]);
const [networkData, setNetworkData] = useState<any>();
const checkForStarkey = () => {
const intervalId = setInterval(() => {
if ((window as any)?.starkey) {
supraProvider = (window as any)?.starkey.supra;
clearInterval(intervalId);
setIsStarkeyInstalled(true);
updateAccounts().then();
}
}, 500);
setTimeout(() => {
clearInterval(intervalId);
}, 5000);
};
useEffect(() => {
checkForStarkey();
}, []);
const getNetworkData = async () => {
if (supraProvider) {
const data = await supraProvider.getChainId();
console.log(data);
if (data) {
setNetworkData(data)
}
}
};
const updateAccounts = async () => {
if (supraProvider) {
try {
const response_acc = await supraProvider.account();
if (response_acc.length > 0) {
setAccounts(response_acc);
} else {
setAccounts([]);
}
} catch (e) {
setAccounts([]);
}
getNetworkData().then();
}
};
useEffect(() => {
if (supraProvider) {
supraProvider.on("accountChanged", (acc: string[]) => {
setAccounts(acc);
});
supraProvider.on("networkChanged", (data: any) => {
setNetworkData(data);
});
supraProvider.on("disconnect", () => {
resetWalletData();
});
}
}, [supraProvider]);
useEffect(() => {
if (accounts) {
getNetworkData().then();
}
}, [accounts]);
const resetWalletData = ()=>{
setAccounts([]);
setNetworkData({});
}
const connectWallet = async () => {
const response = await supraProvider.connect();
updateAccounts().then();
};
const disconnectWallet = async () => {
if (supraProvider) {
await supraProvider.disconnect();
}
resetWalletData();
};
const switchToTestnet = async () => {
if (supraProvider) {
await supraProvider.changeNetwork({chainId:"6"});
await getNetworkData()
}
};
const switchToMainnet = async () => {
if (supraProvider) {
await supraProvider.changeNetwork({chainId:"8"});
await getNetworkData()
}
};
const sendRawTransaction = async () => {
const { sequence_number } = await supra.account.getAccountInfo({
accountAddress: accounts[0],
});
const rawTxn = supra.transaction.build.rawTxnObject({
senderAddress: accounts[0],
senderSequenceNumber: sequence_number,
function: "0x1::supra_account::transfer",
functionTypeArgs: [],
functionArgs: [
new HexString("0x8de4158b48633d853186d5fc790718e5821d7d3c4855e06bcd97b105389a7d0f").toUint8Array(),
BCS.bcsSerializeUint64(100000000)
],
optionalTransactionPayloadArgs: {
maxGas: BigInt(5000),
txExpiryTime: BigInt(Math.ceil(Date.now() / 1000) + 30) //30 seconds
}
});
const txHash = await supraProvider.sendTransaction({
data: HexString.fromUint8Array(rawTxn.toBytes()).noPrefix(),
from: accounts[0],
to: "",
value: "",
chainId: "6",
});
console.log("Transaction hash: ", txHash);
};
return <div>
<button onClick={connectWallet}>Connect Wallet</button>
<button onClick={disconnectWallet}>Disconnect Wallet</button>
<p>Connected Account(s): {JSON.stringify(accounts)}</p>
<p>Network Data: {JSON.stringify(networkData)}</p>
<button onClick={switchToTestnet}>Switch to Testnet</button>
<button onClick={switchToMainnet}>Switch to Mainnet</button>
<br></br>
<button onClick={sendRawTransaction}>Send Test Transaction</button>
</div>;
}
export default App;