How to Integrate the ICP Ledger and ICP Index Canisters in a React Frontend Project

How to Integrate the ICP Ledger and ICP Index Canisters in a React Frontend Project

Introduction

The ICP ledger canister is a critical smart contract canister that holds accounts and transactions for the Internet Computer Protocol's (ICP) utility token. Meanwhile, the ICP index canister is used to retrieve transaction history from the ICP ledger canister. In this article, we'll explore how to use these canisters in a frontend React project, allowing you to manage ICP tokens and transaction history with ease.

Prerequisites

  • Prior knowledge of front-end development using HTML, CSS, Javascript, and React
  • dfx installed on your machine
  • Prior knowledge of the command line or Terminal
  • Code editors like VsCode or sublime Text
  • Internet connection
  • Internet Browser

What is DFX

It is a command line execution environment for creating, deploying, and managing dapps on the Internet Computer. It also provides a local execution environment to test and deploy dapps before deploying them on the live network

What is Internet Identity

Internet Identity in the Internet Computer ecosystem is a decentralized, open-source identity system that allows users to create and manage their digital identities. It is built on top of the Internet Computer blockchain, which provides a secure and immutable ledger for storing user identity information.

With Internet Identity, users can create a unique digital identity that is linked to their public key on the blockchain. This identity can be used to authenticate and interact with various decentralized applications and services on the Internet Computer. Internet Identity also allows users to control their data and decide how it is shared with other parties.

Here is what we will build in this project and the code for this project can be found here

Setting up the project

We are going to use a boiler template for this project in order to make this article not long. the template already has the Internet Identity(II) setup. if you want to learn how to set up the internet identity for your project from scratch, I recommend checking out this YouTube video that explains the whole process. With that in mind, the first thing to do is to open up your terminal.

  • Clone the repo.
    git clone https://github.com/sam-the-tutor/React-Motoko-II-Template.git
    
  • Navigate to the project folder
    cd React-Motoko-II-Template
    
  • Install the dependencies
    npm install
    
  • Start the replica and deploy the project
    dfx start --background
    dfx deploy
    
  • Start the development server
    npm start
    

You will be provided with a link usually http://localhost:3000/. Click on that link and the project will be opened in a browser. If everything is okay at this stage, you should see a page like the one below.

Click on the button to log in with your internet Identity, if you have already created one. Since it will be your first time using this project, you will be prompted to create a new identity on the local host to use for this project.

Follow the instructions to create one and you will be automatically logged in once the process is complete. On successful login, you will be presented with this page, that shows a logout button and the principal ID of your Identity.

Note: The reason why we set up the Internet Identity is because the ICP Ledger that we are going to interact with restricts the anonymous Identity from accessing some or most of the functionality available on the ICP Ledger.

that completes the section for the internet identity setup. the next section is to setup the

All the configurations for the Internet Identity are in the use-auth-client.jsx file. In that file, we use the React useContext to make some of our functionality global to the project and we will use that functionality in our project

Setting up the ICP Ledger Canister.

In order to interact with the icp ledger on the local machine, we need to set up one for our project. there is already a guide on how to set up the ICP ledger on the local machine. So I will not waste a lot of time on this section

Open the project in your favorite code editor.

Add this code to the dfx.json file. This defines the necessary information for the ICP ledger canister.

"icp_ledger_canister": {
      "type": "custom",
      "candid": "https://raw.githubusercontent.com/dfinity/ic/d87954601e4b22972899e9957e800406a0a6b929/rs/rosetta-api/icp_ledger/ledger.did",
      "wasm": "https://download.dfinity.systems/ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ledger-canister.wasm.gz",
      "remote": {
        "id": {
          "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
        }
      }
    }

Next, open your terminal and run the code below to install the ICP ledger

dfx identity use minter
export MINTER_ACCOUNT_ID=$(dfx ledger account-id)

dfx identity use default
export DEFAULT_ACCOUNT_ID=$(dfx ledger account-id)

dfx deploy --specified-id ryjl3-tyaaa-aaaaa-aaaba-cai icp_ledger_canister --argument "
  (variant {
    Init = record {
      minting_account = \"$MINTER_ACCOUNT_ID\";
      initial_values = vec {
        record {
          \"$DEFAULT_ACCOUNT_ID\";
          record {
            e8s = 10_000_000_000 : nat64;
          };
        };
      };
      send_whitelist = vec {};
      transfer_fee = opt record {
        e8s = 10_000 : nat64;
      };
      token_symbol = opt \"LICP\";
      token_name = opt \"Local ICP\";
    }
  })
