{"id":21763403,"url":"https://github.com/complexlity/immutable-planner-app","last_synced_at":"2026-04-18T09:39:02.990Z","repository":{"id":204770280,"uuid":"709055416","full_name":"Complexlity/immutable-planner-app","owner":"Complexlity","description":"Step-by-step guide to integrate immutable passport","archived":false,"fork":false,"pushed_at":"2023-11-01T06:28:39.000Z","size":2732,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-21T04:44:21.879Z","etag":null,"topics":["immutable","reactjs"],"latest_commit_sha":null,"homepage":"https://immutable-planner-app.vercel.app","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Complexlity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-23T23:22:50.000Z","updated_at":"2024-01-19T13:07:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"378a3dfc-9ef5-4ae7-ab38-0edfa35be699","html_url":"https://github.com/Complexlity/immutable-planner-app","commit_stats":null,"previous_names":["complexlity/immutable-planner-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Complexlity/immutable-planner-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Complexlity%2Fimmutable-planner-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Complexlity%2Fimmutable-planner-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Complexlity%2Fimmutable-planner-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Complexlity%2Fimmutable-planner-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Complexlity","download_url":"https://codeload.github.com/Complexlity/immutable-planner-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Complexlity%2Fimmutable-planner-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31964538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["immutable","reactjs"],"created_at":"2024-11-26T12:15:17.522Z","updated_at":"2026-04-18T09:38:57.973Z","avatar_url":"https://github.com/Complexlity.png","language":"JavaScript","readme":"# Immutable Passport Integration\n\nIn this guide, I will cover step by step the process of adding immutable passport authentication to an applilcation and creating transactions with it.\n\nBefore proceeding, Note that this could be done in plain html/javascript as well as all javascript frameworks including [svelte](https://svelte.dev/), [react](https://react.dev/), [vue](https://vuejs.org/), etc. For this guide, we would make use of [Nextjs](https://nextjs.org).\n\nHowever, all the core concepts convered here are applicable to all of them.\n\n## Pre-requisites\n\nThe follow this guide, ensure you have the following installed\n\n- [npm/nodejs](https://nodejs.org/en)\n- A Code Editor\n\n## Getting Started\n\nRun the following commands on your terminal to get started\n\n```bash\ngit clone https://github.com/Complexlity/immutable-planner-app-starter immutable-planner-app\ncd immutable-planner-app\nnpm install\nnpm run dev\n```\n\nOpen \u003chttp://localhost:3001\u003e in your browser\n\n![Immutable Planner App Starter](image.png)\n\nYou can also find a [Live Example](https://immutable-planner-app-starter.vercel.app/)\n\n## Register You Application On Immutable Hub\n\nCreate a new file `.env` and copy all the contents of [.env.example](.env.example) into it.\n\n```.env\nNEXT_PUBLIC_LOGOUT_URL=\u003cYour Immutable Hub Logout URL\u003e\nNEXT_PUBLIC_CALLBACK_URL=\u003cYour Immutable Hub Redirect URL\u003e\nNEXT_PUBLIC_CLIENT_ID=\u003cYour Immutable Hub Client Id\u003e\n```\n\nWe need these three values to connect\n\n- Logout URL\n- Callback URL\n- Client Id\n\nFollow the steps below to get the required values\n\n- Go to [hub.immutable.com](https://hub.immutable.com) and create an account.\n- Initialize a project on Immutable zkEvm and a default Environment on Testnet. If you're unsure how to do that, Complete this [Quest 3 Guide](https://app.stackup.dev/quest_page/quest-3---create-an-immutable-passport)\n- Add A passport Client\n![Alt text](image-1.png)\n- Fill the form provided with the following steps\n\n![Alt text](image-2.png)\n\n1. **Application** Type: Web application (remains unchanged). This represents where the application is intented to be run\n2. **Client Name**: give your application any name. This is just an identifier.\n3. **Logout URLs**: This is very **IMPORTANT**. It represents the url the user is redirected to after they logout of the application (In some applications,the default landing page). E.g `https://your-site-name.com/`.\nSince we would be runnig the code locally on port `3001`. Enter http://localhost:3001 into the input box\n4. **Callback URLs**: Also very **IMPORTANT**. When you try to login, it opens a popup direct to this url. This is where the logging in takes place. E.g `https://your-site-name.com/login`.\nSince we are runnign the code on our dev server port `3001`, Enter http://localhost:3001/login into the input box\n\n**IMPORTANT**: When you deploy, you also have to change these URLs to point to the site address.\n\nClick **Create** once you have filled these values.\n\n![Alt text](image-3.png)\n\nCopy the three values and replace them in the `.env` file.\n\n\n## The Bug Before The Storm\n\nIn the course of writing this guide, I ran into a bug in the sdk where it looks for the `global` and `process`. If you ever encounter errors such as `global object missing` or `process missing`, simply add the code above to be run before all others.\n\n```javascript\nif (typeof global === 'undefined') {\n    window.global = window;\n   }\n\n   if (typeof process === 'undefined') {\n    window.process = { env: { NODE_ENV: 'production' } };\n   }\n```\n\n## Initialise the Passport object\n\nThe main package that enables all the passport functions is `@imtbl/sdk`. First we have to install this package into the project.\n\n```bash\nnpm install @imtbl/sdk\n```\n\nTo have access to immutable authentication, you have to import functions `config` and `passport` which will be used to create a new passport instance object.\n\n```javascript\n//import the needed functions\nimport { config, passport } from '@imtbl/sdk';\n\n// Initialize the passport config\nconst passportConfig = {\n  baseConfig: new config.ImmutableConfiguration({\n    environment: config.Environment.SANDBOX\n  }),\n  // This is the client id obtained from the immutable hub\n  clientId: process.env.NEXT_PUBLIC_CLIENT_ID,\n\n  // This is the callback url obtained from the immutable hub\n  redirectUri: process.env.CALLBACK_URL,\n\n  // This is the logour url obtained from the immutable hub\n  logoutRedirectUri: process.env.NEXT_PUBLIC_LOGOUT_URL,\n  audience: 'platform_api',\n  scope: 'openid offline_access email transact'\n};\n\n// Create a new passport instance\nconst passportInstance = typeof window !== 'undefined' ? new passport.Passport(passportConfig) : undefined\n\n```\n\n`typeof window === undefined`. This is a very important step for bundlers and in our Nextjs use case. This is intended to be run only on the browser so the window object would be undefined on the server.\n\nIn the [src folder]('/src') of the project, create a folder `store` and create a file `passportStore.js` in the newly created folder and copy the contents below into it\n\n\u003cdetails\u003e\n\u003csummary\u003estore/passportStore.js\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\nimport { createContext, useContext, useState, useReducer } from 'react';\nimport { config, passport } from '@imtbl/sdk';\n\nconst passportConfig = {\n  baseConfig: new config.ImmutableConfiguration({\n    environment: config.Environment.SANDBOX\n  }),\n  clientId: process.env.NEXT_PUBLIC_CLIENT_ID,\n  redirectUri: process.env.NEXT_PUBLIC_CALLBACK_URL,\n  logoutRedirectUri: process.env.NEXT_PUBLIC_LOGOUT_URL,\n  audience: 'platform_api',\n  scope: 'openid offline_access email transact'\n};\n\nconst passportInstance = typeof window !== 'undefined' ? new passport.Passport(passportConfig) : undefined\n\nexport const MyContext = createContext();\n\nexport function MyProvider({ children }) {\n  const [passportState] = useState(passportInstance);\n\n  return (\n    \u003cMyContext.Provider value={{ passportState }}\u003e\n      {children}\n    \u003c/MyContext.Provider\u003e\n  );\n}\n\nexport function useMyContext() {\n  return useContext(MyContext);\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nAlso replace the file contents in [src/pages/_app.js](src/pages/_app.js) with the code below\n\n\u003cdetails\u003e\n  \u003csummary\u003esrc/pages/_app.js\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\nimport '@/styles/globals.css'\nimport \"@/styles/App.css\";\nimport \"@/styles/styles.css\"\nimport { MyProvider } from '@/store/passportStore'\n\nexport default function App({ Component, pageProps }) {\n  return(\n   `\u003cMyProvider\u003e`\n    \u003cComponent {...pageProps} /\u003e\n   `\u003c/MyProvider\u003e`\n  )\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nWe have created a react context store and put the passport object. This is done so the same passport object is reusable in multiple components (as we would need it). In a different framework, you could as well do something similar though the syntaxes may differ\n\n## Log In User With Passport\n\nAfter initialising the passport object, we can login a user by running the two commands below\n\n```javascript\nconst providerZkevm = passportInstance.connectEvm()\nconst accounts = await providerZkevm.request({ method: \"eth_requestAccounts\" })\n```\n\nFirst, we create a zkEVM provider. This initializes an object that can be used to interact directly with the blockchain using the details of the passportInstance\n\nSecondly, we call an RPC named `eth_requestAccounts`. This is what trigger's the entire login process. It returns an array containing the addresses associated with the user\n\n*Aside*: An RPC (Remote Procedure Call) is simply a defined method provided by the library (in our case) to interact with the ethereum blockchain. In the course of this guide, we would explore some other examples of it\n\nAfter calling `eth_requestAccoutns`,  a popup opens the `Callback Url` (In our case, `/login`)\nIn this route, we would handle the logging in inside the popup and return the data to the home page\n\n```javascript\nawait passportInstance.loginCallback()\n```\n\nThis is the single line of code used in the `/login` route and it should be made to be called on page load\n\n- Plain javascript\n\n```javascript\nwindow.addEventlistener('load',() =\u003e {\n  await passportInstance.loginCallback()\n})\n```\n\n- React\n\n```javascript\nuseEffect(() =\u003e {\n  (async() =\u003e {\n    await passportInstance.logCallback()\n  })()\n})\n```\n\n- Svelte\n\n```javascript\nonMount(async () =\u003e {\n    await passportInstance.loginCallback()\n  });\n```\n\nThese are some different ways to handle it in different frameworks. The most important thing is to do so on page load\n\nUpdate [src/components/NavBar.jsx](src/components/NavBar.jsx) to add the login function\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/components/NavBar.jsx\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\n'use client'\n\nimport { useMyContext } from \"@/store/passportStore\";\nimport Head from \"next/head\";\nimport { useState } from 'react';\n\nexport default function NavButton() {\n  const {passportState: passportInstance, userInfo, dispatch } = useMyContext();\n  const [buttonState, setButtonState] = useState('Connect Passport')\n  const [isLoading, setIsLoading] = useState(false)\n\n  async function login() {\n    if (!passportInstance) return\n    setButtonState(\"...Connecting\")\n    setIsLoading(true)\n    try {\n      console.log(\"I am connecting now\")\n      const providerZkevm = passportInstance.connectEvm()\n      const accounts = await providerZkevm.request({ method: \"eth_requestAccounts\" })\n      // Set the address\n      dispatch({\n        type: 'add_user_info',\n        key: 'address',\n        value: accounts[0]\n      })\n    } catch (error) {\n    console.log(\"Something went wrong\")\n        console.log({ error })\n        setButtonState('Connect Passport')\n          throw error\n    } finally {\n      setIsLoading(false)\n    }\n    setButtonState('Connected')\n    return\n\n  }\n\n  async function logout() {\n    // Logout Function Go Here\n    return\n}\n\n  return (\n \u003c\u003e\n`\u003cHead\u003e`\n        `\u003ctitle\u003eImmutable Planner App\u003c/title\u003e`\n        `\u003cmeta name=\"description\" content=\"Generated by create next app\" /\u003e`\n        `\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e`\n        `\u003clink rel=\"icon\" href=\"/favicon.ico\" /\u003e`\n      `\u003c/Head\u003e`\n      `\u003cdiv className=\"fixed flex justify-end px-4 gap-4 top-0 backdrop-blur-md py-4   w-full\"\u003e`\n          {\n            buttonState === 'Connected'\n            ?\n            \u003c\u003e\n              `\u003cp className=\"px-4 py-2 bg-teal-600 rounded-lg text-gray-200 flex items-center justify-center\"\u003e`{userInfo.email ?? \"Hello world\"} \u003c/`p\u003e`\n                  `\u003cp className=\"px-4 py-2 bg-teal-600 rounded-lg text-gray-200 flex items-center justify-center\"\u003e{userInfo.address ?? \"Hello world\" }\u003c/p\u003e`\n            `\u003cbutton onClick={logout} className=\"bg-red-500 text-grey-800 px-4 py-2 opacity-100 rounded-full text-lg  text-gray-100\"\u003eLogout\u003c/button\u003e`\n            \u003c/\u003e\n            : `\u003cbutton disabled={isLoading} className=\"text-grey-100 px-4 py-2 opacity-100 rounded-full bg-green-500\" onClick={login}\u003e`\n          {buttonState}\n        `\u003c/button\u003e`\n          }\n        `\u003c/div\u003e`\n      \u003c/\u003e\n  );\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nCreate a file in [src/pages/](src/pages/) and call it `login.js`. This is where we would handle the loginCallback(). Also note that this url would match the `Callback Url` we have set in [hub.immutable.com](https://hub.immutable.com) while creating the passport client\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/pages/login.js\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\nimport { useEffect } from 'react';\nimport { useMyContext } from '@/store/passportStore';\n\nexport default function LoginPage() {\n  const { passportState: passportInstance,  } = useMyContext();\n  useEffect(() =\u003e {\n    async function handleLoginCallback() {\n      if (!passportInstance) {\n        return\n      }\n    try {\n        console.log(\"login callback\");\n        await passportInstance.loginCallback();\n    }\n    catch (err) {\n        console.error(\"login callback error\", err);\n    }\n    }\n    handleLoginCallback()\n  }, []);\n\n  return (\n    `\u003cdiv/\u003e`\n  );\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\n\nUpdate [src/store/passportStore.js](src/store/passportStore.js) to store the user details\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/store/passportStore.js\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\nimport { createContext, useContext, useState, useReducer } from 'react';\nimport { config, passport } from '@imtbl/sdk';\n\nconst passportConfig = {\n  baseConfig: new config.ImmutableConfiguration({\n    environment: config.Environment.SANDBOX\n  }),\n  clientId: process.env.NEXT_PUBLIC_CLIENT_ID,\n  redirectUri: process.env.NEXT_PUBLIC_CALLBACK_URL,\n  logoutRedirectUri: process.env.NEXT_PUBLIC_LOGOUT_URL,\n  audience: 'platform_api',\n  scope: 'openid offline_access email transact'\n};\n\nconst passportInstance = typeof window !== 'undefined' ? new passport.Passport(passportConfig) : undefined\n\nexport const MyContext = createContext();\n\nexport function MyProvider({ children }) {\n  const [passportState] = useState(passportInstance);\n  const [userInfo, dispatch] = useReducer(reducer, {address: null, email: null, nickname: null, idToken: null, accessToken: null})\n\n  function reducer(state, action) {\n    const key = action.key\n    const value = action.value\n    switch (action.type) {\n      case \"add_user_info\": {\n        return {\n          ...state,\n          [key]: value\n        }\n      }\n      default: return state\n    }\n  }\n\n  return (\n    \u003cMyContext.Provider value={{ passportState, userInfo, dispatch }}\u003e\n      {children}\n    \u003c/MyContext.Provider\u003e\n  );\n}\n\nexport function useMyContext() {\n  return useContext(MyContext);\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nWe added a user object to the store. This enables us re-use and update this object in different parts of the codebase without having to recreate it.\n\nAfter updating the files, test the login functionality now.\n\n## Getting Logged In User Details\n\nThe `passportInstance` object comes with more functions to get the details of the logged in user. These only work if there's user currently signed in.\nIn your code, ensure to call the `eth_requestAccounts` function and be sure it doesn't error before trying to fetch the user details\n\n1. User's Email and Nickname\n\n```js\nconst userInfo = await passportInstance.getUserInfo()\n```\n\nOn success, the returns an object of the shape:\n\n```js\n{\n  email: \u003cuser's email\u003e\n  sub: \u003cA unique identifier of the logged in user\u003e\n  nickname: \u003cuser's nickname\u003e\n}\n```\n\nYou could then de-structure the object to get the nickname and the email.\n\n```js\nconst email = userInfo.email\nconst nickname= userInfo.nickname\n```\n\n2. User's Access Token\n\nAccess tokens are used to re-authenticate the user. This value is important so the entire login process is not triggered every time the user reloads the page.\n\n```js\nconst accessToken = await passportInstance.getAccessToken()\n```\n\n3. User's Id Token\n\nThis is an identifier for immutable passport users.\n\n```js\nconst idToken = await passportInstance.getIdToken()\n```\n\nNow you  cold fetch and insert these values on the front end. We would show the user email and eth address on the Navbar while the other details will be shown on the Immutable Widget\n\nUpdate [src/components/NavBar.jsx](src/components/NavBar.jsx) with the code below\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/components/NavBar.jsx\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\n'use client'\n\nimport { useMyContext } from \"@/store/passportStore\";\nimport Head from \"next/head\";\nimport Script from \"next/script\";\nimport { useReducer, useState } from 'react';\n\nexport default function NavButton() {\n  const {passportState: passportInstance, userInfo, dispatch } = useMyContext();\n  const [buttonState, setButtonState] = useState('Connect Passport')\n  const [isLoading, setIsLoading] = useState(false)\n\n  async function login() {\n    if (!passportInstance) return\n    setButtonState(\"...Connecting\")\n    setIsLoading(true)\n    try {\n      console.log(\"I am connecting now\")\n      const providerZkevm = passportInstance.connectEvm()\n      const accounts = await providerZkevm.request({ method: \"eth_requestAccounts\" })\n      // Set the address\n      dispatch({\n        type: 'add_user_info',\n        key: 'address',\n        value: accounts[0]\n      })\n      // Fetch user details\n      const user = await passportInstance.getUserInfo()\n      // Set the email\n      dispatch({\n        type: 'add_user_info',\n        key: 'email',\n        value: user.email\n      })\n      //set the nickname\n      dispatch({\n        type: 'add_user_info',\n        key: 'nickname',\n        value: user.nickname\n      })\n      // Fetch user access token\n      const accessToken = await passportInstance.getAccessToken()\n      // set the access token\n      dispatch({\n        type: 'add_user_info',\n        key: 'accessToken',\n        value: accessToken\n      })\n      // Fetch user's id token\n      const idToken = await passportInstance.getIdToken()\n      // set the id token\n      dispatch({\n        type: 'add_user_info',\n        key: 'idToken',\n        value: idToken\n      })\n    } catch (error) {\n    console.log(\"Something went wrong\")\n        console.log({ error })\n        setButtonState('Connect Passport')\n          throw error\n    } finally {\n      setIsLoading(false)\n    }\n    setButtonState('Connected')\n    return\n  }\n   async function logout() {\n    // Logout Function Go Here\n    return\n}\n\n  return (\n \u003c\u003e\n`\u003cHead\u003e`\n        `\u003ctitle\u003eImmutable Planner App\u003c/title\u003e`\n        `\u003cmeta name=\"description\" content=\"Generated by create next app\" /\u003e`\n        `\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e`\n        `\u003clink rel=\"icon\" href=\"/favicon.ico\" /\u003e`\n      `\u003c/Head\u003e`\n      `\u003cdiv className=\"fixed flex justify-end px-4 gap-4 top-0 backdrop-blur-md py-4   w-full\"\u003e`\n          {\n            buttonState === 'Connected'\n            ?\n            \u003c\u003e\n              `\u003cp className=\"px-4 py-2 bg-teal-600 rounded-lg text-gray-200 flex items-center justify-center\"\u003e`{userInfo.email ?? \"Hello world\"} \u003c/`p\u003e`\n                  `\u003cp className=\"px-4 py-2 bg-teal-600 rounded-lg text-gray-200 flex items-center justify-center\"\u003e{userInfo.address ?? \"Hello world\" }\u003c/p\u003e`\n            `\u003cbutton onClick={logout} className=\"bg-red-500 text-grey-800 px-4 py-2 opacity-100 rounded-full text-lg  text-gray-100\"\u003eLogout\u003c/button\u003e`\n            \u003c/\u003e\n            : `\u003cbutton disabled={isLoading} className=\"text-grey-100 px-4 py-2 opacity-100 rounded-full bg-green-500\" onClick={login}\u003e`\n          {buttonState}\n        `\u003c/button\u003e`\n          }\n        `\u003c/div\u003e`\n      \u003c/\u003e\n  );\n}\n\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nWe're showing the user name and email. Also, we now show the logout button when the user is logged in.\n\nNext, we need to populate the immutable widget with the required data. Update [src/components/widgets/ImmutableWidget.jsx](src/components/widgets/ImmutableWidget.jsx) with the code below\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/components/widgets/ImmutableWidget.jsx\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\n'use client'\n\nimport { useMyContext } from \"@/store/passportStore\";\nimport { useRef, useState } from 'react';\n\nexport default function ImmutableWidget() {\n  const { passportState: passportInstance, userInfo } = useMyContext()\n\nreturn (\n    `\u003cdiv className=\"min-w-[400px] max-w-[500px] grid gap-4 py-3 overflow-hidden\"\u003e`\n      `\u003cdetails open\u003e`\n        `\u003csummary className=\"text-white underline text-xl overflow-x-auto max-w-full mb-4\"\u003eUser Details\u003c/summary\u003e`\n      `\u003cdiv className=\"tokens max-w-[500px]\"\u003e`\n        `\u003cdetails open className=\"\" \u003e\u003csummary\u003eId Token\u003c/summary\u003e{userInfo.idToken ?? \"\"}\u003c/details\u003e`\n        `\u003cdetails open\u003e\u003csummary\u003eAccess Token\u003c/summary\u003e{userInfo.accessToken ?? \"\"}\u003c/details\u003e`\n        `\u003cdetails \u003e\u003csummary\u003eNickname\u003c/summary\u003e{userInfo.nickname ?? \"User has no nickname\"}\u003c/details\u003e`\n      `\u003c/div\u003e`\n          `\u003c/details\u003e`\n          `\u003c/div\u003e`\n  );\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nNow on the page, you should see the use details on the immutable widget\n\n## Log Out A User\n\nThe `passportInstance` comes with a `logout` function which when called, logs the user out and redirect the page to the `Logout URLs` we specified while creating the passport client in [hub.immutable.com](https://hub.immutable.com)\n\nCall `passportInstance.logout` in [src/components/NavBar.jsx](src/components/NavBar.jsx)\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/components/NavBar.jsx\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\n....Rest of the code\nasync function logout()  {\n  // Logout Function Go Here\n    await passportInstance.logout();\n    setButtonState('Connect Passport')\n}\n...Restof the code\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nAnd that's it. We are now able to login and logout the user.\n\n## Interacting With The Blockchain using Passport\n\nAs stated [above](#log-in-user-with-passport), we could call other RPC function and interact with the blockchain once the user is signed in. The functions are called on the `providerZkevm` object and not the `passportInstance`\n\nHere are some of them\n\n1. Get Immutable X Gas Price\n\n```js\nconst gasPrice = await providerZkevm.request({ method: 'eth_gasPrice' });\n```\n\n2. Get The Balance In an ETH address\n\n```js\nconst userBalance = await providerZkevm.request({\n      method: 'eth_getBalance',\n      params: [\n        userInfo.address,\n        'latest'\n      ]\n});\n```\n\n3. Get Latest Block Number\n\n```js\nconst latestBlockNumber = await providerZkevm.request({ method: 'eth_blockNumber' });\n```\n\n4. Get Chain Id\n\n```js\nconst chainId = await providerZkevm.request({ method: 'eth_chainId' });\n```\n\n5. Get Transaction By Hash\nThis function fetch the transaction details of any transaction on the [Immutable Testnet Explorer](https://explorer.testnet.immutable.com/txs)\n\n```js\nconst transaction = await provider.request({\n  method: 'eth_getTransactionByHash',\n  params: [\n    \u003ctransaction hash /\u003e\n  ]\n});\n```\n\nSubstitute `\u003ctransaction hash\u003e` with any valid transaction on the immutable testnet and it would return it's value.\n\nIn our code, this function has been made to download the file as json the the user's computer\n\nUpdate [src/componets/widgets/Immutable.jsx](src/components/widgets/ImmutableWidget.jsx)\n\n\u003cdetails\u003e\n\u003csummary\u003esrc/components/widgets/ImmutableWidget.jsx\u003c/summary\u003e\n\u003ccode\u003e\n\u003cpre\u003e\n'use client'\n\nimport { useMyContext } from \"@/store/passportStore\";\nimport { useRef, useState } from 'react';\n\nexport default function ImmutableWidget() {\n  const { passportState: passportInstance, userInfo } = useMyContext()\n  const providerZkevm = passportInstance?.connectEvm()\n  const [isLoading, setIsLoading] = useState(false);\n  const[gasPrice, setGasPrice] = useState('');\n  const[userBalance, setUserBalance] = useState('');\n  const[latestBlockNumber, setLatestBlockNumber] = useState('');\n  const[chainId, setChainId] = useState('');\n\n  async function getGasPrice() {\n    if (!passportInstance || !userInfo.address) return\n    setIsLoading(true)\n    try {\n      const gasPrice = await providerZkevm.request({ method: 'eth_gasPrice' });\n      setGasPrice(gasPrice)\n    } catch (error) {\n      console.log(error)\n    }\n    finally {\n      setIsLoading(false)\n    }\n  }\n\n  async function getUserBalance() {\n    console.log({user: userInfo.address})\n    if (!passportInstance || !userInfo.address) return\n    setIsLoading(true)\n    try {\n      const userBalance = await providerZkevm.request({\n        method: 'eth_getBalance',\n  params: [\n    userInfo.address,\n    'latest'\n  ]\n      });\n      setUserBalance(userBalance)\n    } catch (error) {\n      console.log(error)\n          }\n    finally {\n      setIsLoading(false)\n    }\n  }\n\n  async function getLatestBlockNumber() {\n    console.log({address: userInfo.address})\n  if (!passportInstance || !userInfo.address) return\n    setIsLoading(true)\n    try {\n      const latestBlockNumber = await providerZkevm.request({ method: 'eth_blockNumber' });\n      setLatestBlockNumber(latestBlockNumber)\n    } catch (error) {\n      console.log(error)\n    }\n    finally {\n      setIsLoading(false)\n    }\n}\n\n  async function getChainId() {\n  if (!passportInstance || !userInfo.address) return\n    setIsLoading(true)\n    try {\n      const chainId = await providerZkevm.request({ method: 'eth_chainId' });\n      setChainId(chainId)\n    } catch (error) {\n      console.log(error)\n    }\n    finally {\n      setIsLoading(false)\n    }\n}\n\nasync function getTransactionByHash(e) {\n    e.preventDefault()\n    let hash = e.target.hash.value\n\n  // if (!passportInstance || !userInfo.address) return\n    setIsLoading(true)\n    if (!hash) {\n      // Default hash value if not provided\n      hash = \"0xa0d300ac90e69f3ba6274ca1a712219951b79ba6c0117f538fe16c016a701951\"\n    }\n    try {\n      const transaction = await providerZkevm.request({\n  method: 'eth_getTransactionByHash',\n  params: [\n    hash\n  ]\n      });\n      // Download file into user's machine as trasaction.json\n       const blob = new Blob([JSON.stringify(transaction, null, 2)], { type: 'application/json' });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement('a');\n    link.href = url;\n    link.download = 'transaction.json';\n    document.body.appendChild(link);\n    link.click();\n    document.body.removeChild(link);\n    URL.revokeObjectURL(url);\n    } catch (error) {\n      console.log(error)\n      alert(\"Something went wrong. Please try again\")\n    }\n    finally {\n      setIsLoading(false)\n    }\n}\n\nreturn (\n    `\u003cdiv className=\"min-w-[400px] max-w-[500px] grid gap-4 py-3 overflow-hidden\"\u003e`\n      `\u003cdetails open\u003e`\n        `\u003csummary className=\"text-white underline text-xl overflow-x-auto max-w-full mb-4\"\u003eUser Details\u003c/summary\u003e`\n      `\u003cdiv className=\"tokens max-w-[500px]\"\u003e`\n        `\u003cdetails open className=\"\" \u003e\u003csummary\u003eId Token\u003c/summary\u003e{userInfo.idToken ?? \"\"}\u003c/details\u003e`\n        `\u003cdetails open\u003e\u003csummary\u003eAccess Token\u003c/summary\u003e{userInfo.accessToken ?? \"\"}\u003c/details\u003e`\n        `\u003cdetails \u003e\u003csummary\u003eNickname\u003c/summary\u003e{userInfo.nickname ?? \"User has no nickname\"}\u003c/details\u003e`\n      `\u003c/div\u003e`\n          `\u003c/details\u003e`\n      `\u003cdetails\u003e`\n      `\u003csummary className=\"text-white text-xl underline mb-4\"\u003e`\n        `{isLoading ?`\n          `\u003csvg class=\"animate-spin mr-3 h-5 w-5 text-white\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\"\u003e`\n            `\u003ccircle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"\u003e\u003c/circle\u003e`\n            `\u003cpath class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\u003e\u003c/path\u003e`\n          `\u003c/svg\u003e : null}`\n         Rpc Methods`\u003c/summary\u003e`\n        `\u003cdiv className=\"grid gap-2\"\u003e`\n        `\u003cdiv  className=\"flex gap-2\"\u003e`\n          `\u003cbutton disabled={isLoading} onClick={getGasPrice} className=\"w-full rounded-full px-3 py-1 bg-green-400 hover:bg-green-500\"\u003eGet Imx Gas Price\u003c/button\u003e`\n          `\u003cdiv className='bg-white w-full rounded-sm py-1 px-2 placeholder:text-gray-800 placeholder:italic'\u003e`\n            {gasPrice}\n          `\u003c/div\u003e`\n        `\u003c/div\u003e`\n        `\u003cdiv  className=\"flex gap-2\"\u003e`\n          `\u003cbutton disabled={isLoading} onClick={getUserBalance} className=\"w-full rounded-full px-3 py-1 bg-green-400 hover:bg-green-500\"\u003eGet User Balance\u003c/button\u003e`\n          `\u003cdiv className='bg-white w-full rounded-sm py-1 px-2 placeholder:text-gray-800 placeholder:italic'\u003e`\n            {userBalance}\n          `\u003c/div\u003e`\n        `\u003c/div\u003e`\n        `\u003cdiv  className=\"flex gap-2\"\u003e`\n          `\u003cbutton disabled={isLoading} onClick={getLatestBlockNumber} className=\"w-full rounded-full px-3 py-1 bg-green-400 hover:bg-green-500\"\u003e`\n            Get Latest Block Number\n          `\u003c/button\u003e`\n          `\u003cdiv className='bg-white w-full rounded-sm py-1 px-2 placeholder:text-gray-800 placeholder:italic'\u003e{latestBlockNumber}\u003c/div\u003e`\n        `\u003c/div\u003e`\n        `\u003cdiv  className=\"flex gap-2\"\u003e`\n          `\u003cbutton disabled={isLoading} onClick={getChainId} className=\"w-full rounded-full px-3 py-1 bg-green-400 hover:bg-green-500\"\u003eGet Chain Id\u003c/button\u003e`\n          `\u003cdiv className='bg-white w-full rounded-sm py-1 px-2 placeholder:text-gray-800 placeholder:italic'\u003e{chainId}\u003c/div\u003e`\n        `\u003c/div\u003e`\n         `\u003cform onSubmit={getTransactionByHash} className=\"px-1\"\u003e`\n          `\u003cp className=\"mx-auto text-white text-center text-xl mb-2 mt-4\"\u003e`\n            Get Transaction By Hash\n          `\u003c/p\u003e`\n          `\u003cdiv className=\"flex gap-4\"\u003e`\n          `\u003cinput type=\"text\" placeholder=\"hash\" name=\"hash\" className=\"w-full px-2 py-2 rounded-xl\" /\u003e`\n          `\u003cbutton disabled={isLoading} className=\" rounded-full px-3 py-1 bg-green-400 hover:bg-green-500\"\u003eSend\u003c/button\u003e`\n          `\u003c/div\u003e`\n        `\u003c/form\u003e`\n        `\u003csmall className=\"text-gray-300 text-center\"\u003e\u003cspan className=\"text-green-400\"\u003eTip\u003c/span\u003e: You can get example hashed from \u003ca className=\"underline hover:no-underline text-amber-400 italic\" href=\"https://explorer.testnet.immutable.com/txs\" target=\"_blank\"\u003eImmutable Explorer\u003c/a\u003e\u003c/small\u003e`\n`\u003c/div\u003e`\n      `\u003c/details\u003e`\n    `\u003c/div\u003e`\n    )\n}\n\u003c/pre\u003e\n\u003c/code\u003e\n\u003c/details\u003e\n\nFully adding all the RPC functions to the immutable widget. We're now able to call all of them once we've logged in using immutable passport\n\n## Conclusion\n\nWe have seen how powerful the Immutable zkEvm passport is and how easy it is to integrate into any application and interact with the blockchain.\n\nUsing the techniques provided by this guide, you could build web application ranging from simple to complex and add the passport authentication in just minutes.\n\n## Resources\n\n- The Demp Project Full Source Code - [Github](https://github.com/Complexlity/immutable-planner-app)\n- The Demo Project Live - [Immutable Planner App](https://immutable-planner-app.vercel.app)\n- The Immutable Passport [Official Documentation](https://docs.immutable.com/docs/zkEVM/products/passport)\n- The Writer Of this Awesome Guide - [Complexlity](https://github.com/complexlity)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomplexlity%2Fimmutable-planner-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcomplexlity%2Fimmutable-planner-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomplexlity%2Fimmutable-planner-app/lists"}