Skip to content

Cartridge Controller React Integration

This guide demonstrates how to integrate the Cartridge Controller with a React application.

Installation

npm
npm install @cartridge/connector @cartridge/controller @starknet-react/core @starknet-react/chains starknet
npm install -D tailwindcss vite-plugin-mkcert

Basic Setup

1. Configure the Starknet Provider

First, set up the Starknet provider with the Cartridge Controller connector:

You can customize the ControllerConnector by providing configuration options during instantiation. The ControllerConnector accepts an options object that allows you to configure various settings such as policies, RPC URLs, theme, and more.

⚠️ Important: The ControllerConnector instance must be created outside of any React components. Creating it inside a component will cause the connector to be recreated on every render, which can lead to connection issues.

// typescript:src/context/StarknetProvider.tsx
import { sepolia, mainnet, Chain } from '@starknet-react/chains'
import { StarknetConfig, voyager, Connector } from '@starknet-react/core'
import { RpcProvider } from 'starknet'
import ControllerConnector from '@cartridge/connector/controller'
 
// Define your contract addresses
const ETH_TOKEN_ADDRESS =
  '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'
 
// Initialize the connector with policies
const connector = new ControllerConnector({
  rpc: 'https://api.cartridge.gg/x/starknet/sepolia',
  policies: [
    {
      target: ETH_TOKEN_ADDRESS,
      method: 'approve',
      description: 'Approval description here',
    },
    {
      target: ETH_TOKEN_ADDRESS,
      method: 'transfer',
    },
  ],
})
 
// Configure RPC provider
function provider(chain: Chain) {
  switch (chain) {
    case mainnet:
      return new RpcProvider({
        nodeUrl: 'https://api.cartridge.gg/x/starknet/mainnet',
      })
    case sepolia:
    default:
      return new RpcProvider({
        nodeUrl: 'https://api.cartridge.gg/x/starknet/sepolia',
      })
  }
}
 
export function StarknetProvider({ children }: { children: React.ReactNode }) {
  return (
    <StarknetConfig
      chains={[mainnet, sepolia]}
      provider={provider}
      connectors={[connector as never as Connector]}
      explorer={voyager}
    >
      {children}
    </StarknetConfig>
  )
}
## Errors were thrown in the sample, but not included in an error tag These errors were not marked as being expected: 2307 2749 2304 2464 2695 2630 2552 2365 1005 1161 1128. Expected: // @errors: 2307 2749 2304 2464 2695 2630 2552 2365 1005 1161 1128 Compiler Errors: index.ts [2307] 257 - Cannot find module '@cartridge/connector/controller' or its corresponding type declarations. [2749] 1217 - 'StarknetConfig' refers to a value, but is being used as a type here. Did you mean 'typeof StarknetConfig'? [2304] 1238 - Cannot find name 'chains'. [2464] 1246 - A computed property name must be of type 'string', 'number', 'symbol', or 'any'. [2695] 1247 - Left side of comma operator is unused and has no side effects. [2630] 1272 - Cannot assign to 'provider' because it is a function. [2552] 1298 - Cannot find name 'connectors'. Did you mean 'connector'? [2464] 1310 - A computed property name must be of type 'string', 'number', 'symbol', or 'any'. [2304] 1351 - Cannot find name 'explorer'. [2365] 1360 - Operator '>' cannot be applied to types '{ voyager: ExplorerFactory<VoyagerExplorer>; }' and '{ children: React.ReactNode; }'. [2365] 1360 - Operator '<' cannot be applied to types 'boolean' and 'RegExp'. [1005] 1238 - '>' expected. [1005] 1244 - ')' expected. [1005] 1264 - ':' expected. [1005] 1343 - ':' expected. [1161] 1398 - Unterminated regular expression literal. [1128] 1417 - Declaration or statement expected.

2. Create a Wallet Connection Component

Use the useConnect, useDisconnect, and useAccount hooks to manage wallet connections:

// typescript:src/components/ConnectWallet.tsx
import { useAccount, useConnect, useDisconnect } from '@starknet-react/core'
import { useEffect, useState } from 'react'
import ControllerConnector from '@cartridge/connector/controller'
import { Button } from '@cartridge/ui-next'
 
