An In-depth Guide To Getting Started With Starknet.js

Darlington Nnam
10 min readSep 12, 2022

--

Few days ago, i was looking to create a working user interface to interact directly with my Cairo contracts, rather than using Voyager. In search for a framework or tool similar to ethers.js or web3.js for EVM development, i came across Starknet.js.

Sadly, things are not quite as polished yet, with Starknet.js, as you’d find with ethers.js or web3.js, owing to the Starknet ecosystem being relatively new, so it took me some days of struggle, to finally build a working UI, which you can find here.

It took that long, as i had to search through the Discord channels, ask a few questions here and there, but i wouldn’t want you to pass through those stress, so i decided to write this article to enable you get started with Starknet.js in few minutes!

P.S: Thus far, we’ve been studying the basic fundamentals of Cairo, so this might seem a little too far fetched, for some of you following my “Journey through Cairo Series”, as we are yet to go through building a full blown Cairo contract, so please feel free to skip this, and come back later.

Getting Started

Starknet.js is an open-source Javascript Library, that allow developers interact with Starknet. Started out by Sean, It stands out as the most popular library for interacting with Starknet as at the time of this writing.

P.S: Before we begin, i want to clarify the fact that, this article does not cover the whole of Starknet.js, as it’s quite broad, and i haven’t gotten to personally play and experiment with all available features, but this should give you the basic knowledge you need to build awesome UIs!

Having established that, here are basic concepts you need to get familiarised with:

  1. Provider: A provider is an API from Starknet.js, that enables you interact with Starknet, without necessarily signing messages, e.g when performing read calls. You can read states without signing messages, but you cannot alter or modify the contract’s state.
  2. Signer: The signer API allows you to sign messages, thus enabling you write to the blockchain. With a signer, you can alter and modify the contract’s state.
  3. Contract: Contracts are primarily how we create instantiations of our Contracts with javascript. It takes in the contract’s abi, contract’s address and the provider or account.

Starknet providers commonly used by developers include Alchemy, Moralis, and Chainstack.

For this tutorial, we’d need:

  1. The contract’s ABI
  2. The contract’s Address
  3. An Argent or Braavos wallet
  4. React.js [front-end framework]
  5. get-starknet [dependency]
  6. Starknet [dependency]
  7. Buffer [dependency]

For the sake of time, and as this tutorial is not targeted at learning Cairo, we’d be skipping the part of writing contracts and deploying to Starknet, and we’d just make use of the ABI and contract address of a contract i already deployed previously.

Project Demo

The project we’d be building, is a Starknet Naming Service, which simply assigns a name to a person’s wallet address.

You could check out the project here.

Contract, ABI and Address

Here’s a peek at the contract, which consists of one storage variable names, one external function storeName, and one view function getName.

Contract

