@tuwaio/quasar-sdk 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -459
- package/dist/chunk-NJH5FYLU.cjs +2 -0
- package/dist/chunk-YPUBFZPR.js +2 -0
- package/dist/cli.cjs +52 -69
- package/dist/index.cjs +1 -2
- package/dist/index.d.cts +113 -202
- package/dist/index.d.ts +113 -202
- package/dist/index.js +1 -2
- package/dist/react.cjs +1 -0
- package/dist/react.d.cts +28 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +1 -0
- package/package.json +24 -57
package/README.md
CHANGED
|
@@ -1,509 +1,102 @@
|
|
|
1
1
|
# @tuwaio/quasar-sdk
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@tuwaio/quasar-sdk)
|
|
4
|
-
[](
|
|
4
|
+
[](https://github.com/TuwaIO/sdk/blob/main/packages/quasar-sdk/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
<img src="https://cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/repos/quasar_sdk.png" alt="Quasar SDK Architecture" width="100%" />
|
|
8
|
-
</p>
|
|
9
|
-
|
|
10
|
-
> The official **Layer 5 (L5)** server-side Node.js & Edge SDK for the **TUWA Quasar Cloud**.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 🏛️ What is `@tuwaio/quasar-sdk`?
|
|
15
|
-
|
|
16
|
-
`@tuwaio/quasar-sdk` is **Layer 5 (L5)** of the TUWA ecosystem architecture — the official backend companion to the TUWA client libraries. It serves as the gateway to the **Quasar Cloud Engine**, allowing your server to securely push transaction logs, query paginated transaction histories, and sync infrastructure states.
|
|
17
|
-
|
|
18
|
-
It operates strictly on the server (Node.js, Next.js Server Actions, or Edge functions) and uses Secret Keys to communicate with Quasar's iron-dome guarded endpoints.
|
|
6
|
+
`@tuwaio/quasar-sdk` is the Layer 5 (L5) package of the **TUWA SDK**: the client of the API of **Quasar**, the TUWA backend that tracks the transactions of your app on the server and keeps their history. It works with Quasar Cloud (`https://api.tuwa.io`) and with a self-hosted Quasar server. It has three parts: the `Quasar` client for your server, a check for the browser (`@tuwaio/quasar-sdk/react`), and the `quasar-sdk` CLI that relays webhooks to `localhost`.
|
|
19
7
|
|
|
20
8
|
---
|
|
21
9
|
|
|
22
|
-
##
|
|
10
|
+
## 🏛️ Core Capabilities
|
|
23
11
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
12
|
+
- **Transaction sync:** `quasar.pulsar.syncCreate` sends a transaction created by Pulsar to Quasar, which tracks it until it reaches a final status (so the status survives a closed tab) and sends your webhooks.
|
|
13
|
+
- **History:** `quasar.pulsar.getHistory` returns the transactions of your app, newest first, filtered by wallet address, chain, status, transaction key or application name.
|
|
14
|
+
- **Errors:** every failed request throws a `QuasarSDKError` with the HTTP `status`.
|
|
15
|
+
- **Pre-flight check:** `preFlightTxCheck` from `@tuwaio/quasar-sdk/react` stops a Pulsar transaction in the browser when the user is not signed in with SIWX or the Quasar API does not respond.
|
|
16
|
+
- **Webhook relay:** `npx @tuwaio/quasar-sdk listen` receives the deliveries of a webhook endpoint with a `localhost` URL and posts them to your local app, without a tunnel.
|
|
29
17
|
|
|
30
18
|
---
|
|
31
19
|
|
|
32
20
|
## 💾 Installation
|
|
33
21
|
|
|
34
22
|
```bash
|
|
35
|
-
pnpm add @tuwaio/quasar-sdk
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
_Note: `ofetch` and `@tuwaio/pulsar-core` are required peer dependencies. The `@tuwaio/siwx-*` packages are required if you intend to use the headless SIWX (CAIP-122) authentication integrations._
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## ⚡ Quasar Webhook Local Dev Relay (Native CLI)
|
|
43
|
-
|
|
44
|
-
Testing Web3 webhooks locally against live cloud indexers usually requires spinning up temporary third-party tunnels (`ngrok`, `cloudflared`), pasting ephemeral URLs into dashboard forms, and restarting your workflow when tunnels expire.
|
|
45
|
-
|
|
46
|
-
The **Quasar SDK CLI** provides a zero-tunnel local development relay. When you configure a `localhost` webhook endpoint in the Quasar Dashboard, Quasar Cloud streams incoming events to your local machine via real-time Server-Sent Events (SSE).
|
|
47
|
-
|
|
48
|
-
### 1. Zero-Config Local Development
|
|
49
|
-
|
|
50
|
-
Add your webhook signing secret from the Quasar Dashboard to `.env.local` or `.env`:
|
|
51
|
-
|
|
52
|
-
```env
|
|
53
|
-
# Signing secret copied from the Quasar Dashboard webhook endpoint
|
|
54
|
-
QUASAR_WEBHOOK_SECRET=whsec_...
|
|
55
|
-
|
|
56
|
-
# Optional Quasar API base URL (defaults to https://api.tuwa.io)
|
|
57
|
-
NEXT_PUBLIC_QUASAR_BASE_URL=https://api.tuwa.io
|
|
23
|
+
pnpm add @tuwaio/quasar-sdk @tuwaio/pulsar-core
|
|
58
24
|
```
|
|
59
25
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
26
|
+
| Import path | Provides | Peer dependencies |
|
|
27
|
+
| -------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
28
|
+
| `@tuwaio/quasar-sdk` | `Quasar`, `PulsarModule`, `QuasarSDKError`, the endpoint constants and types | `@tuwaio/pulsar-core` (>=0.7), for the transaction types |
|
|
29
|
+
| `@tuwaio/quasar-sdk/react` | `preFlightTxCheck` | Also `@tuwaio/siwx-react` (>=0.3, optional for the root) |
|
|
65
30
|
|
|
66
|
-
The
|
|
67
|
-
|
|
68
|
-
### 2. Custom Forwarding & CLI Options
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# Forward to a custom port or path
|
|
72
|
-
npx @tuwaio/quasar-sdk listen --forward-to http://localhost:8080/api/webhooks
|
|
73
|
-
|
|
74
|
-
# Provide secret explicitly via flags
|
|
75
|
-
npx @tuwaio/quasar-sdk listen --secret whsec_... --forward-to http://localhost:3000/api/webhooks/quasar
|
|
76
|
-
|
|
77
|
-
# Specify custom .env file
|
|
78
|
-
npx @tuwaio/quasar-sdk listen --env-file .env.development
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
| Flag | Shorthand | Description | Default |
|
|
82
|
-
| :------------- | :-------- | :-------------------------------------------- | :------------------------------------------ |
|
|
83
|
-
| `--secret` | `-s` | Webhook endpoint signing secret (`whsec_...`) | `QUASAR_WEBHOOK_SECRET` |
|
|
84
|
-
| `--forward-to` | `-f` | Local destination endpoint | `http://localhost:3000/api/webhooks/quasar` |
|
|
85
|
-
| `--api-url` | `-a` | Quasar Cloud API base URL | `https://api.tuwa.io` |
|
|
86
|
-
| `--env-file` | `-e` | Custom path to `.env` file | Auto-detected (`.env.local`, `.env`) |
|
|
87
|
-
| `--help` | `-h` | Display help instructions | — |
|
|
88
|
-
| `--version` | `-v` | Display CLI version | — |
|
|
89
|
-
|
|
90
|
-
### 3. Local Webhook Route Handler (Next.js Example)
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// app/api/webhooks/quasar/route.ts
|
|
94
|
-
import { NextResponse } from 'next/server';
|
|
95
|
-
import crypto from 'node:crypto';
|
|
96
|
-
|
|
97
|
-
export async function POST(req: Request) {
|
|
98
|
-
const secret = process.env.QUASAR_WEBHOOK_SECRET;
|
|
99
|
-
if (!secret) {
|
|
100
|
-
return NextResponse.json({ error: 'Server misconfigured' }, { status: 500 });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const rawBody = await req.text();
|
|
104
|
-
const signature = req.headers.get('x-quasar-signature');
|
|
105
|
-
const event = req.headers.get('x-quasar-event');
|
|
106
|
-
|
|
107
|
-
// Verify HMAC-SHA256 signature
|
|
108
|
-
const expectedSignature = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
|
|
109
|
-
|
|
110
|
-
if (signature !== expectedSignature) {
|
|
111
|
-
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const payload = JSON.parse(rawBody);
|
|
115
|
-
console.log(`[Webhook Received] ${event}:`, payload);
|
|
116
|
-
|
|
117
|
-
return NextResponse.json({ received: true });
|
|
118
|
-
}
|
|
119
|
-
```
|
|
31
|
+
The root entry point imports neither React nor SIWX packages, so it runs in Node.js, Next.js Server Actions and route handlers, and Edge runtimes. Its HTTP client, `ofetch`, is a dependency.
|
|
120
32
|
|
|
121
33
|
---
|
|
122
34
|
|
|
123
|
-
## 🚀
|
|
35
|
+
## 🚀 Usage
|
|
124
36
|
|
|
125
|
-
|
|
37
|
+
Create the client on the server with the secret key of your Quasar app (`sk_live_...` or `sk_test_...`, from the dashboard). Never create it in the browser: the key authorizes writes to your app.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
'use server';
|
|
126
41
|
|
|
127
|
-
```typescript
|
|
128
|
-
import { cookies } from 'next/headers';
|
|
129
42
|
import { Quasar, type Transaction } from '@tuwaio/quasar-sdk';
|
|
130
|
-
import { isSessionMatchingTarget } from '@tuwaio/sdk/siwx';
|
|
131
|
-
import { getSiwxServerSession } from '@tuwaio/sdk/siwx/server';
|
|
132
|
-
import { sessionStore } from '@/lib/authStores';
|
|
133
43
|
|
|
134
|
-
|
|
135
|
-
const quasar = new Quasar({ secretKey: process.env.QUASAR_SDK_SK ?? '' });
|
|
44
|
+
const quasar = new Quasar({ secretKey: process.env.QUASAR_SECRET_KEY ?? '' });
|
|
136
45
|
|
|
137
|
-
/**
|
|
138
|
-
* Example Next.js Server Action to Sync a Transaction
|
|
139
|
-
*/
|
|
140
46
|
export async function syncTransaction(tx: Transaction) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
sessionStore,
|
|
144
|
-
});
|
|
145
|
-
if (!session) throw new Error('Unauthorized: No active session.');
|
|
146
|
-
|
|
147
|
-
// Verify that the transaction sender matches the authenticated session address
|
|
148
|
-
if (tx.from && !isSessionMatchingTarget(session, tx.from, tx.chainId)) {
|
|
149
|
-
throw new Error('Forbidden: Session address mismatch.');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Sync the transaction securely to the Quasar Cloud
|
|
153
|
-
await quasar.pulsar.syncCreate(tx, 'My Application');
|
|
154
|
-
return { success: true };
|
|
47
|
+
const { txKey } = await quasar.pulsar.syncCreate(tx, 'my-app');
|
|
48
|
+
return txKey;
|
|
155
49
|
}
|
|
156
50
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
*/
|
|
160
|
-
export async function getHistory(params: {
|
|
161
|
-
walletAddress: string;
|
|
162
|
-
page?: number;
|
|
163
|
-
limit?: number;
|
|
164
|
-
appName?: string;
|
|
165
|
-
chainId?: string;
|
|
166
|
-
}) {
|
|
167
|
-
const session = await getSiwxServerSession({
|
|
168
|
-
cookieSource: await cookies(),
|
|
169
|
-
sessionStore,
|
|
170
|
-
});
|
|
171
|
-
if (!session || !isSessionMatchingTarget(session, params.walletAddress, params.chainId)) {
|
|
172
|
-
throw new Error('Unauthorized: Session address mismatch.');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Return the paginated transaction history
|
|
176
|
-
return quasar.pulsar.getHistory(params);
|
|
51
|
+
export async function getHistory(walletAddress: string, page = 1) {
|
|
52
|
+
return quasar.pulsar.getHistory({ walletAddress, page, limit: 10, appName: 'my-app' });
|
|
177
53
|
}
|
|
178
54
|
```
|
|
179
55
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## 🔐 Frontend Authentication (SIWX)
|
|
183
|
-
|
|
184
|
-
The Quasar SDK relies on the standard **SIWX (CAIP-122)** protocol for authenticating client requests.
|
|
185
|
-
|
|
186
|
-
You can use the headless `<NovaSiwxWatcher />` component or `useSiwx` / `useSiwxSession` hooks provided by `@tuwaio/sdk/siwx` (and `@tuwaio/sdk/nova-connect`). The auto-auth watcher automatically prompts users to sign a CAIP-122 message when they connect their wallet and synchronizes active session state.
|
|
187
|
-
|
|
188
|
-
For detailed frontend integration, see the [SIWX Documentation](https://siwx.docs.tuwa.io/).
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## 🌍 The Full Flow (Production Architecture)
|
|
193
|
-
|
|
194
|
-
To see how incredibly powerful `@tuwaio/quasar-sdk` is when combined with `@tuwaio/sdk`, here is a complete architectural overview without any skipped steps. Notice how the **Pulsar Store** and **Headless SIWX** dance together effortlessly:
|
|
195
|
-
|
|
196
|
-
### 1. Define Your Transaction Types (`types.ts`)
|
|
197
|
-
|
|
198
|
-
First, define the strict union of all possible transactions your app supports.
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
// src/types.ts
|
|
202
|
-
import type { Transaction } from '@tuwaio/sdk/pulsar';
|
|
203
|
-
|
|
204
|
-
export enum AppTxType {
|
|
205
|
-
SWAP = 'SWAP',
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export type SwapTx = Transaction & {
|
|
209
|
-
type: AppTxType.SWAP;
|
|
210
|
-
payload: { tokenIn: string; tokenOut: string; amount: number };
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
export type TransactionUnion = SwapTx;
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### 2. Secure Server Proxy (`actions.ts`)
|
|
217
|
-
|
|
218
|
-
Proxy calls to Quasar Cloud using your backend to protect secret keys. Never accept client-side session credentials in Server Actions.
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
// src/app/actions.ts
|
|
222
|
-
'use server';
|
|
56
|
+
Before calling Quasar for a user, check on the server that the user owns the wallet (for example with `getSiwxServerSession` and `isSessionMatchingTarget` from `@tuwaio/siwx-server`), so nobody can sync or read the transactions of another address. The complete flow — SIWX sessions, the Pulsar callbacks `onRemoteCreate` and `beforeTxProcess`, the history in Nova Transactions — is the **[Quasar transaction sync guide](https://docs.tuwa.io/guides/quasar-transaction-sync)**; Quasar itself (apps and keys, quotas, webhooks, self-hosting) is described in the **[Quasar docs](https://docs.tuwa.io/quasar)**.
|
|
223
57
|
|
|
224
|
-
|
|
225
|
-
import { Quasar } from '@tuwaio/quasar-sdk';
|
|
226
|
-
import { isSessionMatchingTarget } from '@tuwaio/sdk/siwx';
|
|
227
|
-
import { getSiwxServerSession } from '@tuwaio/sdk/siwx/server';
|
|
228
|
-
import { sessionStore } from '@/lib/authStores';
|
|
229
|
-
import { TransactionUnion } from '@/types';
|
|
230
|
-
|
|
231
|
-
const quasar = new Quasar({ secretKey: process.env.QUASAR_SDK_SK ?? '' });
|
|
232
|
-
|
|
233
|
-
export async function syncTransaction(tx: TransactionUnion) {
|
|
234
|
-
const session = await getSiwxServerSession({
|
|
235
|
-
cookieSource: await cookies(),
|
|
236
|
-
sessionStore,
|
|
237
|
-
});
|
|
238
|
-
if (!session) throw new Error('Unauthorized: No active session.');
|
|
239
|
-
if (tx.from && !isSessionMatchingTarget(session, tx.from, tx.chainId)) {
|
|
240
|
-
throw new Error('Forbidden: Session address mismatch.');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
await quasar.pulsar.syncCreate(tx, 'My App');
|
|
244
|
-
return { success: true };
|
|
245
|
-
}
|
|
58
|
+
### Webhook relay
|
|
246
59
|
|
|
247
|
-
|
|
248
|
-
walletAddress: string;
|
|
249
|
-
page?: number;
|
|
250
|
-
limit?: number;
|
|
251
|
-
appName?: string;
|
|
252
|
-
chainId?: string;
|
|
253
|
-
}) {
|
|
254
|
-
const session = await getSiwxServerSession({
|
|
255
|
-
cookieSource: await cookies(),
|
|
256
|
-
sessionStore,
|
|
257
|
-
});
|
|
258
|
-
if (!session || !isSessionMatchingTarget(session, params.walletAddress, params.chainId)) {
|
|
259
|
-
throw new Error('Unauthorized: Session address mismatch.');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return quasar.pulsar.getHistory(params);
|
|
263
|
-
}
|
|
264
|
-
```
|
|
60
|
+
Add a webhook endpoint with a `localhost` URL in the Quasar dashboard, put its signing secret in `.env.local` and start the relay next to your dev server:
|
|
265
61
|
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
import { createPulsarStore, createTxInMemoryStore, createBoundedUseStore } from '@tuwaio/sdk/pulsar';
|
|
271
|
-
import { pulsarEvmAdapter } from '@tuwaio/evm-sdk/pulsar';
|
|
272
|
-
import { pulsarSolanaAdapter } from '@tuwaio/solana-sdk/pulsar';
|
|
273
|
-
import { preFlightTxCheck } from '@tuwaio/quasar-sdk';
|
|
274
|
-
|
|
275
|
-
import { getHistory, syncTransaction } from '@/app/actions';
|
|
276
|
-
import { wagmiConfig, appEVMChains, solanaRPCUrls } from '@/configs/appConfig';
|
|
277
|
-
import { TransactionUnion } from '@/types';
|
|
278
|
-
|
|
279
|
-
const storageName = 'transactions-tracking-storage';
|
|
280
|
-
|
|
281
|
-
const initialStore = createPulsarStore<TransactionUnion>({
|
|
282
|
-
name: storageName,
|
|
283
|
-
adapter: [pulsarEvmAdapter(wagmiConfig, appEVMChains), pulsarSolanaAdapter({ rpcUrls: solanaRPCUrls })],
|
|
284
|
-
beforeTxProcess: async () => {
|
|
285
|
-
// Ensures we have a valid SIWX session and Quasar Cloud is reachable before executing blockchain logic
|
|
286
|
-
await preFlightTxCheck();
|
|
287
|
-
},
|
|
288
|
-
onRemoteCreate: async (tx) => {
|
|
289
|
-
try {
|
|
290
|
-
// Syncs the new transaction to Quasar via Next.js Server Actions
|
|
291
|
-
await syncTransaction(tx as TransactionUnion);
|
|
292
|
-
} catch (err) {
|
|
293
|
-
console.error('[PulsarHook] Remote sync failed:', err);
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
export const usePulsarStore = createBoundedUseStore(initialStore);
|
|
299
|
-
|
|
300
|
-
// Wrap with inMemoryStore to enable remote history fetching & pagination
|
|
301
|
-
const pulsarInMemoryStore = createTxInMemoryStore<TransactionUnion>({
|
|
302
|
-
localTransactionsPool: initialStore.getState().transactionsPool,
|
|
303
|
-
getHistory: async ({ page, walletAddress }) => {
|
|
304
|
-
try {
|
|
305
|
-
const history = await getHistory({ walletAddress, page, limit: 10, appName: 'My App' });
|
|
306
|
-
|
|
307
|
-
if (!history) return null;
|
|
308
|
-
|
|
309
|
-
return { ...history, docs: history.docs as TransactionUnion[] };
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error('[PulsarHook] Failed to fetch history:', error);
|
|
312
|
-
throw error;
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
onHistoryFetched: async (remoteTxs) => {
|
|
316
|
-
await initialStore.getState().injectExternalPendingTxs(remoteTxs);
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
initialStore.subscribe((state) => pulsarInMemoryStore.getState().syncWithLocalPool(state.transactionsPool));
|
|
321
|
-
|
|
322
|
-
export const usePulsarInMemoryStore = createBoundedUseStore(pulsarInMemoryStore);
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### 4. Nova Transactions Provider (`NovaTransactionsProvider.tsx`)
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
// src/providers/NovaTransactionsProvider.tsx
|
|
329
|
-
'use client';
|
|
330
|
-
|
|
331
|
-
import { useSatelliteConnectStore } from '@tuwaio/sdk/satellite';
|
|
332
|
-
import { useInitializeTransactionsPool, type TxInMemoryPagination } from '@tuwaio/sdk/pulsar';
|
|
333
|
-
import { getAdapterFromConnectorType } from '@tuwaio/sdk/orbit';
|
|
334
|
-
import { NovaTransactionsProvider as NTP } from '@tuwaio/sdk/nova-transactions/providers';
|
|
335
|
-
import { usePulsarInMemoryStore, usePulsarStore } from '@/hooks/usePulsarStore';
|
|
336
|
-
|
|
337
|
-
export function NovaTransactionsProvider({ pagination }: { pagination: TxInMemoryPagination }) {
|
|
338
|
-
const initialTx = usePulsarStore((state) => state.initialTx);
|
|
339
|
-
const closeTxTrackedModal = usePulsarStore((state) => state.closeTxTrackedModal);
|
|
340
|
-
const executeTxAction = usePulsarStore((state) => state.executeTxAction);
|
|
341
|
-
const initializeTransactionsPool = usePulsarStore((state) => state.initializeTransactionsPool);
|
|
342
|
-
|
|
343
|
-
const activeConnection = useSatelliteConnectStore((state) => state.activeConnection);
|
|
344
|
-
const getAdapter = usePulsarStore((state) => state.getAdapter);
|
|
345
|
-
const transactionsPool = usePulsarInMemoryStore((state) => state.transactionsPool);
|
|
346
|
-
|
|
347
|
-
useInitializeTransactionsPool({ initializeTransactionsPool });
|
|
348
|
-
|
|
349
|
-
return (
|
|
350
|
-
<NTP
|
|
351
|
-
transactionsPool={transactionsPool}
|
|
352
|
-
initialTx={initialTx}
|
|
353
|
-
closeTxTrackedModal={closeTxTrackedModal}
|
|
354
|
-
executeTxAction={executeTxAction}
|
|
355
|
-
connectedWalletAddress={activeConnection?.isConnected ? activeConnection.address : undefined}
|
|
356
|
-
connectedAdapterType={getAdapterFromConnectorType(activeConnection?.connectorType ?? 'evm:')}
|
|
357
|
-
adapter={getAdapter()}
|
|
358
|
-
pagination={pagination}
|
|
359
|
-
/>
|
|
360
|
-
);
|
|
361
|
-
}
|
|
62
|
+
```bash
|
|
63
|
+
# .env.local: QUASAR_WEBHOOK_SECRET=whsec_...
|
|
64
|
+
npx @tuwaio/quasar-sdk listen --forward-to http://localhost:3000/api/webhooks/quasar
|
|
362
65
|
```
|
|
363
66
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
import { satelliteEVMAdapter } from '@tuwaio/evm-sdk/satellite';
|
|
373
|
-
import { EVMConnectorsWatcher } from '@tuwaio/evm-sdk/nova-connect';
|
|
374
|
-
import { satelliteSolanaAdapter } from '@tuwaio/solana-sdk/satellite';
|
|
375
|
-
import { SolanaConnectorsWatcher } from '@tuwaio/solana-sdk/nova-connect';
|
|
376
|
-
import { useSiwx, useSiwxSession } from '@tuwaio/sdk/siwx';
|
|
377
|
-
import { isSafeApp, getAdapterFromConnectorType, OrbitAdapter } from '@tuwaio/sdk/orbit';
|
|
378
|
-
|
|
379
|
-
import { appEVMChains, solanaRPCUrls, wagmiConfig } from '@/configs/appConfig';
|
|
380
|
-
import { usePulsarInMemoryStore, usePulsarStore } from '@/hooks/usePulsarStore';
|
|
381
|
-
import { NovaTransactionsProvider } from '@/providers/NovaTransactionsProvider';
|
|
382
|
-
|
|
383
|
-
export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
384
|
-
const getAdapter = usePulsarStore((state) => state.getAdapter);
|
|
385
|
-
const transactionsPool = usePulsarInMemoryStore((state) => state.transactionsPool);
|
|
386
|
-
|
|
387
|
-
const isLoading = usePulsarInMemoryStore((state) => state.isLoading);
|
|
388
|
-
const isError = usePulsarInMemoryStore((state) => state.isError);
|
|
389
|
-
const currentPage = usePulsarInMemoryStore((state) => state.currentPage);
|
|
390
|
-
const hasMore = usePulsarInMemoryStore((state) => state.hasMore);
|
|
391
|
-
const fetchNextPage = usePulsarInMemoryStore((state) => state.fetchNextPage);
|
|
392
|
-
const fetchInitial = usePulsarInMemoryStore((state) => state.fetchInitial);
|
|
393
|
-
|
|
394
|
-
const pagination = { isLoading, isError, currentPage, hasMore, fetchNextPage };
|
|
395
|
-
|
|
396
|
-
// Watch SIWX session to keep connection state aligned
|
|
397
|
-
const siwxSession = useSiwxSession();
|
|
398
|
-
const { signIn } = useSiwx();
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<SatelliteConnectProvider
|
|
402
|
-
adapter={[satelliteEVMAdapter(wagmiConfig, appEVMChains), satelliteSolanaAdapter({ rpcUrls: solanaRPCUrls })]}
|
|
403
|
-
autoConnect={true}
|
|
404
|
-
callbackAfterConnected={async (connection) => {
|
|
405
|
-
const isEVM = getAdapterFromConnectorType(connection.connectorType) === OrbitAdapter.EVM;
|
|
406
|
-
if (isEVM && isSafeApp) return;
|
|
407
|
-
|
|
408
|
-
// Trigger SIWX flow
|
|
409
|
-
await signIn();
|
|
410
|
-
|
|
411
|
-
// Fetch history slightly after connection and sign-in
|
|
412
|
-
setTimeout(() => fetchInitial(connection.address), 2000);
|
|
413
|
-
}}
|
|
414
|
-
>
|
|
415
|
-
<EVMConnectorsWatcher wagmiConfig={wagmiConfig} siwx={siwxSession} />
|
|
416
|
-
<SolanaConnectorsWatcher siwx={siwxSession} />
|
|
417
|
-
|
|
418
|
-
<NovaTransactionsProvider pagination={pagination} />
|
|
419
|
-
|
|
420
|
-
<NovaConnectProvider
|
|
421
|
-
appChains={appEVMChains}
|
|
422
|
-
solanaRPCUrls={solanaRPCUrls}
|
|
423
|
-
transactionPool={transactionsPool}
|
|
424
|
-
pulsarAdapter={getAdapter()}
|
|
425
|
-
withImpersonated
|
|
426
|
-
withBalance
|
|
427
|
-
withChain
|
|
428
|
-
pagination={pagination}
|
|
429
|
-
>
|
|
430
|
-
{children}
|
|
431
|
-
</NovaConnectProvider>
|
|
432
|
-
</SatelliteConnectProvider>
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
```
|
|
67
|
+
| Flag | Short | Default |
|
|
68
|
+
| -------------- | ----- | --------------------------------------------------------------------------------- |
|
|
69
|
+
| `--secret` | `-s` | `QUASAR_WEBHOOK_SECRET` from the environment or the `.env` file |
|
|
70
|
+
| `--forward-to` | `-f` | `QUASAR_WEBHOOK_FORWARD_TO`, else `http://localhost:3000/api/webhooks/quasar` |
|
|
71
|
+
| `--api-url` | `-a` | `NEXT_PUBLIC_QUASAR_BASE_URL` or `QUASAR_BASE_URL`, else `https://api.tuwa.io` |
|
|
72
|
+
| `--env-file` | `-e` | The first of `.env.local`, `.env.development` and `.env` in the working directory |
|
|
73
|
+
| `--help` | `-h` | |
|
|
74
|
+
| `--version` | `-v` | |
|
|
436
75
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
Now you can safely execute strictly-typed, cross-chain transactions anywhere in your app. The store automatically routes the transaction to the correct adapter, and Quasar syncs it to the cloud.
|
|
440
|
-
|
|
441
|
-
```tsx
|
|
442
|
-
// src/components/SwapButton.tsx
|
|
443
|
-
'use client';
|
|
444
|
-
|
|
445
|
-
import { getAdapterFromConnectorType, OrbitAdapter } from '@tuwaio/sdk/orbit';
|
|
446
|
-
import { useSatelliteConnectStore } from '@tuwaio/sdk/satellite';
|
|
447
|
-
import { TxActionButton } from '@tuwaio/sdk/nova-transactions';
|
|
448
|
-
import { usePulsarStore, usePulsarInMemoryStore } from '@/hooks/usePulsarStore';
|
|
449
|
-
import { AppTxType } from '@/types';
|
|
450
|
-
|
|
451
|
-
export function SwapButton() {
|
|
452
|
-
const executeTxAction = usePulsarStore((s) => s.executeTxAction);
|
|
453
|
-
const getLastTxKey = usePulsarStore((s) => s.getLastTxKey);
|
|
454
|
-
const transactionsPool = usePulsarInMemoryStore((s) => s.transactionsPool);
|
|
455
|
-
const activeConnection = useSatelliteConnectStore((s) => s.activeConnection);
|
|
456
|
-
|
|
457
|
-
const handleSwapAction = async () => {
|
|
458
|
-
// Dynamically determine the adapter based on the currently connected wallet
|
|
459
|
-
const adapterType = getAdapterFromConnectorType(activeConnection?.connectorType ?? 'evm:');
|
|
460
|
-
|
|
461
|
-
await executeTxAction({
|
|
462
|
-
actionFunction: async () => {
|
|
463
|
-
/* your wagmi/solana contract call */
|
|
464
|
-
},
|
|
465
|
-
params: {
|
|
466
|
-
adapter: adapterType,
|
|
467
|
-
type: AppTxType.SWAP,
|
|
468
|
-
title: 'Token Swap',
|
|
469
|
-
desiredChainID: adapterType === OrbitAdapter.EVM ? 1 : undefined,
|
|
470
|
-
payload: { tokenIn: 'USDC', tokenOut: adapterType === OrbitAdapter.EVM ? 'ETH' : 'SOL', amount: 100 },
|
|
471
|
-
},
|
|
472
|
-
});
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
return (
|
|
476
|
-
<TxActionButton
|
|
477
|
-
action={handleSwapAction}
|
|
478
|
-
getLastTxKey={getLastTxKey}
|
|
479
|
-
transactionsPool={transactionsPool}
|
|
480
|
-
walletAddress={activeConnection?.address}
|
|
481
|
-
>
|
|
482
|
-
Cross-Chain Swap
|
|
483
|
-
</TxActionButton>
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
```
|
|
76
|
+
The relay posts each delivery with the `x-quasar-signature`, `x-quasar-event` and `x-quasar-delivery-id` headers and the same body as a direct delivery, so your endpoint verifies the signature the same way. It reconnects with a backoff of up to 15 seconds and stops on a `401` (wrong secret) or `404` (no endpoint with that secret).
|
|
487
77
|
|
|
488
78
|
---
|
|
489
79
|
|
|
490
|
-
##
|
|
80
|
+
## 🗄️ Browser Storage
|
|
491
81
|
|
|
492
|
-
|
|
82
|
+
Nothing is written. `preFlightTxCheck` reads the SIWX session from the store of `@tuwaio/siwx-react`, which keeps it in `localStorage` under `siwx-react:session`.
|
|
493
83
|
|
|
494
|
-
|
|
495
|
-
- `preFlightTxCheck` — A client-side helper to ensure the user has a valid SIWX session and Quasar Cloud is reachable before prompting wallet signatures.
|
|
84
|
+
## 🌐 External Services
|
|
496
85
|
|
|
497
|
-
|
|
86
|
+
| Part | Host | Request |
|
|
87
|
+
| ------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `Quasar` client | `baseUrl` (default `https://api.tuwa.io`) | `POST /v1/engine/pulsar/sync` and `GET /v1/engine/pulsar/history`, with the secret key in the `x-tuwa-secret-key` header |
|
|
89
|
+
| `preFlightTxCheck` | Its `customApiUrl` (default `https://api.tuwa.io`) | `GET /v1/engine/monitoring/health`, without credentials |
|
|
90
|
+
| `quasar-sdk listen` | `--api-url` (default `https://api.tuwa.io`), then `--forward-to` | A Server-Sent Events stream from `/v1/engine/webhooks/listen` with the signing secret in the `x-webhook-secret` header; a `POST` to the local URL for each delivery |
|
|
498
91
|
|
|
499
|
-
|
|
92
|
+
The transactions you sync, with their `payload`, are stored by Quasar.
|
|
500
93
|
|
|
501
|
-
|
|
94
|
+
---
|
|
502
95
|
|
|
503
|
-
|
|
96
|
+
## 📚 API Reference
|
|
504
97
|
|
|
505
|
-
|
|
98
|
+
Every export, with signatures and types generated from the source, is documented at **[sdk.docs.tuwa.io/packages/quasar-sdk](https://sdk.docs.tuwa.io/packages/quasar-sdk)**. The HTTP API is described in the **[Quasar API reference](https://docs.tuwa.io/quasar/api)**.
|
|
506
99
|
|
|
507
100
|
## 📄 License
|
|
508
101
|
|
|
509
|
-
|
|
102
|
+
Licensed under the **Apache-2.0 License**. See the [LICENSE](https://github.com/TuwaIO/sdk/blob/main/packages/quasar-sdk/LICENSE) file for details.
|