"

In the code above, we first create a new identity minter, get the account-id associated with that identity, and save it an a variable MINTER_ACCOUNT_ID. we repeat the same pricedure for the identity default

we then deploy the icp-ledger-canister with a specific id ryjl3-tyaaa-aaaaa-aaaba-cai, which is the same identity for the ICP ledger on the main network. The code above mints some tokens and sends them to the DEFAULT_ACCOUNT_ID. We will use this account id in our project and also the -----

If thee abovoe step is successful, we should now have a local ICP ledger that we can interact with from our project.

Next is to download the icp ledger npm package that we will use to interact with the ledger.

Downloading the ICP ledger npm package

In order to be able to interact with our ICP Legder from our project, we will need to download and install the icp ledger package and configure it.

Open your terminal and run the command

npm i @dfinity/ledger-icp

Install the other dependencies that the ledger-icp package depends on

npm i @dfinity/agent @dfinity/candid @dfinity/principal @dfinity/utils @dfinity/nns-proto

Now we can use the ledger-icp in our project.

Setting up the frontend code for the project

In the components folder, create a new file Ledger.jsx

Copy and Paste the following code in the file

import React, { useEffect, useState } from 'react'
import { LedgerCanister, AccountIdentifier } from '@dfinity/ledger-icp'
import { createAgent } from '@dfinity/utils'
import { useAuth } from '../../use-auth-client'
import { Principal } from '@dfinity/principal'

In the above code, we import the useEffect and useState hooks form the react package. From the ledger-icp package. we import the LedegerCanister that we will use to interact with the ICP Ledger on and the AccountIdentifier to format the principal IDs.

From the use-auth-client file, we import the useAuth method that will allow use to interact with our Internet Identity in our project Finally, we import the Principal from the @dfinity/principal package

const { identity, logout, principal } = useAuth()
const [ICPLedger, setICPLedger] = useState(null)
const [hexID, setHexID] = useState(null)
const [bal, setBal] = useState(null)

const MY_LEDGER_CANISTER_ID = 'ryjl3-tyaaa-aaaaa-aaaba-cai'
const HOST = `http://localhost:4943?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY}#authorize`

In the above code, we destructure the useAuth function to get the identity, we useState hook to create the ICPLegder,princID and bal hooks Next, we define the constanst to hold the id of our ICP ledger and finally the HOST which will hold the url of our identity provider in our project.

Since we are running the projct locally, we refer to the internet identity canister that we deployed on our machine.

useEffect(() => {
  setUpLedger()
}, [])
async function setUpLedger() {
  const agent = await createAgent({
    identity,
    host: HOST,
  })
  //{ transactionFee, transfer, icrc1Transfer,accountBalance }
  let ledger = LedgerCanister.create({
    agent,
    canisterId: MY_LEDGER_CANISTER_ID,
  })
  setICPLedger(ledger)
  console.log('Icp ledger setup successfully')
}

We use the useEffect react hook to run the setUpLedger function everytime the compeonent is loaded

In our setUpLedger function,we start by creatting an agent that takes in an object as its arguments. the object contains the identity and the host that we have already setup.

Once the agent is created,we use the LedgerCanister method from the ledger-icp package to create an instance of our ledger canister in order to interact with it. it takes in an object as it argument. the object contains the agent that we created from the previous step, as well as the canister id of the ledger canister

We finalize by updating the ICPLedger state to the currently created ledger and then log that the ledger has been created successdully.

Now that we have the our ICP ledger setup correctly, we ca now interact with it from our project.

Checking account balance

During the ICP Ledger installation, we minted icp tokens to a certain account and we are going to check the balance of that account

In your terminal, run this command and copy the account id that will be displayed. the account id is in a HEX format

dfx identity use default
dfx ledger account-id

Now back to your Ledger.jsx file, paste the following code

async function getBalance() {
  const accIdentifier = AccountIdentifier.fromHex(
    //replace this with the account id that you copied from the previous step
    'c534cb97073c30f897c8dfdd50c86406b297ae9028299f4592ac4a0cb6692f06',
  )
  const res = await ICPLedger.accountBalance({
    accountIdentifier: accIdentifier,
    certified: false,
  })
  setBal(Number(res))
  console.log('Icp balance :', res)
}

async function princToAcc() {
  const acc = AccountIdentifier.fromPrincipal({
    principal: principal,
    subAccount: undefined,
  }).toHex()
  setHex(acc)
  console.log(acc)
}

