Predict Sessions SDK
The @mysten/deepbook-v3/sessions subpath exports SessionsContract, which builds the owner-signed calls that grant and revoke a time-limited session key and the session-signed calls that trade DeepBook Predict positions for the account. Sessions explains the contract mechanism behind each call.
A session key carries authority over everything the account holds, as the Sessions contract page states. It cannot withdraw to an address, grant or revoke sessions, or outlive its expiry, but nothing caps notional, restricts which markets or pools it reaches, or bounds loss to adverse pricing. Fund an account that hands out session keys with only what you are willing to put at risk.
SessionsContract builds commands and never signs or executes them. Each call has a single signer:
- Owner key: The account owner signs
authorizeSessionandrevokeSession, because the contract derives owner authority from the transaction sender. - Session key: The session address signs the 4 Predict builders and the 5 DeepBook spot calls, because each wrapper checks the sender's grant on the account.
- Any sender: You simulate
sessionExpirationMsrather than execute it, so any sender address works.
The session lifecycle follows this order:
- The owner grants the session key with
authorizeSession. - The owner lists the account's grants and revokes the expired ones to reclaim their slots.
- The session key trades for the account with the queued-order builders and
redeemSettled. - The owner removes the grant with
revokeSession, which frees its slot.
Configuration
Both networks run Predict protocol version 4 and Sessions protocol version 3. On Mainnet, use version 2.8.0 or later of the SDK, as DeepBook Predict SDK explains.
Build a SessionsContract for each network from the deployed IDs that getSessionsConfig returns:
import { SessionsContract, getSessionsConfig } from '@mysten/deepbook-v3/sessions';
const ids = getSessionsConfig('testnet');
const sessions = new SessionsContract(ids);
// Every builder takes the owner's wrapper ID.
const wrapperId = sessions.deriveAccountWrapperId(OWNER_ADDRESS);
// Every Predict builder also takes the Predict ProtocolConfig object ID.
const protocolConfig = ids.protocolConfig;
OWNER_ADDRESS is the address that owns the account. The same sessions instance serves the owner-signed and the session-signed calls, because the signer is whoever signs the transaction, not a property of the contract instance. The wrapper ID is not the owner's canonical account ID; ID derivation functions explains the 2 derived objects.
getSessionsConfig
Use getSessionsConfig to read the deployed sessions IDs for a network, so you never transcribe them. It returns the frozen deployment record for that network, typed SessionsConfig & SessionsSpotIds & SessionsPredictIds, and it throws an Error for any network other than Testnet or Mainnet. For a deployment of your own, construct a SessionsConfig and pass it to the SessionsContract constructor directly.
It takes this parameter:
network: The network to resolve. Its type isNetworkArg, the Sui client's network type, soclient.networkpasses directly.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.SessionsContract
Use the SessionsContract constructor to bind every builder to a single deployment. It reads only the 4 SessionsConfig IDs, so the wider getSessionsConfig result passes directly, and each Predict builder takes protocolConfig as its own parameter.
It takes this parameter:
config: The deployed sessions and account IDs, usually thegetSessionsConfigresult.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.SessionsConfig
SessionsConfig holds the IDs every builder needs: the latest deepbook_sessions package to call, the original deepbook_sessions package ID that v1 structs and dynamic-field keys still carry, the shared SessionsConfig object, the account package, and the shared AccountRegistry. sessionsPackageIdV1 is optional and matters only where the package has been upgraded. Both networks have upgraded sessions, so the two IDs differ on both. The account IDs are the same ones AccountContract takes on Predict Accounts SDK, because sessions is an account app on the same registry.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.SessionsSpotIds
SessionsSpotIds holds 2 IDs that only DeepBook spot work needs: deepbookRegistry, which the 2 order-placement calls take as deepbook_registry, and deepbookCoreAccountPackageId, which you need to check that registry's authorization. No Predict builder takes either, so they sit outside SessionsConfig.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.SessionsPredictIds
SessionsPredictIds holds the Predict ProtocolConfig object ID, which every Predict builder takes as protocolConfig.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.MAX_SESSION_DURATION_MS
MAX_SESSION_DURATION_MS is the longest grant the contract accepts, 30 days in milliseconds. authorizeSession does not check durationMs against it, so a duration of 0, or one longer than the maximum, reaches the chain and aborts with EInvalidSessionDuration. Check the duration locally, as the authorize example does, to get the error before you sign.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.MAX_SESSIONS_PER_ACCOUNT
MAX_SESSIONS_PER_ACCOUNT is the number of distinct session addresses an account can store, 20. Expired grants keep counting toward it until the owner revokes them, and the SDK does not check it, so authorizing a new address on a full account aborts with ESessionLimitExceeded. The list example reclaims the slots that expired grants hold.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.ID derivation functions
An owner has 2 derived objects in the account registry, and SessionsContract computes both IDs offchain with no chain read. The shared AccountWrapper is the object every builder takes as wrapperId. The canonical account is the identity that session grants attach to, and the one that SessionAuthorized and SessionRevoked report as account_id. Accounts and Custody describes both objects.
The 3 IDs have separate uses, and each function takes the owner address, not another ID:
- Wrapper ID:
deriveAccountWrapperIdreturns the sharedAccountWrapperID, which every builder takes aswrapperId. - Account ID:
deriveAccountIdreturns the canonical account ID, which session grants attach to. - Sessions field ID:
deriveSessionsFieldIdreturns the ID of theDataKey<SessionsApp>dynamic field under the canonical account, which holds every grant and which you fetch to list grants.
deriveAccountWrapperId
Use deriveAccountWrapperId to compute the owner's AccountWrapper ID. It returns a string. It delegates to AccountContract with the account package and registry that the Predict facade also uses, so it returns the same ID as the wrapper derivation on Predict Accounts SDK.
It takes this parameter:
owner: The address that owns the account.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.deriveAccountId
Use deriveAccountId to compute the owner's canonical account ID, a different derived object from the wrapper. It returns a string. Session grants attach to this object, and deriveSessionsFieldId derives from it.
It takes this parameter:
owner: The address that owns the account.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.deriveSessionsFieldId
Use deriveSessionsFieldId to compute the object ID of the owner's DataKey<SessionsApp> dynamic field, which holds every grant. It returns a string. It derives the account ID from owner first, so pass the owner address, not an account or wrapper ID.
It takes this parameter:
owner: The address that owns the account.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.How to authorize a session
The owner grants a session key with a single owner-signed call. The contract computes the expiry from the clock at execution time, so a queued or retried transaction still gets its full duration.
authorizeSession
Use authorizeSession to grant session authority over the account until the execution-time clock plus durationMs. It returns a (tx: Transaction) => void function that adds the authorize_session call, and the owner must sign it. Re-authorizing an address that already holds a grant replaces its expiry in place and uses no additional slot, which is how you extend a running session.
It takes these parameters:
wrapperId: The owner'sAccountWrapperID, fromderiveAccountWrapperId.session: The address to authorize.durationMs: The grant length in milliseconds, greater than 0 and at mostMAX_SESSION_DURATION_MS.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.Grant a session from the owner key
The following example checks the duration locally, then builds an owner-signed grant for a session address:
import {
MAX_SESSION_DURATION_MS,
SessionsContract,
getSessionsConfig,
} from '@mysten/deepbook-v3/sessions';
import { Transaction } from '@mysten/sui/transactions';
import { NETWORK } from './config.js';
// One contract instance drives every sessions call in these examples.
// `getSessionsConfig` returns the deployed sessions package, the shared
// `SessionsConfig` object, the account registry the app is authorized against,
// the Predict protocol config, and the two extra IDs the DeepBook spot wrappers
// need. It resolves both recorded networks; an unrecorded one throws.
export const SESSIONS_CONFIG = getSessionsConfig(NETWORK);
export const sessions = new SessionsContract(SESSIONS_CONFIG);
// The owner's shared `AccountWrapper` ID, derived from the owner address with no
// chain read. Every builder below takes it as `wrapperId`.
export function wrapperId(owner: string): string {
return sessions.deriveAccountWrapperId(owner);
}
// Authorize `session` to trade the owner's account until `now + durationMs`.
//
// A grant is trading authority over the whole account, not a scoped permission.
// The session key cannot withdraw to an address, cannot grant or revoke
// sessions, and cannot outlive its expiry, but within those limits it chooses
// the market, the size, and the price bounds, and the spot wrappers reach the
// account's entire Base, Quote, and DEEP balance. Fund an account that hands out
// ephemeral session keys with only what you are willing to put at risk.
//
// The owner must sign, because the contract derives owner authority from the
// transaction sender. Re-authorizing an address that already holds a grant
// replaces its expiry in place and consumes no additional slot.
export function authorizeSession(params: {
owner: string;
session: string;
durationMs: number;
}): Transaction {
const { owner, session, durationMs } = params;
// The chain asserts the same bounds and aborts `EInvalidSessionDuration`.
// Checking here turns a wasted transaction into a local error.
if (durationMs <= 0 || durationMs > MAX_SESSION_DURATION_MS) {
throw new Error(
`durationMs must be greater than 0 and at most ${MAX_SESSION_DURATION_MS} (30 days), got ${durationMs}.`,
);
}
const tx = new Transaction();
tx.setSender(owner);
tx.add(sessions.authorizeSession({ wrapperId: wrapperId(owner), session, durationMs }));
// Ready to sign with the owner's key. Nothing here signs it.
return tx;
}
// A short-lived grant for one trading run. Expiry is computed from the onchain
// clock at execution time, not from when you build the transaction, so a queued
// or retried transaction still gets its full duration.
export function authorizeForOneHour(owner: string, session: string): Transaction {
return authorizeSession({ owner, session, durationMs: 60 * 60 * 1000 });
}
How to list grants
No onchain call returns every grant at once. sessionExpirationMs answers for a single known address, so listing every grant means fetching the account's DataKey<SessionsApp> field and decoding it offchain. The Sessions page shows the stored SessionsData layout.
Listing takes 4 steps, and the SDK covers each one:
- Derive the canonical account ID from the owner with
deriveAccountId. - Derive the
DataKey<SessionsApp>field ID under that account withderiveSessionsFieldId. - Fetch that object with the core
getObject, and request its Binary Canonical Serialization (BCS) content withinclude: { content: true }. - Decode the bytes with the static
decodeSessions, then split the result withactiveSessionsandexpiredSessions.
deriveSessionsFieldId runs the first 2 steps together, because it derives the account ID from the owner address internally. The field does not exist until the owner's first authorize_session, so the fetch finds no object for an owner who has never granted a session. Treat only that not-found result as an empty list, because a transport failure says nothing about the account's grants.
That path has 3 traps:
- Account, not wrapper: The field hangs off the canonical account, not the wrapper. Deriving the field under the wrapper ID yields an ID that points at nothing.
- Plain dynamic field: The contract writes the slot with
dynamic_field::add, while the registry claims the account and wrapper IDs throughderived_object::claim, soderiveSessionsFieldIdusesderiveDynamicFieldID. Deriving the ID yourself withderiveObjectIDwraps the type tag in aDerivedObjectKeyand produces a different, empty address. - Whole-field decoding: Pass
decodeSessionsthe wholeField<DataKey<SessionsApp>, SessionsData>bytes, not the inner value.DataKeyis empty in source, but Move inserts a hiddendummy_field: bool, so a single zero byte sits between the field ID and the value, and decoding the value alone reads the field ID's first byte as the map length.
Split the result with the 2 helpers rather than by hand. The chain asserts now < expires_at_ms, so activeSessions keeps grants where nowMs < expiresAtMs and expiredSessions keeps grants where nowMs >= expiresAtMs. A filter written as nowMs > expiresAtMs leaves the grant that expires exactly at nowMs in neither list, so your cleanup skips it and the grant keeps its slot.
Expired grants keep their slots until the owner revokes them, so on a busy account, list the grants, revoke what expiredSessions returns, and then grant.
sessionExpirationMs
Use sessionExpirationMs to read the absolute expiry of a single session address. It returns a (tx: Transaction) => TransactionResult function that adds the session_expiration_ms call. Simulate the transaction and decode the returned BCS as Option<u64>: the stored expiry in milliseconds, or none for an address the owner never authorized or has revoked. The call has no version gate, and it does not classify the timestamp, so compare it with the current time yourself.
It takes these parameters:
wrapperId: The owner'sAccountWrapperID.session: The session address to look up.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.SessionGrant
decodeSessions returns a SessionGrant for each stored address. session is the authorized address, and expiresAtMs is its absolute expiry in milliseconds since the Unix epoch. The grant is dead at that timestamp, not after it.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.decodeSessions
Use the static SessionsContract.decodeSessions to decode an account's stored grants from the raw BCS content of its DataKey<SessionsApp> field object. It returns a SessionGrant[]. It re-encodes what it parsed and throws an Error when the length differs from the input, so truncated or padded bytes fail instead of decoding to an empty list, and an empty result means the account holds no grants.
It takes this parameter:
contents: The whole field object's BCS content, asclient.core.getObjectreturns it withinclude: { content: true }.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.activeSessions
Use the static SessionsContract.activeSessions to keep the grants that are still live at nowMs. It returns a SessionGrant[] of the grants where nowMs < expiresAtMs.
It takes these parameters:
grants: The grants fromdecodeSessions.nowMs: The current time in milliseconds since the Unix epoch.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.expiredSessions
Use the static SessionsContract.expiredSessions to keep the grants that are already dead at nowMs, which are the grants to revoke when you reclaim slots. It returns a SessionGrant[] of the grants where nowMs >= expiresAtMs, the exact complement of activeSessions.
It takes these parameters:
grants: The grants fromdecodeSessions.nowMs: The current time in milliseconds since the Unix epoch.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.List and reclaim expired slots
The following example lists an account's grants, splits them at the current time, and builds a single owner-signed transaction that revokes every expired grant:
import type { SessionGrant } from '@mysten/deepbook-v3/sessions';
import { MAX_SESSIONS_PER_ACCOUNT, SessionsContract } from '@mysten/deepbook-v3/sessions';
import { ObjectError } from '@mysten/sui/client';
import { Transaction } from '@mysten/sui/transactions';
import { client } from './client.js';
import { sessions, wrapperId } from './sessions-authorize.js';
// Every grant an account holds.
//
// There is no bulk onchain read: `session_expiration_ms` answers one address at a
// time. Listing means fetching the account's `DataKey<SessionsApp>` dynamic field
// and decoding it locally. Two details make or break this:
//
// 1. The field hangs off the derived ACCOUNT address, not off the wrapper. They
// are different objects. `deriveSessionsFieldId` derives the account first,
// then the field, so pass the owner address and let it do both steps.
// 2. `decodeSessions` takes the whole `Field<DataKey, SessionsData>` bytes that
// `getObject` returns, not the inner value. It throws on truncated bytes
// rather than decoding to an empty list, so an empty result means the account
// really holds no grants.
//
// The field does not exist until the first `authorize_session`, so a missing
// object is an empty list rather than an error. Only a missing object: any other
// failure rethrows, because a transport error says nothing about the grants.
// Once attached the field stays attached, even after every grant is revoked.
export async function listGrants(owner: string): Promise<SessionGrant[]> {
const fieldId = sessions.deriveSessionsFieldId(owner);
let content: Uint8Array | undefined;
try {
const { object } = await client.core.getObject({
objectId: fieldId,
include: { content: true },
});
content = object.content;
} catch (error) {
// No sessions data attached to this account yet.
if (error instanceof ObjectError && error.reason === 'notFound') return [];
throw error;
}
if (!content) return [];
return SessionsContract.decodeSessions(content);
}
// Split a listing at the current time. Use the helpers rather than comparing by
// hand: the chain asserts `now < expiresAtMs`, so a grant is dead AT its expiry,
// and a filter written as `nowMs > expiresAtMs` leaves the grant expiring exactly
// at `nowMs` occupying a slot forever.
export async function grantsByState(
owner: string,
nowMs: number = Date.now(),
): Promise<{ active: SessionGrant[]; expired: SessionGrant[]; slotsFree: number }> {
const grants = await listGrants(owner);
return {
active: SessionsContract.activeSessions(grants, nowMs),
expired: SessionsContract.expiredSessions(grants, nowMs),
// Expired grants still occupy slots, so free slots count against every
// stored address, not just the live ones.
slotsFree: MAX_SESSIONS_PER_ACCOUNT - grants.length,
};
}
// Reclaim the slots expired grants are still holding.
//
// Time passing executes no Move code, so nothing prunes expired entries. They
// count toward the 20 stored addresses until the owner revokes them, and the
// twenty-first `authorize_session` aborts `ESessionLimitExceeded` even when every
// stored grant is long dead. Run this before granting on a busy account.
//
// Returns null when there is nothing to reclaim, so the caller does not sign an
// empty transaction.
export async function reclaimExpiredSlots(
owner: string,
nowMs: number = Date.now(),
): Promise<Transaction | null> {
const { expired } = await grantsByState(owner, nowMs);
if (expired.length === 0) return null;
const tx = new Transaction();
tx.setSender(owner);
for (const grant of expired) {
tx.add(sessions.revokeSession({ wrapperId: wrapperId(owner), session: grant.session }));
}
return tx;
}
How to trade as a session key
Both networks run Predict protocol version 4 and Sessions protocol version 3, so use the queued-order builders. Mainnet needs version 2.8.0 or later, as DeepBook Predict SDK explains. An older release still builds a working revokeSession on Mainnet, because revocation has no version gate. The immediate mint and live-close builders are deprecated, because protocol version 4 always aborts them.
Queue orders as the session key
4 builders place queued orders as the session key: enqueueExactQuantity, enqueueExactAmount, enqueueExactCost, and enqueueRedeemOpen. Each returns a (tx: Transaction) => TransactionResult function whose result is the new queue record ID, a u64, and each needs Sessions protocol version 3. None takes a pricer, because placement reads the oracle objects itself, and every amount is a raw onchain integer, as for the immediate builders.
Each takes a SessionsQueuedOrderTarget that names the market, the owner's wrapper, the order-flow package's orderDesk and queueRegistry, the Predict protocolConfig, the oracleRegistry, and the market's 3 oracle objects. Take the order-flow IDs from client.predict.cfg.objects and the oracle objects from client.predict.cfg.underlyings. queueId defaults to the ID derived from queueRegistry and the market.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.enqueueExactCost sizes the fill at τ so its all-in cost fits maxCost, at least minQuantity. The builder throws for a maxCost of 0 or u64::MAX, which the chain would abort with EMintCostCapRequired, because every queued mint needs a finite cap. enqueueExactQuantity takes quantity, maxCost, and maxProbability, and enqueueExactAmount takes maxPremium, minQuantity, and maxCost.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.enqueueRedeemOpen sells the Open record recordId, its queue record ID rather than the position's order ID. minProbability and minProceeds are required: on a delegated key, 0 sells at any price, so pass it only on purpose.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.A session key's raw builder skips the preflight and plan binding that client.predict.tx.enqueuePlan runs, so plan the order against the owner with read.planMint or read.planSell, check accepting, and send the plan's own strike and raw limits. An order placed through a session still fills or refunds after the session ends, and a revoked or expired key aborts with sessions::ESessionNotAuthorized.
examples/deepbook-predict/src/sessions-queue.ts. You probably need to run `pnpm prebuild` and restart the site.redeemSettled
Use redeemSettled to claim a settled position in full as the session key. It returns a (tx: Transaction) => void function. It takes no pricer, because settlement has already fixed the price, and no quantity, because a settled claim closes the order in full.
It takes these parameters:
expiryMarketId: The expiry market object ID.wrapperId: The owner'sAccountWrapperID.protocolConfig: The PredictProtocolConfigobject ID.orderId: The ID of the settled order to claim.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.Deprecated immediate builders
mintExactQuantity, mintExactAmount, mintExactCost, and redeemLive build the immediate session wrappers, which took a loadLivePricer result and filled inside the session key's transaction. Protocol version 4 always aborts them through Predict, on both networks, and version 2.7.0 deprecates the 4 builders. Use the queued-order builders above instead.
Slippage bounds
The queued session builders make the session key holder set bounds explicitly. enqueueExactCost, enqueueExactQuantity, and enqueueExactAmount refuse a maxCost of 0 or u64::MAX, because every queued mint needs a finite cap, and enqueueRedeemOpen requires minProbability and minProceeds, because 0 sells at any price. A session key's raw builder also skips the preflight and plan binding that client.predict.tx.enqueuePlan runs, so plan the order against the owner with read.planMint or read.planSell, check accepting, and send the plan's own strike and raw limits, as the queued example does.
How to revoke a session
Revocation removes a grant and frees its slot immediately, and the revoked address cannot trade from the next transaction on. It stops future calls only: it does not unwind positions, cancel resting orders, or reverse executed trades.
revokeSession
Use revokeSession to remove the grant for session. It returns a (tx: Transaction) => void function that adds the revoke_session call, and the owner must sign it. The call takes no SessionsConfig object and has no version gate, so it keeps working after a watermark bump retires the package version. A watermark bump advances the package version floor through bumpVersionWatermark.
Revoking an address that holds no grant is a silent no-op: the call neither aborts nor emits SessionRevoked, so a successful transaction does not prove that the call removed a grant. Read sessionExpirationMs before and after when the difference matters. The Sessions page explains why the transaction result cannot confirm a revocation.
It takes these parameters:
wrapperId: The owner'sAccountWrapperID.session: The session address to revoke.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.Revoke and confirm
The following example builds an owner-signed revocation, reads a session's expiry through a simulation, and records whether a grant existed before the revocation:
import { bcs } from '@mysten/sui/bcs';
import { Transaction } from '@mysten/sui/transactions';
import { client } from './client.js';
import { sessions, wrapperId } from './sessions-authorize.js';
// Revoke one grant. The owner signs, the slot frees immediately, and the address
// stops being able to trade from the next transaction on.
//
// Revocation takes no `SessionsConfig` and is not version gated, so it keeps
// working after the sessions package is retired by a version bump. It does not
// unwind anything the session already did: positions, resting spot orders, and
// executed trades all survive.
export function revokeSession(owner: string, session: string): Transaction {
const tx = new Transaction();
tx.setSender(owner);
tx.add(sessions.revokeSession({ wrapperId: wrapperId(owner), session }));
return tx;
}
// Read one address's absolute expiry, in milliseconds since the epoch.
//
// `session_expiration_ms` returns `Option<u64>`: `none` for an address that was
// never granted or has been revoked, and the stored timestamp otherwise. It does
// not classify the timestamp, so compare it with the current time yourself, and
// remember the comparison is strict: the grant is dead AT `expiresAtMs`.
//
// The call is a `public fun` with a return value rather than an `entry` call, so
// simulate it and decode the returned BCS instead of executing it.
export async function sessionExpirationMs(
owner: string,
session: string,
): Promise<bigint | null> {
const tx = new Transaction();
// A simulation needs a sender; any address works for a read.
tx.setSender(owner);
tx.add(sessions.sessionExpirationMs({ wrapperId: wrapperId(owner), session }));
const result = await client.core.simulateTransaction({
transaction: tx,
// Public non-entry functions are only inspectable with checks disabled.
checksEnabled: false,
include: { commandResults: true },
});
if (result.$kind === 'FailedTransaction') {
throw new Error(`session_expiration_ms simulation failed for ${session}.`);
}
const returned = result.commandResults?.[0]?.returnValues?.[0]?.bcs;
if (!returned) throw new Error('simulateTransaction returned no command results.');
const expiry = bcs.option(bcs.u64()).parse(returned);
return expiry === null ? null : BigInt(expiry);
}
// Whether `session` can trade right now. `false` covers both an address that
// never held a grant and one whose grant has expired or been revoked.
export async function isSessionActive(
owner: string,
session: string,
nowMs: number = Date.now(),
): Promise<boolean> {
const expiry = await sessionExpirationMs(owner, session);
return expiry !== null && BigInt(nowMs) < expiry;
}
// Confirm a revocation by reading, because the transaction result cannot.
//
// Revoking an address that holds no grant is a silent no-op: it does not abort
// and it emits no `SessionRevoked` event, so a successful transaction proves
// nothing about whether a grant was removed or was never there. Read before and
// after when the difference matters, for example when you are auditing a key you
// believe you granted.
export async function revokeAndConfirm(
owner: string,
session: string,
): Promise<{ tx: Transaction; hadGrant: boolean }> {
const before = await sessionExpirationMs(owner, session);
return { tx: revokeSession(owner, session), hadGrant: before !== null };
}
DeepBook spot wrappers
SessionsContract does not wrap the 5 DeepBook spot session calls, because the SDK does not model the surrounding workflow of discovering the account's embedded balance manager and reading its resting orders and locked balances. Reach them through the generated sessionsMoveCalls namespace instead:
placeLimitOrder: Places a DeepBook spot limit order for the account.placeMarketOrder: Places a DeepBook spot market order for the account.cancelLiveOrder: Cancels a single DeepBook spot order.cancelLiveOrders: Cancels several DeepBook spot orders.withdrawSettledAmounts: Sweeps settled DeepBook spot proceeds into account custody.
They need Sessions version 3, so on Mainnet use version 2.8.0 of the SDK or later. Whether they succeed also depends on a DeepBook registry authorization, which both networks' registries hold, and the Sessions page covers how to check it.
Each generated function takes an options object with these fields:
arguments: The Move arguments, in an object whose keys are the parameter names.typeArguments: The pool's base and quote coin types.config: ThesessionsPackageIdandsessionsConfigIDs. The function resolves the package address from it and fills in thesessionsConfigargument, so you can omit that argument.package: An explicit package ID, which overridesconfig.sessionsPackageId.
The functions also add the Clock, and the AccumulatorRoot that the order-placement calls take, so you never pass either. The following snippet cancels a spot order as the session key:
import { getSessionsConfig, sessionsMoveCalls } from '@mysten/deepbook-v3/sessions';
import { Transaction } from '@mysten/sui/transactions';
const ids = getSessionsConfig('testnet');
const tx = new Transaction();
tx.setSender(SESSION_ADDRESS);
tx.add(
sessionsMoveCalls.cancelLiveOrder({
config: { sessionsPackageId: ids.sessionsPackageId, sessionsConfig: ids.sessionsConfig },
arguments: {
pool: POOL_ID,
accountRegistry: ids.accountRegistry,
wrapper: WRAPPER_ID,
orderId: ORDER_ID,
},
typeArguments: [BASE_COIN_TYPE, QUOTE_COIN_TYPE],
}),
);
SESSION_ADDRESS is the session key's address, WRAPPER_ID is the owner's AccountWrapper ID from deriveAccountWrapperId, and BASE_COIN_TYPE and QUOTE_COIN_TYPE are the base and quote coin types of the pool that POOL_ID names.
The functions take arguments by name, so argument positions matter only when you build the moveCall yourself. place_limit_order and place_market_order carry deepbook_registry between pool and account_registry, which puts account_registry at index 2 and sessions_config at index 4. The other 3 take no deepbook_registry, so they keep those arguments at indexes 1 and 3, matching the Predict wrappers.
Other exports
Besides SessionsContract and the spot bindings, the subpath exports the session_config bindings, 4 generated Move structs, and a set of deployment helpers.
sessionConfigMoveCalls
The generated sessionConfigMoveCalls namespace covers the session_config module. id and versionWatermark read the shared SessionsConfig object, and bumpVersionWatermark advances the package version floor, which only the SessionsAdminCap holder can call. The namespace also exports that module's BCS structs, and sessionConfigMoveCalls.SessionsConfig is the onchain object's layout, not the SessionsConfig interface. The Sessions page explains the version watermark.
Move structs
The subpath re-exports 4 generated BCS structs from the sessions module. Each one is a MoveStruct, so its parse method decodes the matching bytes:
SessionsApp: The app type the account registry must authorize before any trading call works.SessionsData: The grant map stored under the account, aVecMapfrom session address to expiry.decodeSessionsdecodes the dynamic field that wraps it.SessionAuthorized: The eventauthorize_sessionemits for a new grant or a re-authorization, withaccount_id,session, andexpires_at_ms.SessionRevoked: The eventrevoke_sessionemits when it removes a grant, with the same 3 fields.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.Deployment helpers
The subpath re-exports these deployment helpers, so code that imports only @mysten/deepbook-v3/sessions can name the deployment its IDs came from and format a custody balance:
getDeploymentandgetUnitsTESTNET_DEPLOYMENTandTESTNET_UNITS- The
DeployedNetworkandNetworkArgtypes
DeepBook Predict SDK describes the deployment record they read.
packages/deepbook-v3/src/sessions.ts. You probably need to run `pnpm prebuild` and restart the site.