Skip to main content

Package

import { ... } from "@loyal-labs/private-transactions";

Main Client

LoyalPrivateTransactionsClient

The SDK client wraps both base-layer and PER (Private Ephemeral Rollup) program access. All operations route to the correct layer automatically.

Factory

static fromConfig(config: ClientConfig): Promise<LoyalPrivateTransactionsClient>
Creates a client with dual connections. Handles TEE RPC integrity verification and PER auth token acquisition automatically.
interface ClientConfig {
  signer: WalletSigner;
  baseRpcEndpoint: string;
  baseWsEndpoint?: string;
  ephemeralRpcEndpoint: string;
  ephemeralWsEndpoint?: string;
  commitment?: Commitment; // default: "confirmed"
  authToken?: { token: string; expiresAt: number };
}

Core Methods

Deposit Initialization

initializeDeposit(params): Promise<string>

Create a deposit account for a user and token mint on the base layer. No-op if the account already exists.
interface InitializeDepositParams {
  user: PublicKey;
  tokenMint: PublicKey;
  payer: PublicKey;
  rpcOptions?: RpcOptions;
}

initializeUsernameDeposit(params): Promise<string>

Create a username-based deposit account. Validates username (5–32 chars, alphanumeric + underscore).
interface InitializeUsernameDepositParams {
  username: string;
  tokenMint: PublicKey;
  payer: PublicKey;
  rpcOptions?: RpcOptions;
}

Balance Operations

modifyBalance(params): Promise<ModifyBalanceResult>

Deposit or withdraw real SPL tokens to/from the program vault. Requires the deposit to be not delegated.
  • increase: true — transfers tokens from user’s ATA to vault, increments deposit amount
  • increase: false — transfers tokens from vault to user’s ATA, decrements deposit amount
interface ModifyBalanceParams {
  user: PublicKey;
  tokenMint: PublicKey;
  amount: number | bigint;
  increase: boolean;
  payer: PublicKey;
  userTokenAccount: PublicKey;
  rpcOptions?: RpcOptions;
}

interface ModifyBalanceResult {
  signature: string;
  deposit: DepositData;
}

Claim

claimUsernameDepositToDeposit(params): Promise<string>

Transfer tokens from a username deposit to the recipient’s user deposit. Both accounts must be delegated. Requires a verified Telegram session matching the username.
interface ClaimUsernameDepositToDepositParams {
  username: string;
  tokenMint: PublicKey;
  amount: number | bigint;
  recipient: PublicKey;
  session: PublicKey;
  rpcOptions?: RpcOptions;
}

Permission Management

createPermission(params): Promise<string | null>

Create a PER access control account for a user deposit. Returns null if the permission already exists. Deposit must be not delegated.
interface CreatePermissionParams {
  user: PublicKey;
  tokenMint: PublicKey;
  payer: PublicKey;
  rpcOptions?: RpcOptions;
}

createUsernamePermission(params): Promise<string | null>

Create PER access control for a username deposit. Requires a verified Telegram session.
interface CreateUsernamePermissionParams {
  username: string;
  tokenMint: PublicKey;
  session: PublicKey;
  authority: PublicKey;
  payer: PublicKey;
  rpcOptions?: RpcOptions;
}

Delegation

delegateDeposit(params): Promise<string>

Delegate a user deposit to the TEE validator for PER execution. Account must be not already delegated.
interface DelegateDepositParams {
  user: PublicKey;
  tokenMint: PublicKey;
  payer: PublicKey;
  validator: PublicKey; // typically ER_VALIDATOR
  rpcOptions?: RpcOptions;
}

delegateUsernameDeposit(params): Promise<string>

Delegate a username deposit to PER.
interface DelegateUsernameDepositParams {
  username: string;
  tokenMint: PublicKey;
  payer: PublicKey;
  validator: PublicKey;
  rpcOptions?: RpcOptions;
}

undelegateDeposit(params): Promise<string>