We declare a function getBalance. In the function, we use the AccountIdentifier.fromHex to convert the account id to the account identifier format. Next, we use ICP ledger to check the balance of our account id and then update the bal state with the new value

Next, we declare the princToAcc function. We use the AccountIdentifier method to conveert the principal ID of the loggedin user to its HEX representation.

 <div>
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          gap: '10px',
        }}
      >
        <h2>ICP Ledger Walkthrough</h2>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '10px',
            padding: '10px',
            alignItems: 'center',
          }}
        >
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => logout()}
          >
            Logout
          </button>
          <br/>

          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => getBalance()}
          >
            Get Balance
          </button>
          <span>Balance : {bal}</span>
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => princToAcc()}
          >
            princToAcc
          </button>
          <span>Hex Account : {hexID}</span>
        </div>
      </div>
    </div>

We have some buttons that which when clicked, we call the respective functions. If everything is okay the whole code for the Ledger.jsx

import React, { useEffect, useState } from 'react'
import { LedgerCanister, AccountIdentifier } from '@dfinity/ledger-icp'
import { createAgent } from '@dfinity/utils'
import { useAuth } from '../use-auth-client'
import { Principal } from '@dfinity/principal'
const Ledger = () => {
  const { identity, logout, principal } = useAuth()
  const [ICPLedger, setICPLedger] = useState(null)
  const [hexID, setHexID] = useState(null)
  const [bal, setBal] = useState(null)
  const MY_LEDGER_CANISTER_ID = 'ryjl3-tyaaa-aaaaa-aaaba-cai'
  const HOST = `http://localhost:4943?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY}#authorize`

  useEffect(() => {
    setUpLedger()
  }, [])
  async function setUpLedger() {
    const agent = await createAgent({
      identity,
      host: HOST,
    })
    let ledger = LedgerCanister.create({
      agent,
      canisterId: MY_LEDGER_CANISTER_ID,
    })
    setICPLedger(ledger)
    console.log('Icp ledger setup successfully')
  }

  async function getBalance() {
    const accIdentifier = AccountIdentifier.fromHex(
      'ebc5f90afc543a620a4b05a1621284a4863ff02649206a4be3718b3cd0511571',
    )
    const res = await ICPLedger.accountBalance({
      accountIdentifier: accIdentifier,
      certified: false,
    })
    setBal(Number(res))
    console.log('Icp balance :', res)
  }

  async function princToAcc() {
    const acc = AccountIdentifier.fromPrincipal({
      principal: principal,
      subAccount: undefined,
    }).toHex()
    setHexID(acc)
    console.log(acc)
  }

  return (
    <div>
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          gap: '10px',
        }}
      >
        <h2>ICP Ledger Tutorial</h2>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '10px',
            padding: '10px',
            alignItems: 'center',
          }}
        >
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => logout()}
          >
            Logout
          </button>
          <br />

          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => getBalance()}
          >
            Get Balance
          </button>
          <span>Balance : {bal}</span>
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => princToAcc()}
          >
            princToAcc
          </button>
          <span>Hex Account : {hexID}</span>
        </div>
      </div>
    </div>
  )
}
export default Ledger
import React from 'react'
import { useAuth } from '../use-auth-client'
import Ledger from './Ledger'

const one = () => {
  const { isAuthenticated, login, principal, logout } = useAuth()
  return (
    <>
      {isAuthenticated ? (
        <Ledger />
      ) : (
        <button
          style={{ backgroundColor: 'brown', color: 'white' }}
          onClick={login}
        >
          Log in
        </button>
      )}
    </>
  )
}
export default one

Run the following command to deploy the frontend canister and also generate the .env variables that we need in the Ledger.jsx file

dfx deploy

Import the Ledger.jsx file in the Login.jsx file and configure it shows when the user has logged in successfully

At this stage, if you have followed all the steps correctly, you should have the following screen in your browser after logging in.

You can interact with the project by pressing the buttons to call the respective functions.

Above is the screenshot of the webpage when you call the get Balance and princToAcc functions. The ICP balance showed is the balance of the account ID that received the ICP tokens when we were deploying the ICP canister

To see which other methods are available on the ledger-icp package that we can use on our ICP Ledger, click here

The next step is to set up the ICP Index canister so that we are able to get the transaction history of different account IDs from the ICP Ledger canister.

Setting up the ICP Index Canister.

