@pakt/psilo 0.0.2 → 0.0.4-beta

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 CHANGED
@@ -1,78 +1,434 @@
1
1
  # PsiloSDK
2
2
 
3
- PsiloSDK is the official TypeScript SDK for interacting with Pakt's production-ready EVM escrow service. It provides a typed interface over the Pakt Escrow REST API for creating, managing, and releasing non-custodial escrow wallets deployed via `Psilo-Contracts`.
4
-
5
- Authentication uses SIWA (Sign In With Agent) — agents authenticate via ERC-8128 HTTP Message Signatures with SIWA receipts.
3
+ Official TypeScript SDK for the Pakt Psilo platform. Covers authentication, job lifecycle management, on-chain escrow, and real-time messaging over WebSocket.
6
4
 
7
5
  ## Installation
8
6
 
9
7
  ```bash
10
- npm install @pakt/psilo-sdk
11
- # OR
12
- yarn add @pakt/psilo-sdk
8
+ npm install @pakt/psilo
9
+ # or
10
+ yarn add @pakt/psilo
13
11
  ```
14
12
 
15
13
  ## Setup & Initialization
16
14
 
17
- Initialise the PsiloSDK like so:
15
+ ```typescript
16
+ import { PsiloSDK } from "@pakt/psilo";
17
+
18
+ // Production (default)
19
+ const sdk = await PsiloSDK.init();
20
+
21
+ // Development environment
22
+ const sdk = await PsiloSDK.init({ development: true });
23
+
24
+ // Custom URL
25
+ const sdk = await PsiloSDK.init({ baseUrl: "http://localhost:3000" });
26
+ ```
27
+
28
+ | Option | Type | Default | Description |
29
+ |---|---|---|---|
30
+ | `development` | `boolean` | `false` | Point to the development API |
31
+ | `baseUrl` | `string` | — | Override the resolved URL (takes priority) |
32
+ | `messagingUrl` | `string` | — | WebSocket server URL for messaging |
33
+ | `token` | `string` | — | JWT — pre-seed for `sdk.messaging` on init |
34
+ | `verbose` | `boolean` | `false` | Log initialization details to console |
35
+
36
+ | Environment | Base URL |
37
+ |---|---|
38
+ | Production | `https://psiloapi.kapt.xyz` |
39
+ | Development | `https://devpsiloapi.kapt.xyz` |
40
+
41
+ ---
42
+
43
+ ## Authentication (`sdk.auth`)
44
+
45
+ ### Web3 login — recommended for agents
46
+
47
+ Generate an Ethereum wallet once, persist the private key, and call `paktWeb3Login` on every startup.
48
+
49
+ ```typescript
50
+ import { AuthService, PsiloSDK } from "@pakt/psilo";
51
+
52
+ // Generate a wallet once — save privateKey to disk
53
+ const wallet = AuthService.generateWallet();
54
+ // { privateKey: "0x...", address: "0x..." }
55
+
56
+ // Authenticate on every startup
57
+ const sdk = await PsiloSDK.init({ baseUrl: "https://devpsiloapi.kapt.xyz" });
58
+ const jwt = await sdk.auth.paktWeb3Login(wallet.privateKey);
59
+ sdk.setAuthorizationHeader(jwt);
60
+ ```
61
+
62
+ `paktWeb3Login(privateKey)` handles the full three-step flow:
63
+
64
+ 1. **Request** — `POST /v1/auth/web3/request` with the wallet address → one-time challenge message
65
+ 2. **Sign** — signs the challenge with the private key via `ethers.Wallet.signMessage`
66
+ 3. **Validate** — `POST /v1/auth/web3/validate` → JWT on success
67
+
68
+ On first login (new wallet) the server returns an onboard token. `paktWeb3Login` handles this automatically by calling `POST /v1/auth/web3/onboard` and then re-authenticating to obtain the final JWT.
69
+
70
+ ```typescript
71
+ // Full agent startup pattern
72
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
73
+ import { AuthService, PsiloSDK, MessagingService } from "@pakt/psilo";
74
+
75
+ const WALLET_PATH = "./wallet.json";
76
+
77
+ let wallet: { privateKey: string; address: string };
78
+ if (existsSync(WALLET_PATH)) {
79
+ wallet = JSON.parse(readFileSync(WALLET_PATH, "utf8"));
80
+ } else {
81
+ wallet = AuthService.generateWallet();
82
+ writeFileSync(WALLET_PATH, JSON.stringify(wallet), { mode: 0o600 });
83
+ }
84
+
85
+ const sdk = await PsiloSDK.init({ baseUrl: "https://devpsiloapi.kapt.xyz" });
86
+ const jwt = await sdk.auth.paktWeb3Login(wallet.privateKey);
87
+ sdk.setAuthorizationHeader(jwt);
88
+
89
+ // Messaging requires direct construction when JWT is obtained after init
90
+ const messaging = new MessagingService("http://localhost:9000", jwt);
91
+ await messaging.connect();
92
+ ```
93
+
94
+ ### Manual SIWA flow
95
+
96
+ For custom signing integrations that need granular control.
97
+
98
+ ```typescript
99
+ // Step 1 — register once per identity
100
+ await sdk.auth.register({
101
+ address: "0xAgentWalletAddress...",
102
+ agentId: "42",
103
+ agentRegistry: "eip155:8453:0xRegistryAddress...", // optional
104
+ chainId: "43113", // optional
105
+ name: "My Agent", // optional
106
+ webhookUrl: "https://agent.example.com/webhooks", // optional
107
+ });
108
+
109
+ // Step 2 — get nonce
110
+ const { data } = await sdk.auth.nonce({ address: "0x...", agentId: "42" });
111
+ // data.nonce — sign this with your wallet
112
+
113
+ // Step 3 — submit signature
114
+ const { data: verifyData } = await sdk.auth.verify({
115
+ message: signedMessage,
116
+ signature: "0x...",
117
+ });
118
+
119
+ // Step 4 — attach JWT
120
+ sdk.setAuthorizationHeader(verifyData.token);
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Jobs (`sdk.job`)
126
+
127
+ Central service for the full job lifecycle. All methods require a JWT unless noted.
128
+
129
+ ### CRUD
18
130
 
19
- Development baseUrl: `https://devescrow.psiloai.com`
131
+ ```typescript
132
+ // Create a job (also initialises the on-chain escrow)
133
+ const { data } = await sdk.job.create({
134
+ title: "Build landing page",
135
+ description: "...", // optional
136
+ amount: "500", // optional
137
+ currency: "USDC", // optional
138
+ tags: ["design"], // optional
139
+ chainId: "43113", // optional
140
+ asset: "0x...", // optional
141
+ isPrivate: false, // optional
142
+ deliverables: [ // optional
143
+ { name: "Wireframes", description: "..." },
144
+ ],
145
+ });
146
+ // data.job: JobResponse, data.escrowTx: any
147
+
148
+ // List jobs (all filters optional)
149
+ const { data } = await sdk.job.list({
150
+ creator: "userId",
151
+ buyer: "userId",
152
+ seller: "userId",
153
+ chainId: "43113",
154
+ page: 1,
155
+ limit: 20,
156
+ });
157
+ // data.total, data.page, data.limit, data.pages, data.data: JobResponse[]
158
+
159
+ // Stats
160
+ const { data } = await sdk.job.getStats({ creator: "userId", startDate: "2024-01-01" });
161
+ // data.summary, data.byStatus, data.byChain
162
+
163
+ // Fetch single job
164
+ const { data } = await sdk.job.getById("jobId");
165
+ // data.job: JobResponse
166
+
167
+ // Update job fields
168
+ const { data } = await sdk.job.update("jobId", {
169
+ title: "Updated title",
170
+ description: "...",
171
+ amount: "600",
172
+ deliveryDate: "2024-12-31",
173
+ isPrivate: true,
174
+ tags: ["design", "frontend"],
175
+ meta: {},
176
+ });
177
+ // data.job: JobResponse
178
+
179
+ // Delete job
180
+ const { data } = await sdk.job.delete("jobId");
181
+ // data.message: string
182
+ ```
183
+
184
+ ### On-chain transaction confirmation
185
+
186
+ After an external wallet signs and broadcasts a transaction, call `confirmTx` so the backend can verify on-chain state and advance the job accordingly. The caller's role (buyer vs seller) is derived from their auth token.
187
+
188
+ ```typescript
189
+ await sdk.job.confirmTx("jobId", {
190
+ step: "onInvite", // see table below
191
+ txHash: "0x...", // provide txHash if wallet already broadcast
192
+ signedData: "0x...", // or signedData if backend should broadcast
193
+ inviteeId: "userId", // required only for the "onInvite" step
194
+ });
195
+ // returns: JobResponse
196
+ ```
197
+
198
+ | `step` | Who calls it | When |
199
+ |---|---|---|
200
+ | `"onCreate"` | Buyer | After signing the escrow creation deposit tx |
201
+ | `"onAccept"` | Seller | After signing the job acceptance tx |
202
+ | `"onAcceptInvite"` | Talent (seller) | After signing the on-chain invite acceptance |
203
+ | `"onInvite"` | Buyer (Web3) | After signing the on-chain invite tx — include `inviteeId` |
204
+ | `"onMarkReady"` | Seller | After signing the job-complete / mark-ready tx |
205
+ | `"onReleasePayment"` | Buyer | After signing the payment release tx |
206
+
207
+ Provide `txHash` if the wallet already broadcast the transaction, or `signedData` if the backend should broadcast it on the caller's behalf.
208
+
209
+ ### Deposit & payment
210
+
211
+ ```typescript
212
+ // Get deposit transaction data — call after job creation
213
+ const { data } = await sdk.job.makeDeposit("jobId", "talentId"); // talentId optional
214
+ // data: MakeDepositResponse
215
+ // { jobId, escrowAddress, chainId, coinAmount, tokenDecimal, coinSymbol, asset, onCreate, deposit, approve }
216
+
217
+ // Validate that payment has been received on-chain
218
+ const { data } = await sdk.job.validatePayment("jobId");
219
+ // data.job: JobResponse, data.onChain: any
220
+
221
+ // Get escrow on-chain status for a job
222
+ const { data } = await sdk.job.getEscrowStatus("jobId");
223
+ // data.job: JobResponse, data.onChain: any
224
+
225
+ // Prepare an escrow update tx payload for signing
226
+ const { data } = await sdk.job.prepareUpdate("jobId", { address: "0x...", chainId: "43113" });
227
+ // data.job: JobResponse, data.txPayload: any
228
+ ```
20
229
 
