Introducing @ic-reactor/core: Streamline Your Development on the Internet Computer

Hello, fellow developers!

I’m excited to introduce @ic-reactor/core, a comprehensive toolkit designed to simplify your interactions with the Internet Computer (IC) from the frontend. This package is part of the larger ic-reactor project, aimed at enhancing your development experience on the IC platform.

Key Features:

  • Simplified Agent and Actor Management: Manage your IC agents and actors efficiently, with type-safe communication right out of the box.
  • Seamless Interaction with Canisters: Our friendly API makes it easier than ever to authenticate, query, and update actors without the hassle.
  • Modular Design: Support for managing multiple actors allows you to modularize your interaction with different services on the IC, keeping your codebase clean and organized.
  • Candid Adapter: Fetch Candid interface definitions directly from the IC, enabling you to adapt to canisters with changing interfaces or to avoid managing Candid files locally.
  • UMD Build: For developers who prefer working directly with HTML/JavaScript, we offer a UMD build that allows you to integrate IC functionality directly into your web applications without the complexity of modern JavaScript build tools.
  • Redux-like State Management: Manage the state of your actors with ease, using a familiar state management pattern similar to Redux with devtools support.

Getting Started:

Installation is a breeze with npm or Yarn:

npm install @ic-reactor/core
# or
yarn add @ic-reactor/core

or you can use the UMD version:

<script src="https://github.com/B3Pay/ic-reactor/releases/download/1.1.1/ic-reactor-core.min.js"></script>

Creating Your First Reactor:

Jump straight into creating your first Reactor instance with the createReactorCore factory function. This function sets up a new Reactor instance, managing the agent and its state internally, offering a simple API for your development needs.

Example 1: Basic Setup with createReactorCore

This example demonstrates how to set up a Reactor instance for managing interactions with a canister, including authentication and making query and update calls.

import { createReactorCore } from "@ic-reactor/core"
import { candid, canisterId, idlFactory } from "./declarations/candid"

type Candid = typeof candid

const reactor = createReactorCore<Candid>({
  canisterId,
  idlFactory,
  withProcessEnv: true, // Utilizes process.env.DFX_NETWORK
})

// Checking if the user is authenticated
await reactor.authenticate()

// Logging in with the user's identity
await reactor.login({
  onSuccess: () => console.log("Logged in successfully"),
  onError: (error) => console.error("Failed to login:", error),
})

// Making a query call
const { subscibe: subscribeBalance } = reactor.queryCall({
  functionName: "icrc1_balance_of",
  args: [{ owner: reactor.getPrincipal(), subaccount: [] }],
  refetchInterval: 1000,
})

// Subscribing to the call's state changes
subscribeBalance(({ loading, error, data }) => {
  console.log({ loading, error, data })
})

// Making an update call
const { call: transferCall, data: transferResult } = reactor.updateCall({
  functionName: "icrc1_transfer",
  args: [
    {
      to: { owner: reactor.getPrincipal(), subaccount: [] },
      amount: BigInt(10000000000),
      fee: [],
      memo: [],
      created_at_time: [],
      from_subaccount: [],
    },
  ],
})

// Executing the update call
const transferResult = await transferCall()
console.log(transferResult)

Example 2: Using CandidAdapter to Fetch candid

The CandidAdapter facilitates interaction with a canister by fetching its Candid interface definition directly. This can be especially useful for applications that need to adapt to canisters with changing interfaces or for developers who prefer not to manage Candid files locally.

import {
  createCandidAdapter,
  createAgentManager,
  createReactorCore,
} from "@ic-reactor/core"

// Create an AgentManager instance
const agentManager = createAgentManager()

// Initialize the CandidAdapter with the agentManager
const candidAdapter = createCandidAdapter({ agentManager })

// Define the canister ID you wish to interact with
const canisterId = "your-canister-id"

// Usage example: Fetching the Candid definition to obtain the idlFactory
async function setupReactorCoreWithCandidAdapter(canisterId) {
  try {
    const { idlFactory } = await candidAdapter.getCandidDefinition(canisterId)

    // Once you have the idlFactory, you can create the ReactorCore
    return createReactorCore({
      agentManager,
      canisterId,
      idlFactory,
    })
  } catch (error) {
    console.error("Failed to setup ReactorCore with CandidAdapter:", error)
    throw error // Rethrow or handle error appropriately
  }
}

// Example call to setup the reactor core with the fetched idlFactory
setupReactorCoreWithCandidAdapter(canisterId)
  .then((reactor) => {
    // ReactorCore is now ready to be used for further canister interactions
    // For example, calling a method on the canister
    const response = await reactor.callMethod("YourMethodName")
    console.log("response:", response)
  })
  .catch((error) => {
    // Handle errors, such as failure to fetch the idlFactory or setup the core
    console.error(error)
  })

Example 3: Simplifying Internet Computer Development with @ic-reactor/core UMD