In this section, we are going to learn how to utilize the ICP Index canister in order to get the transactions from the ICP ledger canister. Unfortunately, there is no package that is available to us to utilize when setting up the Index canister, so we will have to manually set it up.

Setting up the dfx.json

Copy and paste the code in the dfx.json

"icp_index_canister": {
      "type": "custom",
      "candid": "https://raw.githubusercontent.com/dfinity/ic/d87954601e4b22972899e9957e800406a0a6b929/rs/rosetta-api/icp_ledger/index/index.did",
      "wasm": "https://download.dfinity.systems/ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ic-icp-index-canister.wasm.gz",
            "remote": {
        "id": {
          "ic": "qhbym-qaaaa-aaaaa-aaafq-cai"
        }
      }
    }

The above code defines the ICP index canister configurations..

dfx deploy icp_index_canister --specified-id qhbym-qaaaa-aaaaa-aaafq-cai --argument '(record {ledger_id = principal "ryjl3-tyaaa-aaaaa-aaaba-cai"})'

Next, we install the Index canister and link it to our ICP ledger canister. Once the setup is complete, we can now use the canister in our front end.

Frontend ICP Index Canister Setup

In order to use the ICP index canister in our front end, we will need two things, its canister ID and its javascript IDL file. Now, there is a standard javascript IDL file for the ICP Index canister, which can be accessed from the dashboard

The IDL is available in different languages, but for our case, we will select the javascript IDL since we are using javascript in our frontend. In the code editor, navigate to the Ledger folder inside the components folder, and create a new file called index.did.js. Paste the code below and save it

export const idlFactory = ({ IDL }) => {
  const InitArg = IDL.Record({ ledger_id: IDL.Principal })
  const GetAccountIdentifierTransactionsArgs = IDL.Record({
    max_results: IDL.Nat64,
    start: IDL.Opt(IDL.Nat64),
    account_identifier: IDL.Text,
  })
  const Tokens = IDL.Record({ e8s: IDL.Nat64 })
  const TimeStamp = IDL.Record({ timestamp_nanos: IDL.Nat64 })
  const Operation = IDL.Variant({
    Approve: IDL.Record({
      fee: Tokens,
      from: IDL.Text,
      allowance: Tokens,
      expires_at: IDL.Opt(TimeStamp),
      spender: IDL.Text,
    }),
    Burn: IDL.Record({ from: IDL.Text, amount: Tokens }),
    Mint: IDL.Record({ to: IDL.Text, amount: Tokens }),
    Transfer: IDL.Record({
      to: IDL.Text,
      fee: Tokens,
      from: IDL.Text,
      amount: Tokens,
    }),
    TransferFrom: IDL.Record({
      to: IDL.Text,
      fee: Tokens,
      from: IDL.Text,
      amount: Tokens,
      spender: IDL.Text,
    }),
  })
  const Transaction = IDL.Record({
    memo: IDL.Nat64,
    icrc1_memo: IDL.Opt(IDL.Vec(IDL.Nat8)),
    operation: Operation,
    created_at_time: IDL.Opt(TimeStamp),
  })
  const TransactionWithId = IDL.Record({
    id: IDL.Nat64,
    transaction: Transaction,
  })
  const GetAccountIdentifierTransactionsResponse = IDL.Record({
    balance: IDL.Nat64,
    transactions: IDL.Vec(TransactionWithId),
    oldest_tx_id: IDL.Opt(IDL.Nat64),
  })
  const GetAccountIdentifierTransactionsError = IDL.Record({
    message: IDL.Text,
  })
  const Result = IDL.Variant({
    Ok: GetAccountIdentifierTransactionsResponse,
    Err: GetAccountIdentifierTransactionsError,
  })
  const GetBlocksRequest = IDL.Record({
    start: IDL.Nat,
    length: IDL.Nat,
  })
  const GetBlocksResponse = IDL.Record({
    blocks: IDL.Vec(IDL.Vec(IDL.Nat8)),
    chain_length: IDL.Nat64,
  })
  const HttpRequest = IDL.Record({
    url: IDL.Text,
    method: IDL.Text,
    body: IDL.Vec(IDL.Nat8),
    headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)),
  })
  const HttpResponse = IDL.Record({
    body: IDL.Vec(IDL.Nat8),
    headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)),
    status_code: IDL.Nat16,
  })
  const Status = IDL.Record({ num_blocks_synced: IDL.Nat64 })
  return IDL.Service({
    get_account_identifier_balance: IDL.Func(
      [IDL.Text],
      [IDL.Nat64],
      ['query'],
    ),
    get_account_identifier_transactions: IDL.Func(
      [GetAccountIdentifierTransactionsArgs],
      [Result],
      ['query'],
    ),
    get_blocks: IDL.Func([GetBlocksRequest], [GetBlocksResponse], ['query']),
    http_request: IDL.Func([HttpRequest], [HttpResponse], ['query']),
    ledger_id: IDL.Func([], [IDL.Principal], ['query']),
    status: IDL.Func([], [Status], ['query']),
  })
}
export const init = ({ IDL }) => {
  const InitArg = IDL.Record({ ledger_id: IDL.Principal })
  return [InitArg]
}

