> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Session Keys — Node.js

> Generate a short-lived session key, register it on-chain, execute transactions with it, and revoke. Lets your server act on a user's wallet for a bounded time without holding the user's PIN.

Session keys are a SNIP-9 primitive: the user signs a delegation that authorises a server-held key to make specific calls (or any calls) on the user's wallet for a limited time. After it expires or is revoked, that key is dead.

Use cases:

* Server-driven trades or payments without prompting the user every time
* Background workers that act on the user's behalf
* Time-boxed automations (e.g. "rebalance once an hour for the next 24h")

This page shows the full lifecycle (create → register → execute → revoke), with code lifted from `staging-integration/staging-sessions.test.ts` and `staging-session-execute.test.ts`.

<Warning>
  Session keys are powerful — anything the session can do, your server can do without the user present. Always set the shortest reasonable `validUntil`, restrict `allowedEntrypoints` when you can, and revoke as soon as you're done.
</Warning>

## Initialise

```ts theme={null}
import { ChipiServerSDK } from "@chipi-stack/backend";

const sdk = new ChipiServerSDK({
  apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
  apiSecretKey: process.env.CHIPI_SECRET_KEY!,
  baseUrl: process.env.CHIPI_BASE_URL, // omit for production
});
```

## 1. Create a session key

Generates a fresh key pair locally and encrypts the private key. Nothing on-chain yet.

```ts theme={null}
const sessionKey = sdk.sessions.createSessionKey({
  encryptKey: userPin,
  durationSeconds: 3600, // 1 hour
});

console.log(sessionKey.publicKey);  // 0x...
console.log(sessionKey.validUntil); // unix timestamp (seconds)

// sessionKey.encryptedPrivateKey is sensitive — keep it in memory or
// in your secrets store, scoped to this session. Never log it.
```

## 2. Register on-chain

The user's wallet signs a transaction that authorises the session key. Chipi's paymaster covers gas. Wait for confirmation before using the key — Starknet blocks settle in \~20-40s.

```ts theme={null}
const txHash = await sdk.sessions.addSessionKeyToContract(
  {
    encryptKey: userPin,
    wallet: userWallet, // { publicKey, encryptedPrivateKey }
    sessionConfig: {
      sessionPublicKey: sessionKey.publicKey,
      validUntil: sessionKey.validUntil,
      maxCalls: 5,            // hard cap on calls during the window
      allowedEntrypoints: [], // empty = any entrypoint; pass selectors to restrict
    },
  },
  process.env.CHIPI_SECRET_KEY!, // session sub-service requires explicit bearer token
);

console.log("Register tx:", txHash);
// Wait for the register tx to land before executing with the session
```

## 3. Execute a transaction with the session key

Once registered, your server can call any allowed entrypoint without the user's PIN. Below: a self-transfer of 0.001 USDC.

```ts theme={null}
import { STARKNET_CONTRACTS } from "@chipi-stack/types";

const usdc = STARKNET_CONTRACTS["USDC"];
const amount = "0x" + (1000).toString(16); // 0.001 USDC, 6 decimals

const txHash = await sdk.executeTransactionWithSession({
  params: {
    encryptKey: userPin,
    wallet: userWallet,
    session: sessionKey,
    calls: [
      {
        contractAddress: usdc.contractAddress,
        entrypoint: "transfer",
        calldata: [recipientAddress, amount, "0x0"],
      },
    ],
  },
});

console.log("Execute tx:", txHash);
```

You can pass multiple `calls` in the same `executeTransactionWithSession` invocation — they execute as a single multicall.

## 4. Revoke

When you're done — or the user logs out — revoke immediately. The on-chain revocation makes the key unusable even if the encrypted private key leaks.

```ts theme={null}
const revokeTxHash = await sdk.sessions.revokeSessionKey(
  {
    encryptKey: userPin,
    wallet: userWallet,
    sessionPublicKey: sessionKey.publicKey,
  },
  process.env.CHIPI_SECRET_KEY!,
);

console.log("Revoke tx:", revokeTxHash);

// Optionally verify
const data = await sdk.sessions.getSessionData({
  walletAddress: userWallet.publicKey,
  sessionPublicKey: sessionKey.publicKey,
});
console.log("isActive:", data.isActive); // false
```

<Note>
  `getSessionData` reads through a non-paymaster RPC, which can lag the paymaster RPC used for register/revoke by a few seconds. If you need a definitive on-chain confirmation, wait for the revoke tx to settle (e.g. via Voyager) before calling `getSessionData`.
</Note>

## Putting it together

```ts theme={null}
import { ChipiServerSDK } from "@chipi-stack/backend";
import { STARKNET_CONTRACTS } from "@chipi-stack/types";

async function timeboxedSendOnUsersBehalf(
  sdk: ChipiServerSDK,
  userPin: string,
  userWallet: { publicKey: string; encryptedPrivateKey: string },
  recipient: string,
  amountUsdc: bigint, // raw units (6 decimals)
) {
  // 1. Create + register a 1-hour session
  const session = sdk.sessions.createSessionKey({
    encryptKey: userPin,
    durationSeconds: 3600,
  });

  const registerTxHash = await sdk.sessions.addSessionKeyToContract(
    {
      encryptKey: userPin,
      wallet: userWallet,
      sessionConfig: {
        sessionPublicKey: session.publicKey,
        validUntil: session.validUntil,
        maxCalls: 1,
        allowedEntrypoints: [],
      },
    },
    process.env.CHIPI_SECRET_KEY!,
  );

  // 2. Wait for the register tx to confirm before executing — otherwise
  //    the session may not yet be active on-chain.
  await waitForTxConfirmed(sdk, registerTxHash);

  // 3. Use the session to send
  const usdc = STARKNET_CONTRACTS["USDC"];
  const txHash = await sdk.executeTransactionWithSession({
    params: {
      encryptKey: userPin,
      wallet: userWallet,
      session,
      calls: [
        {
          contractAddress: usdc.contractAddress,
          entrypoint: "transfer",
          calldata: [recipient, "0x" + amountUsdc.toString(16), "0x0"],
        },
      ],
    },
  });

  // 4. Revoke immediately
  await sdk.sessions.revokeSessionKey(
    {
      encryptKey: userPin,
      wallet: userWallet,
      sessionPublicKey: session.publicKey,
    },
    process.env.CHIPI_SECRET_KEY!,
  );

  return txHash;
}

// Helper: poll Starknet until a tx reaches a terminal status.
// Starknet blocks settle in ~20-40s, so plan for up to ~60s in practice.
async function waitForTxConfirmed(sdk: ChipiServerSDK, txHash: string) {
  const deadline = Date.now() + 90_000; // 90s budget
  while (Date.now() < deadline) {
    const status = await sdk.getTransactionStatus({ txHash });
    if (status.executionStatus === "SUCCEEDED") return;
    if (status.executionStatus === "REVERTED") {
      throw new Error(`tx ${txHash} reverted`);
    }
    await new Promise((r) => setTimeout(r, 3_000));
  }
  throw new Error(`tx ${txHash} did not confirm within 90s`);
}
```

<Note>
  For production, prefer a webhook (configured at `/configure/notifications`) over polling — you can register the session key, return a 202 to your user, and trigger `executeTransactionWithSession` from the webhook handler when the register tx confirms.
</Note>

> ✅ Verified against the live API on **2026-05-11**.