Commit PER state and return deposit ownership to the program on the base layer. Account must be delegated. Waits for ownership to change back to PROGRAM_ID on the base connection.
interface UndelegateDepositParams {
  user: PublicKey;
  tokenMint: PublicKey;
  payer: PublicKey;
  sessionToken?: PublicKey | null;
  magicProgram: PublicKey;  // MAGIC_PROGRAM_ID
  magicContext: PublicKey;  // MAGIC_CONTEXT_ID
  rpcOptions?: RpcOptions;
}

undelegateUsernameDeposit(params): Promise<string>

Commit and return username deposit ownership to the program.
interface UndelegateUsernameDepositParams {
  username: string;
  tokenMint: PublicKey;
  session: PublicKey;
  payer: PublicKey;
  magicProgram: PublicKey;
  magicContext: PublicKey;
  rpcOptions?: RpcOptions;
}

Private Transfers

These execute on the PER layer — both source and destination accounts must be delegated.

transferDeposit(params): Promise<string>

Transfer balance between two user deposits (accounting only, no token movement).
interface TransferDepositParams {
  user: PublicKey;
  tokenMint: PublicKey;
  destinationUser: PublicKey;
  amount: number | bigint;
  payer: PublicKey;
  sessionToken?: PublicKey | null;
  rpcOptions?: RpcOptions;
}

transferToUsernameDeposit(params): Promise<string>

Transfer from a user deposit to a username deposit.
interface TransferToUsernameDepositParams {
  username: string;
  tokenMint: PublicKey;
  amount: number | bigint;
  user: PublicKey;
  payer: PublicKey;
  sessionToken?: PublicKey | null;
  rpcOptions?: RpcOptions;
}

Queries

Query deposit state from either the base layer or the ephemeral layer.

getBaseDeposit(user, tokenMint): Promise<DepositData | null>

Fetch a user deposit from the base layer.

getEphemeralDeposit(user, tokenMint): Promise<DepositData | null>

Fetch a user deposit from PER.

getBaseUsernameDeposit(username, tokenMint): Promise<UsernameDepositData | null>

Fetch a username deposit from the base layer.

getEphemeralUsernameDeposit(username, tokenMint): Promise<UsernameDepositData | null>

Fetch a username deposit from PER.
interface DepositData {
  user: PublicKey;
  tokenMint: PublicKey;
  amount: bigint;
  address: PublicKey; // PDA
}

interface UsernameDepositData {
  username: string;
  tokenMint: PublicKey;
  amount: bigint;
  address: PublicKey; // PDA
}

PDA Helpers (instance)

  • findDepositPda(user, tokenMint): [PublicKey, number]
  • findUsernameDepositPda(username, tokenMint): [PublicKey, number]
  • findVaultPda(tokenMint): [PublicKey, number]

Accessors

  • publicKey: PublicKey — connected wallet’s public key
  • getBaseProgram(): Program — underlying base-layer Anchor program
  • getEphemeralProgram(): Program — underlying PER-layer Anchor program
  • getProgramId(): PublicKey — returns PROGRAM_ID

Exported Types

// Wallet types
type WalletSigner = WalletLike | Keypair | AnchorProvider;
interface WalletLike {
  publicKey: PublicKey;
  signTransaction<T>(tx: T): Promise<T>;
  signAllTransactions<T>(txs: T[]): Promise<T[]>;
}

// RPC options
interface RpcOptions {
  skipPreflight?: boolean;
  preflightCommitment?: Commitment;
  maxRetries?: number;
}

// Delegation status
interface DelegationRecord {
  authority: string;
  owner?: string;
  delegationSlot?: number;
  lamports?: number;
}
interface DelegationStatusResult {
  isDelegated: boolean;
  fqdn?: string;
  delegationRecord: DelegationRecord;
}
interface DelegationStatusResponse {
  jsonrpc: "2.0";
  id: number | string;
  result: DelegationStatusResult;
  error?: { code: number; message: string } | null;
}