The above code defines all the methods that we can access on our ICP Index canister from our frontend

In the same folder create a new file and name it createActor.js paste the code below

import { Actor, HttpAgent } from '@dfinity/agent'
import { idlFactory } from './index.did.js'

export const createActor = (canisterId, options = {}) => {
  const agent = options.agent || new HttpAgent({ ...options.agentOptions })

  if (options.agent && options.agentOptions) {
    console.warn(
      'Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent.',
    )
  }

  // Fetch root key for certificate validation during development
  if (process.env.DFX_NETWORK !== 'ic') {
    agent.fetchRootKey().catch((err) => {
      console.warn(
        'Unable to fetch root key. Check to ensure that your local replica is running',
      )
      console.error(err)
    })
  }

  // Creates an actor with using the candid interface and the HttpAgent
  return Actor.createActor(idlFactory, {
    agent,
    canisterId,
    ...options.actorOptions,
  })
}

We import the Actor and HttpAgent from the @dfinity/agent package. We then import the IDL file that we created from the previous step. we declare and export a function createActor that takes in two arguments; the canisterId and the options. the function creates an actor using the parameters supplied and returns so that it can be used.

Modifying the Ledger.jsx file

In the Ledger.jsx file, add the following code.

import { createActor } from './createActor'

We first import the createActor method from the createActor file that we created from the previous step

const [indexCanister, setIndexCanister] = useState(null)
const ICPIndexID = 'qhbym-qaaaa-aaaaa-aaafq-cai'

Using the ueState react hook, we declare the indexcanister state that we will update at a later stage. We also declare a variable ICPIndexID that holds the id of the ICP index canister

const actor = createActor(ICPIndexID, {
  agentOptions: {
    identity,
  },
})

setIndexCanister(actor)
console.log('index canister set up successfully')

Add the above code to the setUpLedger function which we declared when we were setting up the ledger canister. The above code creates our Index canister actor and then updates the indexCanister state so that we can start using our Index canister

Now that we have finished setting up the Index canister, it's time to start using it in our project.

We will use the index canister to get the transaction history for our account ID but we need to do a few more transactions so as to get more transaction history.

In the terminal, we will use the account ID that received our tokens and send some of those tokens to another account ID

dfx identity use default

 dfx canister call icp_ledger_canister send_dfx '
 (record{
    memo= 1234;
    amount=(record{
        e8s=20000;
    });
    fee=(record{
        e8s=10000;
    });
    from_subaccount= null;
    to= "c534cb97073c30f897c8dfdd50c86406b297ae9028299f4592ac4a0cb6692f06";
    created_at_time= null;
 })'

We first switch our identity to the identity that received the ICP tokens. Next, we call the send_dfx method icp_ledger_canister in order to send some tokens to another account ID. The send_dfx method transfers tokens to an account identifier account. In order to transfer tokens to a principal ID, you can use the code below.

 dfx canister call icp_ledger_canister icrc1_transfer '
   (record {
     to=(record {
       owner=(principal "xowmd-grmzf-2dacu-dlg3b-3pqmc-xyu7k-xwr5s-dngy4-uvvlq-kycr4-rqe")
     });
     amount=200_000
   })
 '

You need to transfer the ICP tokens a couple of times in order to get a good number of transactions in your history. Once you are done with the transfers, it is time to go back to the Ledger.jsx code and retrieve the transaction history for our account ID

Paste the following code in the Ledger.jsx,

async function getTransactions() {
  const results = await indexCanister.get_account_identifier_transactions({
    start: [],
    max_results: 10,
    account_identifier:
      'ebc5f90afc543a620a4b05a1621284a4863ff02649206a4be3718b3cd0511571',
  })

  if (results.Ok) {
    console.log('transactions results :', results.Ok.transactions)
  }
}

