@usepraxis/sdk 0.1.1 → 0.5.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/dist/index.d.ts CHANGED
@@ -64,7 +64,10 @@ interface WalletChallenge {
64
64
  }
65
65
  interface SessionInfo {
66
66
  authenticated: boolean;
67
+ /** The signed-in wallet (owner). Present whenever `authenticated` is true. */
67
68
  walletAddress: Address;
69
+ /** Unix seconds at which the session expires (from `GET /auth/session`). */
70
+ expiresAt?: number;
68
71
  }
69
72
  interface TokenInfo {
70
73
  symbol: string;
@@ -127,6 +130,14 @@ interface TransferDetail {
127
130
  recipientName: string;
128
131
  recipientAddress: Address;
129
132
  recipientNote?: string;
133
+ /** The destination is the owner's own wallet (derived from the address). */
134
+ toSelf?: true;
135
+ /**
136
+ * Server-computed USD value of `amount`, as a decimal string, when a real
137
+ * price is available. Absent means "not known" — display only, and never an
138
+ * input to base-unit math.
139
+ */
140
+ usdEstimate?: string;
130
141
  }
131
142
  interface SwapDetail {
132
143
  kind: "swap";
@@ -141,6 +152,14 @@ type ProposalDetail = TransferDetail | SwapDetail;
141
152
  type ProposalState = "pending" | "signing" | "signed" | "blocked" | "cancelled";
142
153
  interface ActionProposal {
143
154
  id: string;
155
+ /**
156
+ * Unix seconds when the proposal was produced. The signature gate refuses a
157
+ * proposal older than a week: the amount is fixed and Aegis enforces the
158
+ * envelope live, but the readings on it (fee, simulation, remaining
159
+ * envelope, USD figure) drift. Ask again for a fresh one. Absent on
160
+ * proposals created before this field existed.
161
+ */
162
+ createdAt?: number;
144
163
  detail: ProposalDetail;
145
164
  networkFee: BaseUnitString;
146
165
  simulation: string;
@@ -155,15 +174,51 @@ interface ClarifyOption {
155
174
  }
156
175
  interface ResearchMetric {
157
176
  label: string;
177
+ /** Already display-formatted, and abbreviated where a raw figure is unreadable. */
158
178
  value: string;
179
+ /** The full-precision figure behind an abbreviated `value`, when one was cut. */
180
+ exact?: string;
181
+ /** Why the metric is missing, or what it measures. */
182
+ note?: string;
159
183
  trend?: "up" | "down" | "flat";
160
184
  }
185
+ /**
186
+ * One step in how a research card was produced. A card whose whole claim is
187
+ * "data, no advice" has to be able to show its working: "unavailable" means
188
+ * something different when an indexer has no pair than when an RPC refused
189
+ * the query.
190
+ */
191
+ interface ResearchSource {
192
+ /** "Solana RPC", "Market data (…)", "PreStocks", "Token resolution". */
193
+ label: string;
194
+ status: "ok" | "partial" | "unavailable";
195
+ detail: string;
196
+ }
161
197
  interface ResearchData {
162
198
  token: string;
199
+ /** Project name from the indexer, when it differs from the ticker. */
200
+ name?: string;
163
201
  mint: Address;
164
202
  metrics: ResearchMetric[];
203
+ /** How the card was produced, in the order the steps ran. */
204
+ sources?: ResearchSource[];
165
205
  summary: string;
166
206
  }
207
+ /**
208
+ * Stocklana C07: one PreStocks entry from `GET /get-stock-universe`.
209
+ * Empty unless the server runs with `PRAXIS_STOCKS_ENABLED=1`.
210
+ */
211
+ interface StockUniverseEntry {
212
+ symbol: string;
213
+ name: string;
214
+ mint: Address;
215
+ decimals: number;
216
+ }
217
+ interface PolicyChangeRow {
218
+ label: string;
219
+ from: string;
220
+ to: string;
221
+ }
167
222
  type AgentBlock = {
168
223
  type: "prose";
169
224
  text: string;
@@ -179,6 +234,16 @@ type AgentBlock = {
179
234
  type: "research";
180
235
  text: string;
181
236
  data: ResearchData;
237
+ } | {
238
+ type: "notice";
239
+ tone: "success" | "info";
240
+ text: string;
241
+ } | {
242
+ type: "policy_change";
243
+ text: string;
244
+ patch: PolicyUpdate;
245
+ changes: PolicyChangeRow[];
246
+ applied: boolean;
182
247
  };
183
248
  type UserMessage = {
184
249
  id: string;
@@ -203,6 +268,8 @@ interface ActivityEntry {
203
268
  id: string;
204
269
  kind: "transfer" | "swap";
205
270
  label: string;
271
+ /** Destination address for a transfer. Stable where `label` is a lookup. */
272
+ target?: Address;
206
273
  asset: string;
207
274
  amount: BaseUnitString;
208
275
  decimals: number;
@@ -212,12 +279,55 @@ interface ActivityEntry {
212
279
  ts: number;
213
280
  sig?: string;
214
281
  }
282
+ /** When a recurring buy fires. `weekday`: 0=Sunday..6=Saturday (UTC). */
283
+ type DcaCadence = {
284
+ type: "daily";
285
+ } | {
286
+ type: "weekly";
287
+ weekday: number;
288
+ } | {
289
+ type: "monthly";
290
+ day: number;
291
+ };
292
+ /**
293
+ * One recurring-buy schedule from `GET /get-schedules`. Each fire emits one
294
+ * transfer proposal through the same policy checks as a one-off buy — nothing
295
+ * ever auto-signs. Money is a base-unit string per the money rule above.
296
+ */
297
+ interface DcaSchedule {
298
+ id: string;
299
+ asset: string;
300
+ amount: BaseUnitString;
301
+ decimals: number;
302
+ recipientAddress: Address;
303
+ recipientName: string;
304
+ cadence: DcaCadence;
305
+ /** Unix milliseconds of the next fire. */
306
+ nextFireTs: number;
307
+ createdAt: number;
308
+ threadId: string;
309
+ }
215
310
  interface UnsignedOwnerTransaction {
216
311
  /** base64-encoded unsigned transaction for the owner wallet to sign. */
217
312
  transaction: string;
218
313
  blockhash: string;
219
314
  lastValidBlockHeight: number;
315
+ /**
316
+ * Opaque, backend-signed fingerprint of this draft. Echo it back verbatim in
317
+ * {@link SignedOwnerTransaction}: the relay refuses any transaction that is
318
+ * not the one Praxis built, which is what keeps the server-side checks for
319
+ * this action from being skippable.
320
+ */
321
+ draft: string;
220
322
  }
323
+ /**
324
+ * An {@link UnsignedOwnerTransaction} after the owner wallet has signed it — the
325
+ * base64 `transaction` now carries the owner's signature. This is what you pass
326
+ * to {@link PraxisClient.submitOwnerTransaction}. Signing a Solana transaction
327
+ * requires a transaction-capable wallet (browser wallet adapter / `@solana/web3.js`);
328
+ * the SDK's `keypairSigner` only signs the sign-in *message*, not transactions.
329
+ */
330
+ type SignedOwnerTransaction = UnsignedOwnerTransaction;
221
331
  /** Typed owner action accepted by `POST /owner/build`. */
222
332
  type OwnerAction = {
223
333
  kind: "bootstrapPolicy";
@@ -242,6 +352,14 @@ type OwnerAction = {
242
352
  listKind: AllowListKind;
243
353
  address: Address;
244
354
  mode: "add" | "remove";
355
+ } | {
356
+ kind: "configureToken";
357
+ tokenMint: Address;
358
+ tokenMaxPerTx: BaseUnitString;
359
+ tokenDailyLimit: BaseUnitString;
360
+ } | {
361
+ kind: "prepareTokenAccounts";
362
+ recipientAddresses?: Address[];
245
363
  };
246
364
 
247
365
  type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
@@ -252,8 +370,14 @@ interface PraxisClientOptions {
252
370
  signer?: PraxisSigner;
253
371
  /** Custom fetch (defaults to global fetch). Required in runtimes without one. */
254
372
  fetch?: FetchLike;
255
- /** Per-request timeout in ms (default 20_000). */
373
+ /** Per-request timeout in ms for reads/mutations (default 20_000). */
256
374
  timeoutMs?: number;
375
+ /**
376
+ * Per-request timeout in ms for agent conversation turns (`send`/`ask`), which
377
+ * block on the full LLM round-trip (default 60_000). Effective value is never
378
+ * below `timeoutMs`.
379
+ */
380
+ agentTimeoutMs?: number;
257
381
  }
258
382
  /** Result of {@link PraxisClient.ask} — the agent's reply, distilled. */
259
383
  interface AskResult {
@@ -280,6 +404,7 @@ declare class PraxisClient {
280
404
  private readonly signer?;
281
405
  private readonly fetchImpl;
282
406
  private readonly timeoutMs;
407
+ private readonly agentTimeoutMs;
283
408
  /** Manual cookie jar — Node's fetch does not persist Set-Cookie across calls. */
284
409
  private sessionCookie?;
285
410
  constructor(options: PraxisClientOptions);
@@ -287,12 +412,20 @@ declare class PraxisClient {
287
412
  get address(): string | undefined;
288
413
  /**
289
414
  * Run the wallet-ownership handshake: request a challenge, sign its message,
290
- * verify it, and store the resulting session cookie. Idempotent.
415
+ * verify it, and store the resulting session cookie. Safe to call again to
416
+ * refresh the session (each call issues a new challenge + cookie).
291
417
  */
292
418
  connect(): Promise<SessionInfo>;
293
- /** Current session, or `null` if not signed in. */
419
+ /**
420
+ * Current session, or `null` if not signed in. The endpoint answers `200`
421
+ * with `{ authenticated: false }` when signed out, so this normalizes both
422
+ * that shape and a `401` to `null`.
423
+ */
294
424
  session(): Promise<SessionInfo | null>;
295
- /** Clear the session (server-side cookie + local jar). */
425
+ /**
426
+ * Clear the session (server-side cookie + local jar). Idempotent: if there is
427
+ * no active session, the local jar is still cleared and no error is thrown.
428
+ */
296
429
  logout(): Promise<void>;
297
430
  /** Send a line to the agent. Creates a thread when `threadId` is omitted. */
298
431
  send(text: string, threadId?: string | null): Promise<{
@@ -308,15 +441,46 @@ declare class PraxisClient {
308
441
  }>;
309
442
  signProposal(proposalId: string): Promise<void>;
310
443
  cancelProposal(proposalId: string): Promise<void>;
444
+ /** List recurring-buy schedules (each fire emits one proposal; never signs). */
445
+ getSchedules(): Promise<DcaSchedule[]>;
446
+ /** Stop a recurring-buy schedule. Unknown ids are a no-op (idempotent). */
447
+ cancelSchedule(scheduleId: string): Promise<void>;
448
+ /** Save (or rename) an address-book contact. Labels have no signing power. */
449
+ addContact(label: string, address: string): Promise<void>;
450
+ /** Remove a contact by address or label (case-insensitive, idempotent). */
451
+ removeContact(key: string): Promise<void>;
311
452
  getThreads(): Promise<Thread[]>;
312
453
  getThread(id: string): Promise<Thread>;
313
454
  getProposal(id: string): Promise<ActionProposal>;
455
+ /**
456
+ * Every proposal this wallet holds, in one request. Prefer this over a loop
457
+ * of {@link getProposal}: a per-id fetch is what trips the read rate limit
458
+ * on a busy thread.
459
+ */
460
+ getProposals(): Promise<ActionProposal[]>;
314
461
  getPolicy(): Promise<PolicyView>;
315
462
  getActivity(): Promise<ActivityEntry[]>;
316
463
  getAddressBook(): Promise<AddressBookEntry[]>;
317
- isThinking(threadId: string): Promise<boolean>;
318
464
  getVersion(): Promise<number>;
465
+ /**
466
+ * The server's stock universe (`[]` when `PRAXIS_STOCKS_ENABLED` is off).
467
+ * Read-only; symbols/mints here are the only pre-IPO stocks Praxis will
468
+ * touch (bounty exclusivity is enforced server-side).
469
+ */
470
+ getTokenUniverse(): Promise<StockUniverseEntry[]>;
471
+ /**
472
+ * Read-only research for a stock symbol, via the agent (`research <symbol>`).
473
+ * Returns neutral market data — never advice. Throws when the agent has no
474
+ * research to show (unknown symbol or unavailable quotes).
475
+ */
476
+ getStockResearch(symbol: string): Promise<ResearchData>;
319
477
  bootstrapPolicy(fundLamports?: BaseUnitString): Promise<void>;
478
+ /** Deposit SOL (lamports, base-unit string) from the owner into the vault. */
479
+ fundVault(amount: BaseUnitString): Promise<void>;
480
+ /** Withdraw SOL (lamports, base-unit string) from the vault to the owner. */
481
+ withdrawVault(amount: BaseUnitString): Promise<void>;
482
+ /** Tear the agent down — drain the vault and close the policy. Irreversible. */
483
+ deleteAgent(): Promise<void>;
320
484
  updatePolicy(patch: PolicyUpdate): Promise<void>;
321
485
  configureToken(config: TokenEnvelopeConfig): Promise<void>;
322
486
  prepareTokenAccounts(recipientAddresses?: string[]): Promise<void>;
@@ -324,35 +488,88 @@ declare class PraxisClient {
324
488
  rotateAgent(): Promise<void>;
325
489
  addToAllowList(kind: AllowListKind, address: string): Promise<void>;
326
490
  removeFromAllowList(kind: AllowListKind, address: string): Promise<void>;
327
- /** Build an unsigned owner transaction for the wallet to sign. */
491
+ /**
492
+ * Build an unsigned owner transaction for the wallet to sign. The caller signs
493
+ * the returned base64 `transaction` with a transaction-capable wallet, then
494
+ * passes the result to {@link submitOwnerTransaction}. (The SDK's
495
+ * `keypairSigner` signs sign-in messages only, not transactions.)
496
+ */
328
497
  buildOwnerTransaction(action: OwnerAction): Promise<UnsignedOwnerTransaction>;
329
- /** Submit a wallet-signed owner transaction. */
330
- submitOwnerTransaction(signed: UnsignedOwnerTransaction): Promise<{
498
+ /** Submit a wallet-signed owner transaction; resolves with its signature. */
499
+ submitOwnerTransaction(signed: SignedOwnerTransaction): Promise<{
331
500
  sig: string;
332
501
  }>;
333
502
  private get;
334
503
  private post;
504
+ /**
505
+ * Run a request, and if the session has expired, sign in again and retry it
506
+ * once.
507
+ *
508
+ * Sessions are deliberately short — holding one is enough to move value
509
+ * within the Aegis envelope — so a long-lived agent process WILL outlive its
510
+ * cookie. Without this, every caller writes the same catch-401-and-reconnect
511
+ * block, and the ones who do not simply stop working after a day. Retried
512
+ * once only, never for the auth endpoints themselves, and only when a signer
513
+ * is configured; without one there is nothing to re-authenticate with and the
514
+ * 401 is the honest answer.
515
+ */
335
516
  private request;
517
+ private send1;
336
518
  private captureCookie;
337
519
  }
338
520
 
521
+ /**
522
+ * A stable, machine-readable classification of a backend failure. Branch on
523
+ * this rather than on `message`, which is human prose and free to change.
524
+ * `client_error` is synthesized SDK-side for failures with no HTTP response
525
+ * (timeout, DNS, TLS).
526
+ */
527
+ type PraxisErrorCode = "config_error" | "unauthorized" | "invalid_input" | "not_found"
528
+ /** The wallet has no Aegis policy account yet — call `bootstrapPolicy`. */
529
+ | "policy_not_found" | "rate_limited"
530
+ /** A concurrent writer changed this wallet's state first; reload and retry. */
531
+ | "conflict" | "internal_error" | "client_error";
339
532
  /**
340
533
  * Thrown when the Praxis API returns a non-2xx response. The backend's error
341
- * envelope is `{ error: string, type: string }` with a meaningful HTTP status
534
+ * envelope is `{ error, type, code, details? }` with a meaningful HTTP status
342
535
  * (400 input, 401 auth, 404 not-found, 429 rate-limit, 503 config, 500 other).
343
536
  */
344
537
  declare class PraxisApiError extends Error {
345
538
  readonly status: number;
346
539
  /** The backend error class name, e.g. "PraxisAuthError", "PraxisRateLimitError". */
347
540
  readonly type: string;
348
- constructor(status: number, type: string, message: string);
541
+ /** Stable error classification the field to branch on. */
542
+ readonly code: PraxisErrorCode;
543
+ /** Structured facts about the failure, e.g. `{ policyAddress }` for `policy_not_found`. */
544
+ readonly details?: Record<string, string | number | boolean>;
545
+ constructor(status: number, type: string, message: string, options?: {
546
+ cause?: unknown;
547
+ code?: PraxisErrorCode;
548
+ details?: Record<string, string | number | boolean>;
549
+ });
349
550
  get isAuth(): boolean;
350
551
  get isRateLimited(): boolean;
351
552
  get isInput(): boolean;
553
+ /** Resource not found (404). */
554
+ get isNotFound(): boolean;
555
+ /** Server reported a configuration problem (503) — usually transient. */
556
+ get isConfig(): boolean;
557
+ /** The request timed out client-side before any HTTP response. */
558
+ get isTimeout(): boolean;
559
+ /** A connection-level failure (DNS, refused, TLS) before any HTTP response. */
560
+ get isNetwork(): boolean;
561
+ /** Any server-side failure (HTTP >= 500). */
562
+ get isServer(): boolean;
563
+ /** The wallet has no Aegis policy yet — the first-run state, not a fault. */
564
+ get isPolicyNotFound(): boolean;
565
+ /** A concurrent writer won; the call is safe to retry after a reload. */
566
+ get isConflict(): boolean;
352
567
  }
353
568
  /** Thrown for SDK-side misconfiguration (no fetch, no signer, bad key, …). */
354
569
  declare class PraxisConfigError extends Error {
355
- constructor(message: string);
570
+ constructor(message: string, options?: {
571
+ cause?: unknown;
572
+ });
356
573
  }
357
574
 
358
575
  /**
@@ -360,13 +577,21 @@ declare class PraxisConfigError extends Error {
360
577
  * base units (lamports / token base units). These convert to/from `bigint` and
361
578
  * human decimal amounts without floats.
362
579
  */
363
- /** Parse a base-unit string (or bigint) into a bigint. */
580
+ /**
581
+ * Parse a base-unit string (or bigint) into a bigint. Rejects floats, hex, and
582
+ * other non-decimal-integer input so the SDK and server agree on what a valid
583
+ * base-unit string is (mirrors the server's `parseUnits`).
584
+ */
364
585
  declare function toBaseUnits(value: string | bigint): bigint;
365
- /** Serialize a bigint (or number of whole base units) into a base-unit string. */
586
+ /**
587
+ * Serialize a bigint (or a whole, safe-integer number of base units) into a
588
+ * base-unit string. A non-integer or unsafe `number` throws rather than
589
+ * silently losing precision.
590
+ */
366
591
  declare function fromBaseUnits(value: bigint | number): string;
367
592
  /** Convert a human decimal amount ("0.5") into base units for `decimals` places. */
368
593
  declare function humanToBaseUnits(amount: string, decimals: number): string;
369
594
  /** Convert base units into a human decimal string for `decimals` places. */
370
595
  declare function baseUnitsToHuman(value: string | bigint, decimals: number): string;
371
596
 
372
- export { ActionKind, type ActionProposal, type ActivityEntry, type Address, type AddressBookEntry, type AgentBlock, type AgentMessage, type AllowListKind, type AskResult, type BaseUnitString, type ClarifyOption, type FetchLike, type Message, type OwnerAction, type PolicyCheckResult, type PolicyUpdate, type PolicyView, PraxisApiError, PraxisClient, type PraxisClientOptions, PraxisConfigError, type PraxisSigner, type ProposalDetail, type ProposalState, RejectReason, type ResearchData, type ResearchMetric, type SecretKeyInput, type SessionInfo, type SwapDetail, type Thread, type TokenEnvelopeConfig, type TokenInfo, type TransferDetail, type UnsignedOwnerTransaction, type UserMessage, type WalletChallenge, baseUnitsToHuman, fromBaseUnits, humanToBaseUnits, keypairSigner, toBaseUnits };
597
+ export { ActionKind, type ActionProposal, type ActivityEntry, type Address, type AddressBookEntry, type AgentBlock, type AgentMessage, type AllowListKind, type AskResult, type BaseUnitString, type ClarifyOption, type DcaCadence, type DcaSchedule, type FetchLike, type Message, type OwnerAction, type PolicyChangeRow, type PolicyCheckResult, type PolicyUpdate, type PolicyView, PraxisApiError, PraxisClient, type PraxisClientOptions, PraxisConfigError, type PraxisErrorCode, type PraxisSigner, type ProposalDetail, type ProposalState, RejectReason, type ResearchData, type ResearchMetric, type ResearchSource, type SecretKeyInput, type SessionInfo, type SignedOwnerTransaction, type StockUniverseEntry, type SwapDetail, type Thread, type TokenEnvelopeConfig, type TokenInfo, type TransferDetail, type UnsignedOwnerTransaction, type UserMessage, type WalletChallenge, baseUnitsToHuman, fromBaseUnits, humanToBaseUnits, keypairSigner, toBaseUnits };