ShainShain/docs
Shain/Docs/Gate a swap
Integration

Gate a swap

Bundle a Shain session with a Jupiter / DEX swap in a single transaction. The canonical integration pattern.

The pattern

Shain sessions are composable: because the program writes state in one instruction and you gate the next one on that state, you can bundle the two in a single transaction. The order is:

  1. (if no session) createAssociatedTokenAccount for $SHAIN — only for cold wallets.
  2. start_session — opens the 24h window.
  3. gated_action(tag) — proof point for your dapp.
  4. Your dapp's private route (DEX swap, perp adjust, etc).

The transaction either lands as a whole or fails as a whole — you never end up paying the session fee without getting the gate.

Full example

examples/gate_swap.tstypescript
import {
  Connection,
  Keypair,
  PublicKey,
  Transaction,
  TransactionInstruction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import { ShainClient, tagFromCallsite } from "@shain/sdk";

const connection = new Connection(process.env.SHAIN_RPC_URL!, "confirmed");
const wallet = Keypair.fromSecretKey(/* load from env */);

const client = new ShainClient({
  connection,
  programId: new PublicKey(process.env.SHAIN_PROGRAM_ID!),
  wallet,
});

const shainMint = new PublicKey(process.env.SHAIN_MINT!);
const userAta = client.ata(wallet.publicKey, shainMint);

const snapshot = await client.snapshotSession();
const tag = tagFromCallsite("examples", "gate_swap");

const ixs: TransactionInstruction[] = [];

// 1. Open a session if not already active
if (!snapshot?.active) {
  ixs.push(
    client.buildStartSessionIx({
      shainMint,
      userTokenAccount: userAta,
    }),
  );
}

// 2. Gate the dapp call
ixs.push(client.buildGatedActionIx({ tag }));

// 3. Append your DEX instructions (Jupiter, Raydium, Orca, etc.)
ixs.push(...await fetchDexSwapIxs(/* swap args */));

const tx = new Transaction().add(...ixs);
const sig = await sendAndConfirmTransaction(connection, tx, [wallet]);

console.log("bundled:", sig);

Transaction size

Solana transactions are capped at 1232 bytes pre-v0. Shain's contribution is small:

  • start_session — 88 B
  • gated_action — 22 B

Both together add ~110 B of instruction payload plus the accounts they reference. You'll rarely hit the limit, but for complex Jupiter routes consider versioned transactions (v0) with address lookup tables.

Retry safety

If the transaction fails mid-bundle (expired blockhash, compute exhausted, etc.), nothing persists — Solana is atomic. On retry:

  • If the retry succeeds while the original was actually included, you'll see SessionAlreadyActive on the second start_session. Catch it and proceed.
  • If the original never landed, the retry succeeds cleanly.

The safest retry strategy is to fetch the session snapshot before retrying and skip start_session if one is already active.