%lang starknetfrom starkware.cairo.common.math import assert_nnfrom starkware.cairo.common.cairo_builtins import HashBuiltinfrom starkware.starknet.common.syscalls import get_caller_address@storage_varfunc names(address: felt) -> (name: felt):end
@externalfunc storeName{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( _name: felt): let (caller) = get_caller_address() names.write(caller, _name) return ()end
@viewfunc getName{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( _address: felt) -> (name: felt): let (name) = names.read(_address) return (name)end

Contract ABI

[{"inputs": [{"name": "_name","type": "felt"}],"name": "storeName","outputs": [],"type": "function"},{"inputs": [{"name": "_address","type": "felt"}],"name": "getName","outputs": [{"name": "name","type": "felt"}],"stateMutability": "view","type": "function"}]

Contract Address

0x049e5c0e9fbb072d7f908e77e117c76d026b8daf9720fe1d74fa3309645eabce

Installing A Wallet

To interact with our contracts, we need a wallet. The popular choices amongst Starknet users are ArgentX and Braavos, so we’d go through the installation processes for both.

PS: You don’t need to get the both, any of them could work flawlessly with this tutorial.

To install ArgentX:

  1. On chrome, visit the Chrome extension marketplace here, for other browsers, checkout their extension marketplaces.
  2. Add the extension to your Chrome browser.
  3. Following the instructions, create a new password, then create a new account.

You’d notice unlike other wallets like Metamask implementing Externally owned account model, creating a new account or wallet here, requires deploying a new contract due to Account Abstraction. So we’d wait till our contract is successfully deployed.

To install Braavos:

  1. For chrome users, head over to the extension marketplace here, to get Braavos. You could also check your browser’s marketplace if you are on other browsers.
  2. Add the extension to your browser.
  3. Follow the instruction to create a new password, and then wait for your account to be deployed.

Installing Dependencies

Having gotten most of what we need ready, we’d need a front-end framework to build our User interface with.

For this tutorial, we’d be using React.js, but you could also use Next.js, Vite etc.

Since this tutorial, is not a front-end tutorial, i’m assuming you already have basic foundational knowledge with React.js, and we’d be using a boilerplate design i’ve already created in advance. To get started head over to the tutorial’s repo over here, and clone it using the following command:

git clone git@github.com:Darlington02/starknet-js-tutorial.git --branch boilerplate --single-branch

Up next, we’d need to install the dependencies (react, get-starknet, starknet, buffer). to do this, run:

npm install

or if you are using yarn, run:

yarn

This should take a while, but once it’s done, run:

yarn start

And opening localhost in browser, you should be greeted by a similar UI as this:

Now, we’ve got our dependencies installed, our boilerplate ready and fully prepared to start interacting with our Starknet contract!

The get-starknet Dependency

get-starknet, is a community-managed package wrapper created to help youeasily interact with Starknet.js using your Braavos or Argent wallet.

Before this, you’d have to go through the stressful process of deploying an account to use Starknet.js, which was not very fun.

With get-starknet, you get to easily sign these messages from your Starknet wallet.

Development

Like i said earlier, this is not a React tutorial, so I won’t be going over the React codes, as this would make this article very lengthy.

We’ve got the logic written, in the App.js file, that calls the functions from the image above on button clicks, but we’d need to implement what happens in the function body.

  1. connectWallet Function

The connectWallet function, implements the logic that handles connection to our installed wallet. To get started, first we’d need to import connect from the get-starknet dependency, we installed earlier.

PS: If you still don’t have this dependency for any reason at all (although you should, if you used the boilerplate), you could manually install it with:

yarn add get-starknet starknet

Up next, we’d need to import connect from the get-starknet module, at the top of our App.js file.

import { connect } from "get-starknet"

After which we’d write the logic for connecting our wallet, in our connectWallet function body.

const connectWallet = async() => {   try{      // allows a user to pick a wallet on button click      const starknet = await connect()     // connect to the wallet     await starknet?.enable({ starknetVersion: "v4" })     // set account provider to provider state     setProvider(starknet.account)     // set user address to address state     setAddress(starknet.selectedAddress)     // set connection status     setIsConnected(true)   }   catch(error){     alert(error.message)   }}

The code above implements the wallet connect logic.

The bolded comments, describes what each line of code does. a try, catch is used, so in case the connection doesn’t work out, the error message is echoed to the user.

Lastly, we set some states using the useState hook from react.js, which enables us to store our account provider, wallet address and connection status states to be used later in our UI.

Now, when we click on the Connect wallet button, we should be able to see our Argent wallet request to connect, with a pop us asking us, to choose a wallet to connect to:

Creating A Contract Instance

For every contract call we need to make, we need to create a contract instance, using the contract’s abi, contract’s address, and our provider (or account).

First, we’d create a folder named abis, inside our src folder, and then we’d need to create a file name main_abi.json where we’d paste the contract abi from earlier above.

Next up, we’d import the abi, at the top of our App.js like this:

import contractAbi from "./abis/main_abi.json"

Lastly, we’d set our contract address from earlier above:

const contractAddress = "0x049e5c0e9fbb072d7f908e77e117c76d026b8daf9720fe1d74fa3309645eabce"

Having got, all we need now (remember we had already set our provider variable when we connected our wallet), we can now create a contract instance like this:

const contract = new Contract(contractAbi, contractAddress, provider)

Now we can proceed to creating the logic for both the setName and the getName functions.

2. setName Function

The setName Function, takes in a name as an argument, and makes a contract call to the external function setName in our Cairo contract, which stores the wallet address of the msg.sender and maps it to the inputted name. In order words, this function modifies the state of our contract, and as such needs to be signed by an account or wallet.

But before we proceed to writing our function logic, i want us to remember that, our inputted name is in strings format, but our contract requires a felt, as such we’d need to do some conversions.

Luckily, Starknet also comes with a tonne of utils, that could help you with these conversions, found here. You just need to import the function you need, and use them.

P.S I had some issues with the shortString utils created in the official Starknet repo, so i created my own utils for short strings, which i’d share with you, but the other utils didn’t give me any issues.

For felt to string, and string to felt conversions, create a folder named utils inside your src directory, then create a file in it named utils.js.

Copy this code into your utils.js:

import {Buffer} from 'buffer'export function feltToString(felt) {const newStrB = Buffer.from(felt.toString(16), 'hex')return newStrB.toString()}export function stringToFelt(str) {return "0x" + Buffer.from(str).toString('hex')}

Then import these functions into your App.js, by adding this to the top:

import { feltToString, stringToFelt } from './utils/utils'

Now we have everything we need, let’s go ahead to write the logic for our setName function:

const setNameFunction = async() => {   try{      // initialize contract using abi, address and provider      const contract = new Contract(contractAbi, contractAddress,      provider)      // convert string to felt      const nameToFelt = stringToFelt(name)      // make contract call      await contract.storeName(nameToFelt)      alert("You've successfully associated your name with this address!")   }   catch(error){      alert(error.message)   }}

As always, the bolded comments, explains each line of code.

If we got everything right, we should get a pop up similar to the image below, which signs the transaction:

3. getName Function

the getName function, takes in an address as an argument, and makes a contract call to the view function getName in our Cairo contract, which returns the name associated with the wallet address of the sender. In order words, this function is a view function and does not modify the state of our contract, and as such do not need to be signed by our wallet.

This function is very much straightforward, but we’d need to import the toBN function from the number utils provided by Starknet.js, to help us with our felt to string conversion:

import { toBN } from "starknet/dist/utils/number"

Now, we can now write the logic for the getName function:

const getNameFunction = async() => {   try{      // initialize contract using abi, address and provider      const contract = new Contract(contractAbi, contractAddress, provider)      // make contract call      const _name = await contract.getName(inputAddress)      // convert resulting felt to string      const _decodedname = feltToString(toBN(_name.toString()))

// set decoded name to retrieveName state
setRetrievedName(_decodedname) } catch(error){ alert(error.message) }}

When we call this function, it gets the name returned from the contract call, decodes it from felt to string, and then set it in the retrievedName state.

When you call this function, you should get the name associated with the wallet address outputted as in the image:

Conclusion

If you got to this point, a big Kudos to you! You just created your first ever UI to interact with Starknet.

Mind you, if you got stuck, i’ve got the solution to this tutorial, in my repo here.

As always, If you got value from this article, do well to share with others.

You could also connect with me on the following socials, especially on Twitter, where i share my little findings on Cairo!

Twitter: https://twitter.com/0xdarlington

LinkedIn: https://www.linkedin.com/in/nnamdarlington

Github: https://github.com/Darlington02

--

--