Transaction Syncing & History
The @tuwaio/quasar-sdk package enables your backend (Next.js Server Actions or Node.js API routes) to securely persist transaction states to the cloud (syncCreate) and query cross-device history logs (getHistory) for connected wallet addresses.
🔐 The SIWX Security Model
When calling Quasar Cloud APIs from your server, your QUASAR_SECRET_KEY grants administrative access to the Quasar Engine. However, to prevent unauthorized clients from reading or spoofing another user’s transaction history, every request sent to your backend should include a SIWX Session payload (SiwxSession).
Why SIWX Auth?
- Cryptographic Proof of Ownership: Proves that the connected user actually owns the wallet address (
walletAddress) using the standard CAIP-122 sign-in. - Replay Attack Protection: Every signature embeds an ISO timestamp and a secure nonce. The
verifySiwxPayloadmethod from@tuwaio/sdk/siwx/serverstrictly validates the signature and its expiration. - Stateless Multi-Chain Verification: Supports both EVM (
viem) and Solana (@solana/kit/ SubtleCrypto) verification out of the box with zero database lookups on your server.
☁️ 1. Syncing Transactions (syncCreate)
Use quasar.pulsar.syncCreate in your backend server action or API route to persist a transaction state in the Quasar Engine database.
// src/app/actions/syncTransaction.ts
'use server';
import { cookies } from 'next/headers';
import { Quasar, QuasarSDKError, type Transaction } from '@tuwaio/quasar-sdk';
import { isSessionMatchingTarget } from '@tuwaio/sdk/siwx';
import { getSiwxServerSession } from '@tuwaio/sdk/siwx/server';
import { sessionStore } from '@/lib/authStores';
// Initialize Quasar with your server-only Secret Key
const quasar = new Quasar({
secretKey: process.env.QUASAR_SECRET_KEY ?? '',
});
export async function syncTransactionServerAction(tx: Transaction, appName: string = 'My dApp') {
// Step 1: Read and verify session server-side
const session = await getSiwxServerSession({
cookieSource: await cookies(),
sessionStore,
});
if (!session) {
throw new Error('Unauthorized: No active session found.');
}
try {
// Step 2: Ensure the transaction address matches the verified signer (EVM case-insensitive / Solana case-sensitive)
if (tx.from && !isSessionMatchingTarget(session, tx.from, tx.chainId)) {
throw new Error('Forbidden: Wallet address mismatch.');
}
// Step 3: Persist the transaction to Quasar Cloud
const result = await quasar.pulsar.syncCreate(tx, appName);
return { success: true, txKey: result.txKey };
} catch (error) {
if (error instanceof QuasarSDKError) {
console.error(`[Quasar Engine Error ${error.status}]: ${error.message}`);
throw new Error(`Cloud Sync Failed: ${error.message}`);
}
throw error;
}
}📜 2. Querying Transaction History (getHistory)
Query paginated transaction histories for a verified user across all their devices.
// src/app/actions/getHistory.ts
'use server';
import { cookies } from 'next/headers';
import { Quasar, QuasarSDKError } from '@tuwaio/quasar-sdk';
import { isSessionMatchingTarget } from '@tuwaio/sdk/siwx';
import { getSiwxServerSession } from '@tuwaio/sdk/siwx/server';
import { sessionStore } from '@/lib/authStores';
const quasar = new Quasar({
secretKey: process.env.QUASAR_SECRET_KEY ?? '',
});
export interface GetHistoryParams {
walletAddress: string;
page?: number;
limit?: number;
chainId?: string;
appName?: string;
}
export async function getHistoryServerAction(params: GetHistoryParams) {
const session = await getSiwxServerSession({
cookieSource: await cookies(),
sessionStore,
});
if (!session || !isSessionMatchingTarget(session, params.walletAddress, params.chainId)) {
throw new Error('Unauthorized: Session mismatch or unauthenticated.');
}
try {
// Query paginated history from Quasar Cloud Engine
const history = await quasar.pulsar.getHistory({
walletAddress: params.walletAddress,
page: params.page ?? 1,
limit: params.limit ?? 10,
appName: params.appName ?? 'My dApp',
});
return history;
} catch (error) {
if (error instanceof QuasarSDKError) {
console.error(`[Quasar History Error ${error.status}]: ${error.message}`);
throw new Error(`Failed to fetch history: ${error.message}`);
}
throw error;
}
}🛡️ 3. Production Error Handling (QuasarSDKError)
All calls to quasar.pulsar.* throw typed QuasarSDKError instances when an API request fails (e.g. rate limits, invalid payload, or authentication issues).
import { QuasarSDKError } from '@tuwaio/quasar-sdk';
try {
// @ts-expect-error - Assuming txData is a valid Transaction
await quasar.pulsar.syncCreate(txData, 'My App');
} catch (error) {
if (error instanceof QuasarSDKError) {
// Inspected properties: error.status (HTTP status code), error.message
console.error(`Quasar Error Code: ${error.status}`);
console.error(`Error Message: ${error.message}`);
} else {
console.error('Unexpected System Error:', error);
}
}⚡ 4. Full Client Integration with Pulsar Engine
Now that your server actions are ready, connect them to your client-side Pulsar transaction store. The server action automatically resolves the authenticated user session from HTTP-Only cookies:
// src/hooks/usePulsarStore.ts
'use client';
import { createPulsarStore } from '@tuwaio/sdk/pulsar';
import { preFlightTxCheck } from '@tuwaio/quasar-sdk';
import { syncTransactionServerAction } from '@/app/actions';
import { TransactionUnion } from '@/types';
export const pulsarStore = createPulsarStore<TransactionUnion>({
name: 'app-transaction-storage',
// 1. Ensure a valid session exists before initiating transaction tracking
// and ping Quasar Cloud to ensure the server is alive.
beforeTxProcess: async () => {
await preFlightTxCheck();
},
// 2. Automatically sync transaction states to Quasar Cloud Engine
onRemoteCreate: async (tx) => {
try {
await syncTransactionServerAction(tx as TransactionUnion);
} catch (err) {
console.error('Remote transaction sync failed:', err);
throw err; // Rethrow to inform pulsar-core that sync failed
}
},
});