Skip to content

Signing Sessions

View as Markdown

Use signing sessions when a connected app needs a temporary wallet-owned signer for repeated actions. A session can sign the same ThruTransactionIntent shape as wallet.signTransaction(...), but the session handle signs through the wallet’s session key instead of opening the normal approval UI for every action.

Use a signing session when:

  • the user has already approved a narrow app flow
  • the app needs to submit repeated transactions for the same wallet account
  • the app can keep the session short-lived and revocable

Do not use a signing session as a replacement for the initial wallet connection or for actions that should always show the full wallet approval modal.

createSigningSession(...) opens the wallet, asks the user to approve the session, stores the session descriptor in app-local SDK storage, and returns a ThruSigningSession handle.

const session = await wallet.createSigningSession({
walletAddress,
durationSeconds: 60 * 30,
review: {
appName: "My Thru App",
instruction: "Repeated transfer approval",
},
});
console.log(session.id);
console.log(session.walletAddress);
console.log(session.publicKey);
console.log(session.expiresAt);

The wallet stores the private key. The SDK stores only the descriptor needed by this app to find and use the session later.

Use the returned session handle to sign a wallet-managed transaction intent. The returned string is the signed raw transaction encoded as base64, ready for submission.

function bytesToBase64(bytes: Uint8Array): string {
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToBytes(value: string): Uint8Array {
const binary = atob(value);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
const signedBase64 = await session.signTransaction({
programAddress,
instructionData: bytesToBase64(instructionData),
readWriteAddresses,
readOnlyAddresses,
review: {
appName: "My Thru App",
programAddress,
instruction: "Transfer",
},
});
const signature = await thru.transactions.send(base64ToBytes(signedBase64));

Session-backed signing still uses wallet-managed transaction construction. The app supplies the program instruction intent, and the wallet/session path owns the final transaction wire layout.

Signing sessions are app-scoped. Use getSigningSessions() to find sessions known to the current SDK storage scope, or getSigningSession(id) when you already stored an id in app state.

const sessions = await wallet.getSigningSessions();
const activeSession = sessions.find(
(session) =>
session.walletAddress === walletAddress &&
session.expiresAt > Math.floor(Date.now() / 1000),
);
if (activeSession) {
const signedBase64 = await activeSession.signTransaction({
programAddress,
instructionData: bytesToBase64(instructionData),
readWriteAddresses,
readOnlyAddresses,
});
}

If no session is available, fall back to wallet.signTransaction(intent) or ask the user to create a new session.

Use createSigningSessionInstruction(...) when the app needs to prepare the session authority instruction first, include it in a later user-approved transaction, and confirm the session only after that transaction lands.

const prepared = await wallet.createSigningSessionInstruction({
walletAddress,
walletAccountIdx: 2,
durationSeconds: 60 * 30,
});
console.log(prepared.session.id);
console.log(prepared.programAddress);
console.log(prepared.instructionData);

Include prepared.instructionData in the transaction that adds the session authority. After that transaction is accepted on-chain, confirm the session:

const session = await wallet.confirmSigningSession(prepared.session.id);

confirmSigningSession(id) asks the wallet to verify the session and then stores the descriptor in the SDK’s app-local storage.

Revoke a session when the user disconnects, when the app no longer needs the temporary signer, or when the app detects that the session is expired.

await session.revoke();
// Or revoke by id.
await wallet.revokeSigningSession(session.id);

Revocation removes the local SDK descriptor and asks the wallet to delete its session key.

type ThruTransactionIntent = {
walletAddress?: string;
programAddress: string;
instructionData: string;
readWriteAddresses?: string[];
readOnlyAddresses?: string[];
review?: ThruTransactionReviewPayload;
};
type ThruTransactionReviewPayload = {
appName?: string;
programAddress?: string;
abiName?: string;
instruction?: string;
simulation?: {
before?: string;
after?: string;
};
abiReflection?: {
label?: string;
kind?: string | null;
typeName?: string;
value?: unknown;
rawHex?: string;
source?: string;
error?: string;
};
};
type ThruSigningSessionDescriptor = {
id: string;
walletAddress: string;
publicKey: string;
authIdx: number;
expiresAt: number;
createdAt: number;
};
type ThruSigningSession = ThruSigningSessionDescriptor & {
signTransaction(transaction: ThruTransactionIntent): Promise<string>;
revoke(): Promise<void>;
toJSON(): ThruSigningSessionDescriptor;
};