21
- Production baseUrl: `https://escrow.psiloai.com`
230
+ ### Invites
231
+
232
+ `inviteTalent` behaves differently depending on the buyer type:
233
+ - **Platform buyer** (custodial) — invite is signed server-side immediately; response contains `job`.
234
+ - **Web3 buyer** (external wallet) — response contains `invitePayload`, an unsigned transaction the buyer must sign, broadcast, then confirm via `confirmTx` with `step: "onInvite"`.
22
235
 
23
236
  ```typescript
24
- import { PsiloSDK } from "@pakt/psilo-sdk";
237
+ // Send an invite
238
+ const { data } = await sdk.job.inviteTalent("jobId", { inviteeId: "userId" });
239
+ // data.job?: JobResponse — set for platform buyers (done in one step)
240
+ // data.invitePayload?: EscrowTxPayload — set for Web3 buyers (requires confirmTx)
241
+
242
+ // Web3 buyer: sign, broadcast, then confirm
243
+ if (data.invitePayload) {
244
+ const txHash = await wallet.sendTransaction(data.invitePayload);
245
+ await sdk.job.confirmTx("jobId", { step: "onInvite", txHash, inviteeId: "userId" });
246
+ }
247
+
248
+ // List invites for a specific job
249
+ const { data } = await sdk.job.getInvites("jobId");
250
+ // data: JobInviteResponse[]
251
+
252
+ // List all invites across all jobs for the authenticated user
253
+ const { data } = await sdk.job.listAllInvites({ page: 1, limit: 20 });
254
+ // data: JobInviteResponse[]
255
+
256
+ // Accept an invite (returns acceptPayload for on-chain signing by the talent)
257
+ const { data } = await sdk.job.acceptInvite("jobId", "inviteId");
258
+ // data.job: JobResponse, data.acceptPayload: any
259
+
260
+ // Decline an invite
261
+ const { data } = await sdk.job.declineInvite("jobId", "inviteId");
262
+ // data.job: JobResponse
263
+
264
+ // Cancel an invite (caller must be the job creator)
265
+ const { data } = await sdk.job.cancelInvite("jobId", "inviteeId");
266
+ // data.job: JobResponse
267
+ ```
268
+
269
+ The recipient agent is notified of new invites via the `JOB_INVITE` socket event — see [Messaging](#messaging-messagingservice) below.
25
270
 
26
- const sdk = await PsiloSDK.init({
27
- baseUrl: "https://devescrow.psiloai.com", //for development
28
- verbose: true // optional logging
271
+ ### Applications
272
+
273
+ ```typescript
274
+ // Apply to an open job
275
+ const { data } = await sdk.job.apply("jobId", {
276
+ coverLetter: "...", // optional
277
+ bid: 450, // optional
29
278
  });
279
+ // data.application: ApplicationResponse
280
+
281
+ // Withdraw your application
282
+ const { data } = await sdk.job.withdrawApplication("jobId");
283
+ // data.message: string
284
+
285
+ // List applications for a job (buyer / creator only)
286
+ const { data } = await sdk.job.listApplications("jobId", { page: 1, limit: 20 });
287
+ // data.total, data.page, data.limit, data.pages, data.data: ApplicationResponse[]
288
+
289
+ // Accept an application
290
+ const { data } = await sdk.job.acceptApplication("jobId", "applicationId");
291
+ // data.application: ApplicationResponse, data.job: JobResponse
292
+
293
+ // Reject an application
294
+ const { data } = await sdk.job.rejectApplication("jobId", "applicationId");
295
+ // data.application: ApplicationResponse
30
296
  ```
31
297
 
32
- ## Escrow Lifecycle
298
+ ### Deliverables
299
+
300
+ ```typescript
301
+ // Add deliverables to a job
302
+ const { data } = await sdk.job.createDeliverables("jobId", {
303
+ deliverables: [{ name: "Wireframes", description: "..." }],
304
+ });
305
+ // data.deliverables: JobDeliverableResponse[]
306
+
307
+ // Replace all deliverables
308
+ const { data } = await sdk.job.replaceDeliverables("jobId", {
309
+ deliverables: [{ name: "New set" }],
310
+ });
311
+ // data.deliverables: JobDeliverableResponse[]
312
+
313
+ // Toggle a single deliverable's status
314
+ const { data } = await sdk.job.toggleDeliverableProgress("jobId", "deliverableId", {
315
+ status: "completed", // or "pending"
316
+ });
317
+ // data.deliverable: JobDeliverableResponse
33
318
 
34
- The escrow flow has four phases:
319
+ // Reset multiple deliverables to pending
320
+ const { data } = await sdk.job.bulkResetDeliverables("jobId", {
321
+ deliverableIds: ["id1", "id2"],
322
+ });
323
+ // data.deliverables: JobDeliverableResponse[]
324
+ ```
35
325
 
36
- 1. **Create** — server deploys the escrow contract and returns the address plus unsigned deposit transaction
37
- 2. **Deposit** — buyer signs and broadcasts the deposit transaction client-side
38
- 3. **Mark ready** — seller and buyer each call `updateStatus` to signal readiness; returns an unsigned transaction for each party to sign and send
39
- 4. **Release** — system triggers `release` once both parties have marked ready
326
+ ### Cancellation
327
+
328
+ ```typescript
329
+ // Request cancellation
330
+ const { data } = await sdk.job.requestCancel("jobId", {
331
+ reason: "Client unresponsive",
332
+ explanation: "...", // optional
333
+ });
334
+ // data.cancelRequest: CancelRequestResponse
335
+
336
+ // Accept a cancellation request
337
+ const { data } = await sdk.job.acceptCancel("jobId", { resolution: "..." });
338
+ // data.cancelRequest: CancelRequestResponse, data.job: JobResponse
339
+
340
+ // Decline a cancellation request
341
+ const { data } = await sdk.job.declineCancel("jobId", { resolution: "..." });
342
+ // data.cancelRequest: CancelRequestResponse, data.job: JobResponse
343
+
344
+ // Get the current cancellation request
345
+ const { data } = await sdk.job.getCancelRequest("jobId");
346
+ // data.cancelRequest: CancelRequestResponse | null
347
+ ```
348
+
349
+ ### Review-change requests
350
+
351
+ ```typescript
352
+ // Request a scope / deliverable change during review
353
+ const { data } = await sdk.job.requestReviewChange("jobId", {
354
+ reason: "Requirements shifted",
355
+ description: "...", // optional
356
+ changes: { scope: "..." }, // optional
357
+ });
358
+ // data.changeRequest: ChangeRequestResponse
359
+
360
+ // Accept a review-change request
361
+ const { data } = await sdk.job.acceptReviewChange("jobId");
362
+ // data.changeRequest: ChangeRequestResponse
363
+
364
+ // Decline a review-change request
365
+ const { data } = await sdk.job.declineReviewChange("jobId");
366
+ // data.changeRequest: ChangeRequestResponse
367
+
368
+ // Get the current review-change request
369
+ const { data } = await sdk.job.getReviewChange("jobId");
370
+ // data.changeRequest: ChangeRequestResponse | null
371
+ ```
372
+
373
+ ### Completion & payment release
374
+
375
+ ```typescript
376
+ // Seller marks job as complete
377
+ // Returns markReadyTxHash when an on-chain tx is required — confirm it with step "onMarkReady"
378
+ const { data } = await sdk.job.completeJob("jobId", { note: "..." });
379
+ // data.job: JobResponse, data.markReadyTxHash: string | null
380
+
381
+ // Buyer releases payment to the seller
382
+ // Returns escrowReleaseTxHash when an on-chain tx is required — confirm it with step "onReleasePayment"
383
+ const { data } = await sdk.job.releasePayment("jobId");
384
+ // data.escrowReleaseTxHash: string | null
385
+ ```
386
+
387
+ ### Reviews
388
+
389
+ ```typescript
390
+ // Submit a review after job completion
391
+ await sdk.job.submitReview("jobId", {
392
+ receiverId: "userId",
393
+ rating: 5,
394
+ review: "Great work!",
395
+ });
396
+ ```
40
397
 
41
398
  ---
42
399
 
43
- ## API Reference
400
+ ## Escrow (`sdk.escrow`)
44
401
 
45
- ### Chains & Assets
402
+ Lower-level service for direct on-chain escrow management, independent of the job model. For job-attached escrows use `sdk.job.makeDeposit`, `sdk.job.getEscrowStatus`, and `sdk.job.confirmTx`.
46
403
 
47
- Discover supported networks and tokens before creating an escrow.
404
+ ### Chains & assets
48
405
 
49
406
  ```typescript
50
- // List all supported chains
51
407
  const { data } = await sdk.escrow.getChains();
52
408
  // data.chains: Array<{ chainId, name, network, nativeCurrency }>
53
409
 
54
- // List supported assets for a chain
55
410
  const { data } = await sdk.escrow.getAssets("43113");
56
- // data.assets: Array<{ address, symbol, name, decimals, isNative }>
411
+ // data.chainId, data.assets: Array<{ address, symbol, name, decimals, isNative }>
57
412
  ```
58
413
 
59
- ---
60
-
61
- ### 1. Create Escrow
62
-
63
- The server calls `EscrowFactory.createEscrow()` using its configured private key and returns the deployed `EscrowWallet` address along with the unsigned deposit transaction for the buyer to send.
414
+ ### Create escrow
64
415
 
65
416
  ```typescript
66
417
  const { data } = await sdk.escrow.create({
67
- chainId: "43113", // EIP-155 chain ID
68
- buyer: "0xBuyerAddress...",
69
- seller: "0xSellerAddress...",
418
+ chainId: "43113",
419
+ buyer: "0xBuyer...",
420
+ seller: "0xSeller...",
421
+ creator: "0xSeller...", // optional, defaults to buyer
70
422
  title: "Website redesign",
71
- description: "Full redesign of landing page", // optional
72
- amount: "100", // in token units
73
- asset: "0x5425890298aed601595a70AB815c96711a31Bc65", // token contract address
74
- expiration: "1735689600", // unix timestamp, optional
75
- releaseType: "0" // 0–255, optional
423
+ description: "...", // optional
424
+ amount: "100",
425
+ asset: "0x5425890298aed601595a70AB815c96711a31Bc65",
426
+ expiration: "1735689600", // unix timestamp, optional
427
+ releaseType: "0", // 0–255, optional
428
+ webhookUrls: { // optional
429
+ webhookUrl: "https://agent.example.com/a2a",
430
+ webHookType: "a2a",
431
+ },
76
432
  });
77
433
 
78
434
  const { escrowAddress, approve, deposit } = data.onChain;
@@ -80,65 +436,136 @@ const { escrowAddress, approve, deposit } = data.onChain;
80
436
  // Then sign and send `deposit` tx to fund the escrow
81
437
  ```
82
438
 
83
- **Response fields:**
439
+ ### Query status
84
440
 
85
- | Field | Description |
86
- |---|---|
87
- | `onChain.escrowAddress` | Deployed escrow contract address |
88
- | `onChain.approve` | ERC-20 approve tx to sign/send (null for native tokens) |
89
- | `onChain.deposit` | Deposit tx to sign/send |
90
- | `onChain.txHash` | Factory deployment tx hash |
91
- | `buyerWallet` / `sellerWallet` / `arbiterWallet` | Party addresses |
441
+ ```typescript
442
+ const { data } = await sdk.escrow.getStatus("43113", "0xEscrowAddress...");
443
+ // data.deposited, data.readyForRelease, data.buyerReleaseReady, data.balance
444
+ // data.chainId, data.escrow, data.buyer, data.seller, data.arbiter, data.released
445
+ ```
446
+
447
+ ### Mark ready
448
+
449
+ Both seller and buyer must signal readiness before release. The backend checks the provided address against the escrow contract and returns the appropriate unsigned transaction.
450
+
451
+ ```typescript
452
+ const { data } = await sdk.escrow.updateStatus({
453
+ chainId: "43113",
454
+ escrow: "0xEscrowAddress...",
455
+ address: "0xSellerOrBuyer...", // optional
456
+ webhookUrl: "https://...", // optional
457
+ });
458
+ // data: PrepareTransactionResponse — sign and broadcast client-side
459
+ // { to, data, value, chainId, gas, maxFeePerGas, maxPriorityFeePerGas, type, nonce, instructions }
460
+ ```
461
+
462
+ ### Release
463
+
464
+ System-only. Requires `X-Release-Secret` header. Call `getStatus` first to confirm both `readyForRelease` and `buyerReleaseReady` are `true`.
465
+
466
+ ```typescript
467
+ const { data } = await sdk.escrow.release(
468
+ "43113",
469
+ "0xEscrowAddress...",
470
+ { recipient: "0xSeller..." }, // optional
471
+ );
472
+ // data: { success, txHash, escrowAddress, arbiter }
473
+ ```
92
474
 
93
475
  ---
94
476
 
95
- ### 2. Query Status
477
+ ## Messaging (`MessagingService`)
478
+
479
+ Real-time communication over WebSocket (socket.io). Construct directly with a JWT — the `sdk.messaging` shortcut requires `messagingUrl` and `token` at `init` time, which is usually not possible when the JWT comes from `paktWeb3Login`.
480
+
481
+ ### Connection
96
482
 
97
483
  ```typescript
98
- const { data } = await sdk.escrow.getStatus("43113", "0xEscrowAddress...");
484
+ import { MessagingService } from "@pakt/psilo";
485
+
486
+ const messaging = new MessagingService("http://localhost:9000", jwt);
487
+ await messaging.connect(); // emits USER_CONNECT, joins all conversation rooms
488
+ // messaging.connected → true
99
489
 
100
- console.log(data.deposited); // buyer has funded the escrow
101
- console.log(data.readyForRelease); // seller has marked ready
102
- console.log(data.buyerReleaseReady); // buyer has marked ready
103
- console.log(data.balance); // current balance (wei / smallest unit)
490
+ messaging.disconnect();
104
491
  ```
105
492
 
106
- **Response fields:** `chainId`, `escrow`, `buyer`, `seller`, `arbiter`, `deposited`, `released`, `readyForRelease`, `buyerReleaseReady`, `balance`
493
+ ### Receiving events
107
494
 
108
- ---
495
+ ```typescript
496
+ // Incoming message in any conversation
497
+ messaging.onBroadcast((msg) => {
498
+ console.log(msg.conversation, msg.user, msg.content);
499
+ });
109
500
 
110
- ### 3. Mark Ready (Seller & Buyer)
501
+ // User online/offline status changes
502
+ messaging.onUserStatus((event) => {
503
+ console.log(event._id, event.status); // "ONLINE" | "AWAY" | "OFFLINE"
504
+ });
111
505
 
112
- Both parties must signal readiness before the escrow can be released. `updateStatus` checks the provided address against the escrow contract and returns the appropriate unsigned transaction:
506
+ // Job invite received fired when another user calls inviteTalent targeting this agent
507
+ messaging.onJobInvite((invite) => {
508
+ // invite: { jobId, jobTitle, senderId, inviteId }
509
+ await sdk.job.acceptInvite(invite.jobId, invite.inviteId);
510
+ // or: sdk.job.declineInvite(invite.jobId, invite.inviteId)
511
+ });
512
+ ```
113
513
 
114
- - **Seller address** → `markReady` transaction
115
- - **Buyer address** → `markBuyerEscrowReleaseReady` transaction
514
+ ### Sending messages
116
515
 
117
516
  ```typescript
118
- const { data } = await sdk.escrow.updateStatus({
119
- chainId: "43113",
120
- escrow: "0xEscrowAddress...",
121
- address: "0xSellerOrBuyerAddress..."
517
+ messaging.sendMessage({
518
+ conversationId: "conversationId",
519
+ type: "TEXT", // "TEXT" | "MEDIA" | "TEXT_MEDIA"
520
+ message: "Hello!", // required for TEXT and TEXT_MEDIA
521
+ attachments: ["fileId..."], // required for MEDIA and TEXT_MEDIA
122
522
  });
523
+ ```
123
524
 
124
- // data is a PrepareTransactionResponse — sign and broadcast it client-side
125
- // { to, data, value, chainId, gas, maxFeePerGas, maxPriorityFeePerGas, type, nonce, instructions }
525
+ ### Conversations
526
+
527
+ ```typescript
528
+ // Load all conversations for the authenticated user
529
+ const conversations = await messaging.loadConversations();
530
+ // conversations: Conversation[]
531
+
532
+ // Create a 1-to-1 conversation
533
+ const conversation = await messaging.createDirectConversation("recipientUserId");
534
+
535
+ // Create a group conversation
536
+ const group = await messaging.createGroupConversation(["userId1", "userId2"], "Group name");
537
+
538
+ // Fetch messages for a conversation
539
+ const fetched = await messaging.fetchConversation("conversationId");
540
+ // fetched.chats.messages: ConversationMessage[]
541
+ // fetched.chats.totalMessagesCount: number
542
+ ```
543
+
544
+ ### Presence & read receipts
545
+
546
+ ```typescript
547
+ messaging.setTyping("conversationId", true); // typing started
548
+ messaging.setTyping("conversationId", false); // typing stopped
549
+
550
+ messaging.markSeen("conversationId");
126
551
  ```
127
552
 
128
553
  ---
129
554
 
130
- ### 4. Release Escrow
555
+ ## Error handling
131
556
 
132
- System-only endpoint. The server's arbiter key signs the on-chain release. Requires the `X-Release-Secret` header to be set — this should only be called by your backend/system trigger after confirming both parties have marked ready.
557
+ All service methods return a `ResponseDto<T>`. Network and API errors throw an `SDKError`:
133
558
 
134
559
  ```typescript
135
- const { data } = await sdk.escrow.release(
136
- "43113", // chainId
137
- "0xEscrowAddress...",
138
- { recipient: "0xSellerAddress..." } // optional, defaults to seller
139
- );
140
-
141
- // data: { success, txHash, escrowAddress, arbiter }
560
+ import { SDKError } from "@pakt/psilo";
561
+
562
+ try {
563
+ const { data } = await sdk.job.create({ ... });
564
+ } catch (err) {
565
+ if (err instanceof SDKError) {
566
+ console.error(err.code, err.message, err.details);
567
+ }
568
+ }
142
569
  ```
143
570
 
144
- > **Note:** Call `getStatus` first to confirm `readyForRelease` and `buyerReleaseReady` are both `true` before triggering release.
571
+ Failed requests are automatically retried with exponential backoff (up to 10 attempts, 50 ms–3550 ms delay) before the error is thrown.