We declare a function getTransactions function, and we then call the get_account_identifier_transactions method IndexCanister which takes in an object as its argument. the object contains the;

  • start: the index of the transactions in the Index canister from which to start from. In our case, we leave it empty.
  • account_identifier: the account id whose transaction history we want to return.
  • max_results: the number of records to retrieve, for our case, we only want 10. if we leave this empty, all the transactions for the specific account ID will be returned.

If there are results returned, we log them in the browser.

<button
style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
onClick={() => getTransactions()}
>
Get Transactions
</button>

Finally, we add a button in the return section of the page that will call the getTransactions() when clicked

The new code for the Ledger.jsx should look like this

import React, { useEffect, useState } from 'react'
import { LedgerCanister, AccountIdentifier } from '@dfinity/ledger-icp'
import { createAgent } from '@dfinity/utils'
import { useAuth } from '../use-auth-client'
import { Principal } from '@dfinity/principal'
import { createActor } from './createActor'
const Ledger = () => {
  const { identity, logout, principal } = useAuth()
  const [ICPLedger, setICPLedger] = useState(null)
  const [hexID, setHexID] = useState(null)
  const [bal, setBal] = useState(null)
  const MY_LEDGER_CANISTER_ID = 'ryjl3-tyaaa-aaaaa-aaaba-cai'
  const HOST = `http://localhost:4943?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY}#authorize`
  const [indexCanister, setIndexCanister] = useState(null)
  const ICPIndexID = 'qhbym-qaaaa-aaaaa-aaafq-cai'
  useEffect(() => {
    setUpLedger()
  }, [])
  async function setUpLedger() {
    const agent = await createAgent({
      identity,
      host: HOST,
    })
    //{ transactionFee, transfer, icrc1Transfer,accountBalance }
    let ledger = LedgerCanister.create({
      agent,
      canisterId: MY_LEDGER_CANISTER_ID,
    })
    setICPLedger(ledger)
    console.log('Icp ledger setup successfully')
    const actor = createActor(ICPIndexID, {
      agentOptions: {
        identity,
      },
    })

    setIndexCanister(actor)
    console.log('index canister set up successfully')
  }

  async function getTransactions() {
    const results = await indexCanister.get_account_identifier_transactions({
      start: [],
      max_results: 10,
      account_identifier:
        'ebc5f90afc543a620a4b05a1621284a4863ff02649206a4be3718b3cd0511571',
    })

    if (results.Ok) {
      console.log('transactions results :', results.Ok.transactions)
    }
  }

  async function getBalance() {
    const accIdentifier = AccountIdentifier.fromHex(
      'ebc5f90afc543a620a4b05a1621284a4863ff02649206a4be3718b3cd0511571',
    )
    const res = await ICPLedger.accountBalance({
      accountIdentifier: accIdentifier,
      certified: false,
    })
    setBal(Number(res))
    console.log('Icp balance :', res)
  }

  async function princToAcc() {
    const acc = AccountIdentifier.fromPrincipal({
      principal: principal,
      subAccount: undefined,
    }).toHex()
    setHexID(acc)
    console.log(acc)
  }

  return (
    <div>
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          gap: '10px',
        }}
      >
        <h2>ICP Ledger Tutorial</h2>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '10px',
            padding: '10px',
            alignItems: 'center',
          }}
        >
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => logout()}
          >
            Logout
          </button>
          <br />

          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => getBalance()}
          >
            Get Balance
          </button>
          <span>Balance : {bal}</span>
          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => princToAcc()}
          >
            princToAcc
          </button>
          <span>Hex Account : {hexID}</span>

          <button
            style={{ backgroundColor: 'brown', color: 'white', width: '400px' }}
            onClick={() => getTransactions()}
          >
            Get Transactions
          </button>
        </div>
      </div>
    </div>
  )
}
export default Ledger

If you have followed all the previous steps correctly, your frontend should look like the one below

Clicking on the Get Transactions button, you should get something like this in the terminal.

The array contains all the transactions for the account ID that we specified. You can choose to do whatever you want with that information like displaying those transactions in a table on the frontend.

Next Steps

We just looked at a few of the functions that both the ICP Legder and ICP Index canisters provide. Feel free to explore more on how you can use the other functions that we didn't look at.

Conclusion

In this article, you have learned how to set up the ICP ledger canister and use it in the front end, you have also looked at how to use the ICP Index canister in order to get the transaction history of different account ids. In case of any inquiries, let's connect on Twitter. I will be glad to help out.