Parts of this article were originally written for The Starknet Book. Check it out for more content on Starknet development.
The good news
If you've been following Starknet's progress, you're probably aware that there has been a lot of change recently. Cairo 1 was released, and that means frontend tools need updating to keep up.
If you have experience building dapps but are new to Starknet, the good news is that you can apply plenty of that knowledge here! If you're familiar with Starknet, the good news (in my opinion) is that the changes are moving in the right direction.
The basic tools you can use to get started are Starknet.js and get-starknet. Starknet.js is an essential low level lib, equivalent to ethers.js, whereas get-starknet handles connecting to wallets. You should definitely read Starknet.js' docs for a more in-depth understanding.
With these tools, there are basically 3 main concepts to know on the frontend:
We can generally think of the account as the "end user" of a dapp, and some user interaction will be involved to gain access to it.
Think of a dapp where the user connects their browser extension wallet (such as ArgentX or Braavos) - if the user accepts the connection, that gives us access to the account and signer, which can sign transactions and messages.
Unlike Ethereum, where user accounts are Externally Owned Accounts, Starknet accounts are contracts. This might not impact your dapp's frontend, but you should definitely be aware of this difference.
The snippet above uses the
connect function provided by
get-starknet to establish a connection to the user wallet.
This one is pretty simple. The provider allows you to interact with the Starknet network. You can think of it as a "read" connection to the blockchain, with methods such as
Just like in Ethereum, you can use a default provider, or use services like Infura or Alchemy, both of which support Starknet.
Of course, your frontend will likely be interacting with deployed contracts. For each contract, there should be a counterpart on the frontend. To create these instances, you will need the contract's address and ABI, and either a provider or signer.
If you create a contract instance with a provider, you'll be limited to calling read functions on the contract - only with a signer can you change the state of the blockchain.
If you have previous experience with web3, you know dealing with units can be tricky, and Starknet is no exception. Once again, the docs are very useful here, especially this section on data transformation.
Very often you will need to convert Cairo structs (such as Uint256) that are returned from contracts into numbers:
And vice versa:
If we put it all together, calling a
transfer function on a contract looks like this:
There are other helpful utils, besides
uint256ToBN, provided by Starknet.js.
We now have a solid foundation to build a Starknet dapp! If you have a background in regular Ethereum apps, you'll notice how similar the experience is, but you're probably also wondering if there are more high-level tools out there that we can use. Well, there are!
Starknet React is an open-source collection of React providers and hooks for Starknet, inspired by wagmi. If you've used wagmi before, you should have a pretty good idea of what it looks like, as it aims to have a very similar API.
To explore an example project showcasing a dapp built with Starknet React, check out the starknet-demo-dapp repo.
To use Starknet React, start by installing the necessary dependencies:
yarn add @starknet-react/core starknet get-starknet
Then, wrap your app in a
StarknetConfig component. This allows for some configuration and provides a React Context for the underlying app to be able to use the shared data and hooks.
StarknetConfig receives a
connectors prop that defines which wallet connection options will be available to the user.
Connection and account
You can now use a hook to access the connectors you defined in the config, allowing the user to connect their wallet:
Note that there is also a
disconnect function provided, in order to end the connection. Once the user has connected their wallet, you have access to the connected account through the
useAccount hook. This also provides the current state of connection:
Note that state values such as
isReconnecting are updated automatically, making it easier to update the UI conditionally. This is a very useful and common pattern when dealing with asynchronous processes, as it means you don't have to manually keep track of that state locally in your components.
Once the user is connected, signing messages may be done through the
account value returned from the
useAccount hook, or more simply through the
You can sign an array of
BigNumberish, or an object. In the event of signing an object, the data must be correctly typed, in accordance with EIP712. You can find a more in depth explanation here.
Starknet React also provides hooks for interacting with the provider. For instance,
useBlock fetches the latest block:
refetchInterval specifies how often the hook will attempt to refetch the data. Under the hood, Starknet React uses react-query for state and query management. Besides
useBlock, there are other hooks that can be configured to periodically update, such as
useStarknet hook, it's also possible to directly access the
Interacting with contracts
Just like with wagmi, there is a
useContractRead hook provided specifically for calling read functions on contracts. Note that this may be used even with without a connected user, as read functions don't require a signer.
For working with ERC20s, there is also a convenience hook -
useBalance. This hook doesn't require passing in an ABI, and will return a correctly formatted balance value.
For write functions, the
useContractWrite hook is slightly different to wagmi. Due to Starknet's architecture, accounts can natively support multicall transactions. In practice, this means an improved user experience when executing multiple transactions, as you won't have to individually approve each transaction. Starknet React makes the most of this feature through the
useContractWrite hook. It can be used in the following manner:
In the example above, we first compile the calldata to be executed using Starknet.js's
compileCalldata util. We pass the contract address, entrypoint, and calldata to the
useContractWrite hook, which returns a
write function we can use to actually trigger the transaction. The hook also returns the hash and state of the contract call.
Single instance of a contract
useContractWrite might not be a good fit for your use case - you might want to work with a single instance of the contract, instead of continually specifying its address and ABI in individual hooks. This is also possible throught the
Once you have a transaction hash, you may track it's state through the
useTransaction hook. This keeps a cache of all transactions, reducing duplicated network requests.
You can find a working example with any of these hooks implemented in this repo.
Expect more changes in the next few months, as the Starknet ecosystem continues to evolve!
Happy hacking :wave: