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 MCPverify_authorizationtool) 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:
- The session's public key is registered in Keystore, making it discoverable by any tool.
- 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
Sessionobject, not justpublicKey. The agent needspermissions + expirybyte-exact at execute time. - If you generated the session signer (omitted
sessionSigner), persistsession.signersecurely. Losing it means the session can no longer sign. permissions.callsomitted = unrestricted. Always set bothcallsandspendunless 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
initialRegisterKeyfor the admin is prepended into the same userOp. RecordtransactionHashif you show users a history of what they were charged for. Passregister: falseto skip the registration fee entirely.