FRONTEND

React

Bind the hooks to your client once. Every call site is then typed off your ABI.

React context cannot be generic, so the hooks are not free functions you import from the package. You bind them to your client once, next to where you created it, and export the result. Every call site then knows your ABI.

app/game.tsx
import { createInterludeHooks } from "@interludelayer-sdk/sdk/react";
import { interlude } from "@/lib/demo";

export const { InterludeProvider, useSession, useSessionCall, useRead } =
  createInterludeHooks(interlude);

function Floor() {
  const { session, open, isOpening } = useSession();
  const join = useSessionCall("join");
  const move = useSessionCall("move");

  if (!session) {
    return (
      <button onClick={() => open()}>
        {isOpening ? "Check your wallet" : "Enter"}
      </button>
    );
  }

  return (
    <>
      <button onClick={() => join.send()}>Join</button>
      <button onClick={() => move.send([1])}>East</button>
    </>
  );
}

export function Demo({ wallet }) {
  return (
    <InterludeProvider wallet={wallet} scope={["join", "move", "jump", "hit", "leave"]}>
      <Floor />
    </InterludeProvider>
  );
}

What each hook is for

useSession is the grant. open() is openSession with the wallet and scope you put on the provider. useSessionCall("move") is one function on that grant: .send(), the last .data, .error, .isPending, .latencyMs. useRead is the live node read, same arguments as interlude.read.

useSessionCall(...).send puts failures in error instead of rejecting, so a bare onClick cannot produce an unhandled rejection. If you await it yourself, it still throws.