Type Guards

  • isKeypair(signer): signer is Keypair
  • isAnchorProvider(signer): signer is AnchorProvider
  • isWalletLike(signer): signer is WalletLike

Exported Constants

ConstantValue
PROGRAM_ID97FzQdWi26mFNR21AbQNg4KqofiCLqQydQfAvRQMcXhV
DELEGATION_PROGRAM_IDDELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh
PERMISSION_PROGRAM_IDACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1
ER_VALIDATORFnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
MAGIC_PROGRAM_IDMagic11111111111111111111111111111111111111
MAGIC_CONTEXT_IDMagicContext1111111111111111111111111111111

PDA Seeds

ConstantString Value
DEPOSIT_SEED"deposit_v2"
USERNAME_DEPOSIT_SEED"username_deposit_v2"
VAULT_SEED"vault"
PERMISSION_SEED"permission:"

Utilities

  • solToLamports(sol: number): number
  • lamportsToSol(lamports: number): number
  • LAMPORTS_PER_SOL

Exported PDA Helpers (standalone)

FunctionSeeds
findDepositPda(user, tokenMint)["deposit_v2", user, tokenMint]
findUsernameDepositPda(username, tokenMint)["username_deposit_v2", username, tokenMint]
findVaultPda(tokenMint)["vault", tokenMint]
findPermissionPda(account)["permission:", account] (PERMISSION_PROGRAM_ID)
findDelegationRecordPda(account)["delegation", account] (DELEGATION_PROGRAM_ID)
findDelegationMetadataPda(account)["delegation-metadata", account] (DELEGATION_PROGRAM_ID)
findBufferPda(account)["buffer", account] (PROGRAM_ID)

IDL Export

For advanced users who need direct Anchor program access:
import { IDL } from "@loyal-labs/private-transactions";
import type { TelegramPrivateTransfer } from "@loyal-labs/private-transactions";

Example

import { Keypair, PublicKey } from "@solana/web3.js";
import {
  ER_VALIDATOR,
  LoyalPrivateTransactionsClient,
  MAGIC_CONTEXT_ID,
  MAGIC_PROGRAM_ID,
} from "@loyal-labs/private-transactions";

const signer = Keypair.fromSecretKey(Uint8Array.from([...secretBytes]));
const tokenMint = new PublicKey("<token-mint>");

const client = await LoyalPrivateTransactionsClient.fromConfig({
  signer,
  baseRpcEndpoint: "https://api.devnet.solana.com",
  // Mainnet: https://mainnet-tee.magicblock.app
  // Devnet: https://tee.magicblock.app
  ephemeralRpcEndpoint: "https://mainnet-tee.magicblock.app",
  ephemeralWsEndpoint: "wss://mainnet-tee.magicblock.app",
});

// Shield
await client.initializeDeposit({ user: signer.publicKey, tokenMint, payer: signer.publicKey });
await client.modifyBalance({ user: signer.publicKey, tokenMint, payer: signer.publicKey, userTokenAccount: new PublicKey("<ata>"), amount: 1_000_000, increase: true });
await client.createPermission({ user: signer.publicKey, tokenMint, payer: signer.publicKey });
await client.delegateDeposit({ user: signer.publicKey, tokenMint, payer: signer.publicKey, validator: ER_VALIDATOR });

// Private transfer
await client.transferToUsernameDeposit({ username: "alice_user", tokenMint, amount: 100_000, user: signer.publicKey, payer: signer.publicKey, sessionToken: null });

// Unshield
await client.undelegateDeposit({ user: signer.publicKey, tokenMint, payer: signer.publicKey, sessionToken: null, magicProgram: MAGIC_PROGRAM_ID, magicContext: MAGIC_CONTEXT_ID });
await client.modifyBalance({ user: signer.publicKey, tokenMint, payer: signer.publicKey, userTokenAccount: new PublicKey("<ata>"), amount: 1_000_000, increase: false });