export function ConnectWallet() {
  const { connect, connectors } = useConnect()
  const { disconnect } = useDisconnect()
  const { address } = useAccount()
  const controller = connectors[0] as ControllerConnector
  const [username, setUsername] = useState<string>()
 
  useEffect(() => {
    if (!address) return
    controller.username()?.then((n) => setUsername(n))
  }, [address, controller])
 
  return (
    <div>
      {address && (
        <>
          <p>Account: {address}</p>
          {username && <p>Username: {username}</p>}
        </>
      )}
      {address ? (
        <Button onClick={() => disconnect()}>Disconnect</Button>
      ) : (
        <Button onClick={() => connect({ connector: controller })}>
          Connect
        </Button>
      )}
    </div>
  )
}
## Errors were thrown in the sample, but not included in an error tag These errors were not marked as being expected: 2307 7006 2872 2304 2552 2349 2365 1005 1110 1128 1136 1109 1161. Expected: // @errors: 2307 7006 2872 2304 2552 2349 2365 1005 1110 1128 1136 1109 1161 Compiler Errors: index.ts [2307] 200 - Cannot find module '@cartridge/connector/controller' or its corresponding type declarations. [2307] 257 - Cannot find module '@cartridge/ui-next' or its corresponding type declarations. [7006] 626 - Parameter 'n' implicitly has an 'any' type. [2872] 692 - This kind of expression is always truthy. [2304] 693 - Cannot find name 'div'. [2304] 740 - Cannot find name 'p'. [2304] 742 - Cannot find name 'Account'. [2304] 789 - Cannot find name 'p'. [2552] 791 - Cannot find name 'Username'. Did you mean 'username'? [2304] 845 - Cannot find name 'address'. [2552] 873 - Cannot find name 'onClick'. Did you mean 'onclick'? [2349] 881 - This expression is not callable. Type '{}' has no call signatures. [2552] 888 - Cannot find name 'disconnect'. Did you mean 'useDisconnect'? [2365] 901 - Operator '<' cannot be applied to types 'boolean' and 'RegExp'. [2552] 902 - Cannot find name 'Disconnect'. Did you mean 'useDisconnect'? [2552] 950 - Cannot find name 'onClick'. Did you mean 'onclick'? [2349] 958 - This expression is not callable. Type '{}' has no call signatures. [2304] 965 - Cannot find name 'connect'. [2304] 986 - Cannot find name 'controller'. [2365] 1000 - Operator '<' cannot be applied to types 'boolean' and 'RegExp'. [2304] 1012 - Cannot find name 'Connect'. [1005] 713 - ',' expected. [1110] 727 - Type expected. [1005] 749 - ')' expected. [1110] 761 - Type expected. [1005] 799 - ';' expected. [1110] 812 - Type expected. [1110] 826 - Type expected. [1128] 835 - Declaration or statement expected. [1005] 873 - '>' expected. [1005] 880 - ')' expected. [1136] 882 - Property assignment expected. [1005] 885 - ':' expected. [1109] 901 - Expression expected. [1161] 913 - Unterminated regular expression literal. [1128] 928 - Declaration or statement expected. [1128] 930 - Declaration or statement expected. [1005] 950 - '>' expected. [1005] 957 - ')' expected. [1136] 959 - Property assignment expected. [1005] 962 - ';' expected. [1128] 999 - Declaration or statement expected. [1109] 1000 - Expression expected. [1161] 1029 - Unterminated regular expression literal. [1128] 1044 - Declaration or statement expected. [1128] 1045 - Declaration or statement expected. [1110] 1052 - Type expected. [1128] 1060 - Declaration or statement expected. [1128] 1062 - Declaration or statement expected.

3. Performing Transactions

Execute transactions using the account object from useAccount hook:

// typescript:src/components/TransferEth.tsx
import { useAccount, useExplorer } from '@starknet-react/core'
import { useCallback, useState } from 'react'
 
const ETH_CONTRACT =
  '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'
 