For developers looking to integrate Internet Computer functionality directly into their web applications without the complexity of modern JavaScript build tools. Below is a basic example that demonstrates how to use @ic-reactor/core via a UMD build, enabling interactions with the Internet Computer directly from your HTML files.

This example showcases user authentication and a simple canister interaction:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IC Reactor Example</title>
  </head>

  <body>
    <h1>Authenticate and Fetch Data from the ICP blockchain</h1>
    <div id="userInfo"></div>
    <button id="loginBtn">Login</button>
    <div id="canisterData"></div>
    <button id="refetchBtn">Refetch</button>
  </body>
  <script src="https://github.com/B3Pay/ic-reactor/releases/download/v1.1.1/ic-reactor-core.js"></script>
  <script>
    const loginBtn = document.getElementById("loginBtn")
    const userInfo = document.getElementById("userInfo")
    const canisterData = document.getElementById("canisterData")
    const refetchBtn = document.getElementById("refetchBtn")

    const agentManager = Reactor.createAgentManager({ withDevtools: true })

    agentManager.subscribeAuthState(
      ({ identity, authenticating, authenticated }) => {
        if (authenticating) {
          loginBtn.innerText = "Authenticating..."
        } else if (authenticated) {
          loginBtn.onclick = agentManager.logout
          loginBtn.innerText = "Logout"
        } else {
          loginBtn.onclick = agentManager.login
          loginBtn.innerText = "Login"
        }

        userInfo.innerText = identity?.getPrincipal().toText()
      }
    )

    agentManager.authenticate()

    const candidAdapter = Reactor.createCandidAdapter({ agentManager })

    async function createReactor(canisterId) {
      const { idlFactory } = await candidAdapter.getCandidDefinition(canisterId)

      return Reactor.createReactorCore({
        agentManager,
        canisterId,
        idlFactory,
        withDevtools: true,
      })
    }

    createReactor("ryjl3-tyaaa-aaaaa-aaaba-cai").then((reactor) => {
      const owner = agentManager.getPrincipal()
      const { subscribe, call } = reactor.queryCall({
        functionName: "icrc1_balance_of",
        args: [{ owner, subaccount: [] }],
      })

      refetchBtn.onclick = () => {
        const owner = agentManager.getPrincipal()
        call([{ owner, subaccount: [] }])
      }

      subscribe(({ data, loading, error }) => {
        if (loading) {
          canisterData.innerText = "Loading..."
        } else if (error) {
          canisterData.innerText = error.message
        } else {
          canisterData.innerText = Reactor.utils.jsonToString(data)
        }
      })
    })
  </script>
</html>

This approach can be useful for creating simple web applications that can be serve directly from the backend canister.

Documentation and Examples:

For a deep dive into all the features and to get your hands on more examples, check out documentation and examples.

Contributing:

We welcome your feedback and contributions to the @ic-reactor project. If you have any questions, suggestions, or issues, feel free to reach out to us on GitHub.


For React developers looking to dive deeper into utilizing the Internet Computer within their projects, we’ve also prepared a dedicated thread focusing on the @ic-reactor/react library. This comprehensive guide is tailored for React applications, offering insights into efficiently integrating IC functionalities with React’s ecosystem. Discover more about harnessing the full potential of the Internet Computer in your React projects by visiting our thread: Introduction to IC-Reactor for React Developers.

10 Likes

@b3hr4d Nice work, I’ve been playing a little bit with it, it’s quite fast to make it work which is pleasant :nerd_face:

However I have two points that make me doubt using it:

  • is it ever possible to have a type verification on the function name and arguments when doing a queryCall/updateCall or is it always using a string?
  • is it possible to share the authentication between many actors, so you only have to login once with II?
1 Like

Thank you, @sardariuss! I appreciate your feedback and enthusiasm. Regarding your points:

  1. We actually have type verification for function names and arguments in queryCall/updateCall. You can explore how it’s implemented in our example here: ic-reactor TypeScript Example.

  2. As for sharing authentication between multiple actors with a single II login, this feature is also available.

Would you be open to sharing your repository? I’d love to help you get started with ic-reactor and provide any assistance you might need!

1 Like

Thanks for the quick answer! Yes it’s here. You should be able to deploy everything by calling install-local.sh

I realized the current implementation is not working anymore, I’m not sure what I’ve did last time…

Firstly, you did a great job with ic-reactor! I’ve already made a pull request with some small changes to your repository. You can check it out here: Pull Request #6.

I couldn’t run it on my machine, but it should be fine. The changes should help improve functionality.

Looking forward to your feedback!

1 Like

Thank you! I think it helped, but I realized I have an error which is probably linked to my build :ninja: When I access my frontend with “vite --port 3000” it initializes the actor on the local network and it works fine, but when accessing it via the deployed frontend, I got “Initializing actor bkyz2-fmaaa-aaaaa-qaaaq-cai on IC network” and then a few errors with code 400

I highly recommend using this vite template, it will uses ic-reactor!

1 Like