Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

SDK

grantSession

Grant a scoped session key for a wallet. The admin signer authorizes the session onchain; from that point forward the session can act on the wallet within its permissions, enforced onchain.

import { createClient, BNB, signerFromPrivateKey } from "@altananetwork/sdk";
 
const client = createClient({ chains: [BNB] });
const admin = signerFromPrivateKey("0x...");
const wallet = await client.createWallet({ signer: admin });
 
const session = await client.grantSession({
  wallet,
  signer: admin,
  permissions: {
    calls: [{ to: "0xUniswapRouter..." }],
    spend: [{
      limit: 50n * 10n ** 18n,    // 50 USDT. Note: 18 decimals on BNB Chain.
      period: "day",
      token: "0xUSDT...",
    }],
  },
  expiry: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7 days
});
 
// Hand `session` to whichever process runs the agent.

Registering the session key

The session's public key is written to Keystore via the Controller, batched into the same userOp — controlled by the register flag, on by default. Registration is what makes the key's authority provable to anyone:

  • Third parties can verify it on-chain — other agents, counterparties, dashboards, and compliance tools can check the key's authority, expiry, and revocation state (getKeys / isValidKey, or the MCP verify_authorization tool) without trusting you or Altana. Reads are free and unlimited.
  • Keys are discoverable — a wallet's keys can be enumerated from chain for recovery and auditing.
  • Grants and revokes leave an on-chain trail — timestamped, monotonic revocation.
  • (Ethereum) one registration extends across chains via ensureKeyCached.

To skip registration (and its one-time fee), pass register: false. The session works the same, but Keystore readers like verify_authorization won't see it. Register it later at any time:

await client.registerSessionKey({ wallet, signer: admin, session });

Parameters

client.grantSession(opts: ClientGrantSessionOptions): Promise<GrantSessionResult>;
 
type ClientGrantSessionOptions = {
  wallet: Wallet;
  signer: Signer;                  // the wallet's admin signer
  permissions: SessionPermissions;
  /** Unix epoch seconds. Most apps use Date.now()/1000 + N. */
  expiry: number;
  /** Bring your own session signer, or omit to let the SDK generate one. */
  sessionSigner?: Signer;
  /** Register the key in Keystore (default true). */
  register?: boolean;
  /** Fee token (default: native token). */
  feeToken?: Address;
  /** Target chain. Defaults to the client's default chain. */
  chainId?: number;
};

See Sessions for the full permission shape reference.

Returns

type GrantSessionResult = Session & {
  /** The transaction that carried the grant, when the relay reported one. */
  transactionHash?: Hex;
};
 
type Session = {
  walletAddress: Address;
  signer: Signer;          // session key. Agent signs with this.
  publicKey: Hex;
  permissions: SessionPermissions;
  expiry: number;
};

GrantSessionResult is a Session, so it goes anywhere a Session goes: execute, revokeSession, registerSessionKey, signOrder.

transactionHash is the receipt for the grant. It is optional because the relay can confirm an intent without surfacing one, so check before you use it. It sits on the result rather than on Session deliberately: a session outlives the transaction that created it, and a hash carried around on a session you loaded from disk weeks later describes something that already happened.

What lands onchain

In a single userOp:

  1. The session's public key is registered in Keystore, making it discoverable by any tool.
  2. The session is authorized on the wallet's smart account with its permissions hash.

Both happen atomically. There is no intermediate state where one exists without the other.

Notes

  • Persist the full Session object, not just publicKey. The agent needs permissions + expiry byte-exact at execute time.
  • If you generated the session signer (omitted sessionSigner), persist session.signer securely. Losing it means the session can no longer sign.
  • permissions.calls omitted = unrestricted. Always set both calls and spend unless you specifically want an open-scope session.
  • This is the call that costs the user money. It pays a one-time Keystore registration fee, and on a wallet's very first admin action it pays that fee twice, because initialRegisterKey for the admin is prepended into the same userOp. Record transactionHash if you show users a history of what they were charged for. Pass register: false to skip the registration fee entirely.