export const TransferEth = () => {
  const [submitted, setSubmitted] = useState<boolean>(false)
  const { account } = useAccount()
  const explorer = useExplorer()
  const [txnHash, setTxnHash] = useState<string>()
 
  const execute = useCallback(
    async (amount: string) => {
      if (!account) return
      setSubmitted(true)
      setTxnHash(undefined)
      try {
        const result = await account.execute([
          {
            contractAddress: ETH_CONTRACT,
            entrypoint: 'approve',
            calldata: [account?.address, amount, '0x0'],
          },
          {
            contractAddress: ETH_CONTRACT,
            entrypoint: 'transfer',
            calldata: [account?.address, amount, '0x0'],
          },
        ])
        setTxnHash(result.transaction_hash)
      } catch (e) {
        console.error(e)
      } finally {
        setSubmitted(false)
      }
    },
    [account],
  )
 
  if (!account) return null
 
  return (
    <div>
      <h2>Transfer ETH</h2>
      <button onClick={() => execute('0x1C6BF52634000')} disabled={submitted}>
        Transfer 0.005 ETH
      </button>
      {txnHash && (
        <p>
          Transaction hash:{' '}
          <a
            href={explorer.transaction(txnHash)}
            target="blank"
            rel="noreferrer"
          >
            {txnHash}
          </a>
        </p>
      )}
    </div>
  )
}
## Errors were thrown in the sample, but not included in an error tag These errors were not marked as being expected: 2304 2349 18004 2552 2365 1005 1161 1136 1128 1110. Expected: // @errors: 2304 2349 18004 2552 2365 1005 1161 1136 1128 1110 Compiler Errors: index.ts [2304] 1213 - Cannot find name 'div'. [2304] 1225 - Cannot find name 'h2'. [2304] 1228 - Cannot find name 'Transfer'. [2304] 1237 - Cannot find name 'ETH'. [2304] 1253 - Cannot find name 'button'. [2304] 1260 - Cannot find name 'onClick'. [2349] 1268 - This expression is not callable. Type '{}' has no call signatures. [2304] 1275 - Cannot find name 'execute'. [2304] 1303 - Cannot find name 'disabled'. [18004] 1313 - No value exists in scope for the shorthand property 'submitted'. Either declare one or provide an initializer. [2304] 1333 - Cannot find name 'Transfer'. [2304] 1348 - Cannot find name 'ETH'. [2304] 1375 - Cannot find name 'txnHash'. [2304] 1397 - Cannot find name 'p'. [2552] 1410 - Cannot find name 'Transaction'. Did you mean 'IDBTransaction'? [2304] 1444 - Cannot find name 'a'. [2304] 1458 - Cannot find name 'href'. [2304] 1464 - Cannot find name 'explorer'. [2304] 1485 - Cannot find name 'txnHash'. [2304] 1507 - Cannot find name 'target'. [2304] 1534 - Cannot find name 'rel'. [2365] 1538 - Operator '>' cannot be applied to types 'string' and '{ txnHash: any; }'. [2365] 1538 - Operator '<' cannot be applied to types 'boolean' and 'RegExp'. [2365] 1538 - Operator '<' cannot be applied to types 'boolean' and 'RegExp'. [18004] 1576 - No value exists in scope for the shorthand property 'txnHash'. Either declare one or provide an initializer. [1005] 1237 - ')' expected. [1161] 1241 - Unterminated regular expression literal. [1005] 1260 - ';' expected. [1136] 1269 - Property assignment expected. [1005] 1272 - ';' expected. [1128] 1301 - Declaration or statement expected. [1005] 1342 - ';' expected. [1005] 1348 - ';' expected. [1161] 1359 - Unterminated regular expression literal. [1005] 1422 - ')' expected. [1005] 1458 - '>' expected. [1005] 1462 - ';' expected. [1161] 1596 - Unterminated regular expression literal. [1161] 1609 - Unterminated regular expression literal. [1128] 1619 - Declaration or statement expected. [1110] 1627 - Type expected. [1128] 1635 - Declaration or statement expected. [1128] 1637 - Declaration or statement expected.

4. Add Components to Your App

// typescript:src/App.tsx
import { StarknetProvider } from './context/StarknetProvider'
import { ConnectWallet } from './components/ConnectWallet'
import { TransferEth } from './components/TransferEth'
 
function App() {
  return (
    <StarknetProvider>
      <ConnectWallet />
      <TransferEth />
    </StarknetProvider>
  )
}
export default App
## Errors were thrown in the sample, but not included in an error tag These errors were not marked as being expected: 2307 1005 1109 1110. Expected: // @errors: 2307 1005 1109 1110 Compiler Errors: index.ts [2307] 59 - Cannot find module './context/StarknetProvider' or its corresponding type declarations. [2307] 118 - Cannot find module './components/ConnectWallet' or its corresponding type declarations. [2307] 175 - Cannot find module './components/TransferEth' or its corresponding type declarations. [1005] 275 - '>' expected. [1109] 298 - Expression expected. [1110] 305 - Type expected.

Important Notes

Make sure to use HTTPS in development by configuring Vite:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert'
 
export default defineConfig({
  plugins: [react(), mkcert()],
})
## Errors were thrown in the sample, but not included in an error tag These errors were not marked as being expected: 2307. Expected: // @errors: 2307 Compiler Errors: index.ts [2307] 29 - Cannot find module 'vite' or its corresponding type declarations. [2307] 54 - Cannot find module '@vitejs/plugin-react' or its corresponding type declarations. [2307] 96 - Cannot find module 'vite-plugin-mkcert' or its corresponding type declarations.