@dexterai/connect 0.18.0 → 0.20.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.
@@ -124,6 +124,24 @@ function onStorageEvent(e) {
124
124
  }
125
125
  var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
126
126
 
127
+ // src/policy.ts
128
+ var SESSION_TTL_30D = "2592000";
129
+ function usdToAtomic(input) {
130
+ const cleaned = input.trim().replace(/^\$/, "").replace(/,/g, "");
131
+ if (!/^\d+(\.\d{1,6})?$/.test(cleaned)) return null;
132
+ const [whole, frac = ""] = cleaned.split(".");
133
+ try {
134
+ return BigInt(whole) * 1000000n + BigInt((frac + "000000").slice(0, 6));
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ function authoredPolicy(usdInput) {
140
+ const atomic = usdToAtomic(usdInput);
141
+ if (atomic === null || atomic <= 0n) return null;
142
+ return { spendLimitAtomic: atomic.toString(), sessionTtlSeconds: SESSION_TTL_30D };
143
+ }
144
+
127
145
  // src/enroll.ts
128
146
  import { startRegistration } from "@simplewebauthn/browser";
129
147
 
@@ -218,11 +236,13 @@ var DEFAULT_RP_ID = "dexter.cash";
218
236
  var DEFAULT_WALLET_NAME = "Dexter Wallet";
219
237
  async function createWallet(config = {}) {
220
238
  if (shouldUsePopup(config.transport)) {
221
- return openCeremonyPopup("create", {
239
+ const result = await openCeremonyPopup("create", {
222
240
  connectHost: config.connectHost,
223
241
  name: config.name,
224
242
  apiBase: config.apiBase
225
243
  });
244
+ setActiveHandle(result.handle, config.name, result.credentialId);
245
+ return result;
226
246
  }
227
247
  if (typeof navigator === "undefined" || !navigator.credentials) {
228
248
  throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
@@ -247,7 +267,12 @@ async function createWallet(config = {}) {
247
267
  config.onPhase?.("verifying");
248
268
  const enrolled = await submitEnrollComplete(apiBase, regResponse);
249
269
  config.onPhase?.("finalizing");
250
- const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
270
+ const init = await initializeVault(
271
+ apiBase,
272
+ enrolled.userHandle,
273
+ enrolled.credentialId,
274
+ config.spendPolicy
275
+ );
251
276
  setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
252
277
  return {
253
278
  handle: enrolled.userHandle,
@@ -288,11 +313,16 @@ async function submitEnrollComplete(apiBase, response) {
288
313
  if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
289
314
  return await res.json();
290
315
  }
291
- async function initializeVault(apiBase, userHandle, credentialId) {
316
+ async function initializeVault(apiBase, userHandle, credentialId, spendPolicy) {
317
+ const body = { userHandle, credentialId, coolingOffSeconds: 0 };
318
+ if (spendPolicy) {
319
+ body.spendLimitAtomic = spendPolicy.spendLimitAtomic;
320
+ body.sessionTtlSeconds = SESSION_TTL_30D;
321
+ }
292
322
  const res = await fetch(`${apiBase}/api/passkey-vault-anon/initialize`, {
293
323
  method: "POST",
294
324
  headers: { "content-type": "application/json" },
295
- body: JSON.stringify({ userHandle, credentialId, coolingOffSeconds: 0 })
325
+ body: JSON.stringify(body)
296
326
  });
297
327
  if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
298
328
  return await res.json();
@@ -312,10 +342,14 @@ var DEFAULT_API_BASE2 = "https://api.dexter.cash";
312
342
  var ANON_SIGN_BASE = "/api/passkey-anon/sign";
313
343
  async function passkeyLogin(config = {}, onPhase) {
314
344
  if (shouldUsePopup(config.transport)) {
315
- return openCeremonyPopup("signin", {
345
+ const result2 = await openCeremonyPopup("signin", {
316
346
  connectHost: config.connectHost,
317
347
  apiBase: config.apiBase
318
348
  });
349
+ if (result2.vault) {
350
+ setActiveHandle(result2.vault.userHandle, void 0, result2.vault.credentialId);
351
+ }
352
+ return result2;
319
353
  }
320
354
  if (!browserSupportsWebAuthn()) {
321
355
  throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
@@ -331,15 +365,25 @@ async function passkeyLogin(config = {}, onPhase) {
331
365
  throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
332
366
  }
333
367
  onPhase?.("verifying");
334
- return submitLogin(apiBase, response);
368
+ const result = await submitLogin(apiBase, response);
369
+ if (result.vault) {
370
+ setActiveHandle(result.vault.userHandle, void 0, result.vault.credentialId);
371
+ }
372
+ return result;
335
373
  }
336
374
  async function continueWithDexter(config = {}, onPhase) {
337
375
  if (shouldUsePopup(config.transport)) {
338
- return openCeremonyPopup("continue", {
376
+ const result2 = await openCeremonyPopup("continue", {
339
377
  connectHost: config.connectHost,
340
378
  name: config.name,
341
379
  apiBase: config.apiBase
342
380
  });
381
+ if (result2.kind === "create") {
382
+ setActiveHandle(result2.handle, config.name, result2.credentialId);
383
+ } else if (result2.vault) {
384
+ setActiveHandle(result2.vault.userHandle, void 0, result2.vault.credentialId);
385
+ }
386
+ return result2;
343
387
  }
344
388
  if (getActiveHandle()) {
345
389
  const result2 = await passkeyLogin(config, onPhase);
@@ -623,6 +667,9 @@ export {
623
667
  forgetWallet,
624
668
  subscribe,
625
669
  ACTIVE_WALLET_STORAGE_KEY,
670
+ SESSION_TTL_30D,
671
+ usdToAtomic,
672
+ authoredPolicy,
626
673
  createWallet,
627
674
  passkeyLogin,
628
675
  continueWithDexter,
@@ -6,22 +6,21 @@ var BUTTON_CSS = `
6
6
  @keyframes dx-spin { to { transform: rotate(360deg); } }
7
7
  @keyframes dx-pulse { 0%,100% { opacity: 1; } 50% { opacity: .6; } }
8
8
  .dx-btn{
9
- --dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
10
9
  position:relative; display:inline-flex; align-items:center; justify-content:center; gap:10px;
11
- padding:11px 22px; border:1px solid color-mix(in srgb,var(--dx-ember) 55%,transparent);
12
- border-radius:var(--dx-radius);
13
- background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2));
14
- color:var(--dx-fg); font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em;
10
+ padding:11px 22px; border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 55%,transparent);
11
+ border-radius:var(--dx-radius,0px);
12
+ background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00));
13
+ color:var(--dx-fg,#fff4ea); font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em;
15
14
  text-transform:uppercase; cursor:pointer; -webkit-tap-highlight-color:transparent;
16
- box-shadow:0 14px 26px color-mix(in srgb,var(--dx-ember) 24%,transparent);
15
+ box-shadow:0 14px 26px color-mix(in srgb,var(--dx-ember,#f26c18) 24%,transparent);
17
16
  transition:transform .16s ease, box-shadow .16s ease, filter .16s ease, background .16s ease;
18
17
  }
19
- .dx-btn:hover{ transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 20px 34px color-mix(in srgb,var(--dx-ember) 32%,transparent); }
20
- .dx-btn:active{ transform:translateY(0); filter:brightness(.97); box-shadow:0 8px 16px color-mix(in srgb,var(--dx-ember) 22%,transparent); }
21
- .dx-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 38%,transparent); }
18
+ .dx-btn:hover{ transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 20px 34px color-mix(in srgb,var(--dx-ember,#f26c18) 32%,transparent); }
19
+ .dx-btn:active{ transform:translateY(0); filter:brightness(.97); box-shadow:0 8px 16px color-mix(in srgb,var(--dx-ember,#f26c18) 22%,transparent); }
20
+ .dx-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 38%,transparent); }
22
21
  .dx-btn:disabled{ cursor:default; filter:saturate(.85) brightness(.98); }
23
- .dx-btn--secondary{ background:transparent; color:var(--dx-ember); box-shadow:none; border-color:color-mix(in srgb,var(--dx-ember) 45%,transparent); }
24
- .dx-btn--secondary:hover{ background:color-mix(in srgb,var(--dx-ember) 9%,transparent); filter:none; box-shadow:none; }
22
+ .dx-btn--secondary{ background:transparent; color:var(--dx-ember,#f26c18); box-shadow:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent); }
23
+ .dx-btn--secondary:hover{ background:color-mix(in srgb,var(--dx-ember,#f26c18) 9%,transparent); filter:none; box-shadow:none; }
25
24
  .dx-btn--block{ width:100%; }
26
25
  .dx-btn__mark{ flex-shrink:0; }
27
26
  .dx-btn__spin{ width:15px; height:15px; flex-shrink:0; border-radius:50%;
@@ -29,7 +28,7 @@ var BUTTON_CSS = `
29
28
  animation:dx-spin .7s linear infinite; }
30
29
  .dx-btn__doing{ animation:dx-pulse 1.4s ease-in-out infinite; }
31
30
  .dx-chip{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; font:inherit;
32
- font-variant-numeric:tabular-nums; border-radius:var(--dx-radius,0);
31
+ font-variant-numeric:tabular-nums; border-radius:var(--dx-radius,0px);
33
32
  border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 35%,transparent); }
34
33
  .dx-chip__dot{ width:7px; height:7px; border-radius:50%; background:var(--dx-ember,#f26c18); }
35
34
  .dx-chip__bal{ font-weight:600; opacity:.85; }
package/dist/index.d.ts CHANGED
@@ -1,33 +1,7 @@
1
- import { C as ConnectVault, D as DexterConnectConfig, a as CeremonyPhase, S as SignInResult, I as IdentityKind } from './signals-BaXobUfB.js';
2
- export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, c as IdentityInput, P as PasskeyLoginTokens, d as PasskeySignalSupport, R as ResolvedIdentity, e as StoredWallet, f as ejectActiveWallet, g as forgetWallet, h as getActiveHandle, i as getCredentialId, l as listWallets, p as passkeySignalSupport, j as prunePasskey, r as renamePasskey, k as resolveIdentity, s as setActiveHandle, m as subscribeWallet, n as switchWallet, o as syncAcceptedPasskeys } from './signals-BaXobUfB.js';
1
+ import { S as SignInResult, C as CreateWalletResult, a as CreateWalletConfig, b as CeremonyPhase, D as DexterConnectConfig, c as ConnectVault, I as IdentityKind } from './signals-qc4rDyQ6.js';
2
+ export { A as ACTIVE_WALLET_STORAGE_KEY, d as ConnectError, e as IdentityInput, P as PasskeyLoginTokens, f as PasskeySignalSupport, R as ResolvedIdentity, g as SESSION_TTL_30D, h as SpendPolicy, i as StoredWallet, j as authoredPolicy, k as createWallet, l as ejectActiveWallet, m as forgetWallet, n as getActiveHandle, o as getCredentialId, p as listWallets, q as passkeySignalSupport, r as prunePasskey, s as renamePasskey, t as resolveIdentity, u as setActiveHandle, v as subscribeWallet, w as switchWallet, x as syncAcceptedPasskeys, y as usdToAtomic } from './signals-qc4rDyQ6.js';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
- interface CreateWalletConfig extends DexterConnectConfig {
6
- /** Label for the passkey in the OS keychain AND the wallet roster. Set at
7
- * creation — the only moment naming is guaranteed to stick. Default "Dexter Wallet". */
8
- name?: string;
9
- /** RP id for the new credential. Default "dexter.cash". */
10
- rpId?: string;
11
- /** Called as the ceremony progresses, for live "connecting steps" UI:
12
- * challenge → passkey → verifying → finalizing. */
13
- onPhase?: (phase: CeremonyPhase) => void;
14
- }
15
- interface CreateWalletResult {
16
- /** Server-minted 16-byte user handle, base64url — the vault identity. */
17
- handle: string;
18
- /** base64url credential id of the new passkey. */
19
- credentialId: string;
20
- /** The freshly initialized vault (swig not yet deployed; deploys lazily). */
21
- vault: ConnectVault;
22
- }
23
- /**
24
- * Mint a brand-new Dexter wallet (passkey + vault) and make it the active wallet.
25
- *
26
- * One passkey approval. Throws ConnectError on any failed leg (the `code` is the
27
- * server's error string, or webauthn_failed / no_credential for the ceremony).
28
- */
29
- declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
30
-
31
5
  /**
32
6
  * "Sign in with Dexter" — the discoverable-credential login ceremony.
33
7
  *
@@ -247,4 +221,4 @@ interface EnableAgentSpendResult {
247
221
  */
248
222
  declare function enableAgentSpend(id: AgentSpendIdentity, vaultPda: string, apiOrigin: string, credentialId?: string | null): Promise<EnableAgentSpendResult>;
249
223
 
250
- export { AgentSpendError, type AgentSpendIdentity, type AgentSpendStatus, type AgentSpendTab, type AnonChallengeResult, type AnonServerPolicy, type AutomaticAgentSpend, CeremonyPhase, ConnectVault, type ContinueResult, type CreateWalletConfig, type CreateWalletResult, DexterConnectConfig, type EnableAgentSpendResult, IdentityKind, type RawAgentSpendSession, type RawAgentSpendStatus, type RevokeAgentSpendResult, SignInResult, assembleAgentSpendStatus, ceremonyPhaseLabel, continueWithDexter, createAnonServerPolicy, createPasskeySigner, createWallet, describeAgentSpendError, enableAgentSpend, passkeyLogin, revokeAgentSpend };
224
+ export { AgentSpendError, type AgentSpendIdentity, type AgentSpendStatus, type AgentSpendTab, type AnonChallengeResult, type AnonServerPolicy, type AutomaticAgentSpend, CeremonyPhase, ConnectVault, type ContinueResult, CreateWalletConfig, CreateWalletResult, DexterConnectConfig, type EnableAgentSpendResult, IdentityKind, type RawAgentSpendSession, type RawAgentSpendStatus, type RevokeAgentSpendResult, SignInResult, assembleAgentSpendStatus, ceremonyPhaseLabel, continueWithDexter, createAnonServerPolicy, createPasskeySigner, describeAgentSpendError, enableAgentSpend, passkeyLogin, revokeAgentSpend };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ACTIVE_WALLET_STORAGE_KEY,
3
3
  ConnectError,
4
+ SESSION_TTL_30D,
5
+ authoredPolicy,
4
6
  base64urlToBase64,
5
7
  bytesToBase64url,
6
8
  ceremonyPhaseLabel,
@@ -21,8 +23,9 @@ import {
21
23
  setActiveHandle,
22
24
  subscribe,
23
25
  switchWallet,
24
- syncAcceptedPasskeys
25
- } from "./chunk-Y22JFT53.js";
26
+ syncAcceptedPasskeys,
27
+ usdToAtomic
28
+ } from "./chunk-2QZ5QLGI.js";
26
29
 
27
30
  // src/agentSpend.ts
28
31
  import { PublicKey } from "@solana/web3.js";
@@ -175,7 +178,9 @@ export {
175
178
  ACTIVE_WALLET_STORAGE_KEY,
176
179
  AgentSpendError,
177
180
  ConnectError,
181
+ SESSION_TTL_30D,
178
182
  assembleAgentSpendStatus,
183
+ authoredPolicy,
179
184
  ceremonyPhaseLabel,
180
185
  continueWithDexter,
181
186
  createAnonServerPolicy,
@@ -197,5 +202,6 @@ export {
197
202
  setActiveHandle,
198
203
  subscribe as subscribeWallet,
199
204
  switchWallet,
200
- syncAcceptedPasskeys
205
+ syncAcceptedPasskeys,
206
+ usdToAtomic
201
207
  };
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
2
- import { a as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, C as ConnectVault, b as ConnectError, e as StoredWallet, d as PasskeySignalSupport, R as ResolvedIdentity } from './signals-BaXobUfB.js';
2
+ import { b as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, c as ConnectVault, d as ConnectError, i as StoredWallet, f as PasskeySignalSupport, R as ResolvedIdentity, C as CreateWalletResult } from './signals-qc4rDyQ6.js';
3
3
  import { ReactElement, ReactNode } from 'react';
4
4
 
5
5
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
@@ -149,8 +149,10 @@ interface DexterWalletMenuProps {
149
149
  avatarInitial?: string;
150
150
  /** "Manage wallet" → the consumer's wallet page. Row hidden if omitted. */
151
151
  onManageWallet?: () => void;
152
- /** "Start fresh" → the consumer ejects/resets (guard + perform on its side). Row hidden if omitted. */
152
+ /** Eject row → the consumer ejects/resets (guard + perform on its side). Row hidden if omitted. */
153
153
  onStartFresh?: () => void;
154
+ /** Label for the eject/reset row. Default "Eject wallet". */
155
+ startFreshLabel?: string;
154
156
  /** Sign-in/save UI revealed when "Save your wallet" is tapped (e.g. <SignInWithDexter/>). Row hidden if omitted. */
155
157
  saveSlot?: ReactNode;
156
158
  /** Short hint shown above the save UI. */
@@ -168,4 +170,34 @@ interface UseIdentityConfig {
168
170
  }
169
171
  declare function useIdentity({ accountToken }: UseIdentityConfig): ResolvedIdentity;
170
172
 
171
- export { type ConnectStatus, DexterButton, type DexterButtonProps, DexterMark, DexterWalletChip, type DexterWalletChipProps, DexterWalletMenu, type DexterWalletMenuProps, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
173
+ interface AllowanceChipsProps {
174
+ /** The authored USD amount as a raw string, or null when nothing is chosen.
175
+ * NONE selected initially → pass null. Preset chips echo '5' / '20' / '50';
176
+ * Custom echoes whatever the user types. */
177
+ value: string | null;
178
+ /** Fired with the raw USD string (or null when Custom is opened empty). */
179
+ onChange: (usd: string | null) => void;
180
+ /** Extra className composed after the brand classes. */
181
+ className?: string;
182
+ }
183
+ /** The consent-at-birth allowance chips. */
184
+ declare function AllowanceChips(props: AllowanceChipsProps): ReactElement;
185
+
186
+ interface CreateWalletPanelProps {
187
+ /** Fired with the minted wallet the moment creation succeeds. */
188
+ onCreated?: (result: CreateWalletResult) => void;
189
+ /** Fired with the typed error if the ceremony fails. */
190
+ onError?: (error: ConnectError) => void;
191
+ /** dexter-api base. Default https://api.dexter.cash (createWallet's default). */
192
+ apiBase?: string;
193
+ /** Where the WebAuthn ceremony runs. Default 'auto' (createWallet's default). */
194
+ transport?: 'auto' | 'popup' | 'inline';
195
+ /** Render the optional "Name your wallet" field. Default true. */
196
+ showName?: boolean;
197
+ /** Extra className composed after the brand classes. */
198
+ className?: string;
199
+ }
200
+ /** The turnkey consent-at-birth create panel. */
201
+ declare function CreateWalletPanel(props: CreateWalletPanelProps): ReactElement;
202
+
203
+ export { AllowanceChips, type AllowanceChipsProps, type ConnectStatus, CreateWalletPanel, type CreateWalletPanelProps, DexterButton, type DexterButtonProps, DexterMark, DexterWalletChip, type DexterWalletChipProps, DexterWalletMenu, type DexterWalletMenuProps, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
package/dist/react.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ConnectError,
3
+ authoredPolicy,
3
4
  ceremonyPhaseLabel,
4
5
  createPasskeySigner,
6
+ createWallet,
5
7
  ejectActiveWallet,
6
8
  getActiveHandle,
7
9
  getCredentialId,
@@ -14,13 +16,13 @@ import {
14
16
  setActiveHandle,
15
17
  subscribe,
16
18
  switchWallet
17
- } from "./chunk-Y22JFT53.js";
19
+ } from "./chunk-2QZ5QLGI.js";
18
20
  import {
19
21
  DexterButton,
20
22
  DexterMark,
21
23
  cx,
22
24
  ensureDexterButtonStyles
23
- } from "./chunk-S7G2ZZOO.js";
25
+ } from "./chunk-2WETR5DJ.js";
24
26
 
25
27
  // src/useSignInWithDexter.ts
26
28
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -233,35 +235,34 @@ import { useEffect as useEffect4 } from "react";
233
235
  var STYLE_ID = "dexter-connect-wallet-kit-styles";
234
236
  var WALLET_KIT_CSS = `
235
237
  .dx-wchip{
236
- --dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
237
238
  display:inline-flex; align-items:center; gap:9px; max-width:240px;
238
239
  padding:6px 12px 6px 7px; font:inherit; font-weight:600; font-size:.74rem;
239
240
  letter-spacing:.1em; text-transform:uppercase; cursor:pointer; background:transparent;
240
- color:inherit; border:1px solid color-mix(in srgb,var(--dx-ember) 42%,transparent);
241
- border-radius:var(--dx-radius); -webkit-tap-highlight-color:transparent;
241
+ color:inherit; border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 42%,transparent);
242
+ border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
242
243
  transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease;
243
244
  }
244
- .dx-wchip:hover{ filter:brightness(1.08); box-shadow:0 8px 18px color-mix(in srgb,var(--dx-ember) 18%,transparent); }
245
- .dx-wchip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 34%,transparent); }
245
+ .dx-wchip:hover{ filter:brightness(1.08); box-shadow:0 8px 18px color-mix(in srgb,var(--dx-ember,#f26c18) 18%,transparent); }
246
+ .dx-wchip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 34%,transparent); }
246
247
  .dx-wchip--signedout{ border-color:color-mix(in srgb,currentColor 22%,transparent); padding-left:12px; }
247
248
  .dx-wchip__avatar{
248
249
  width:22px; height:22px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
249
250
  border-radius:50%; overflow:hidden; font-size:.72rem; font-weight:700; letter-spacing:0; line-height:1;
250
- background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2)); color:var(--dx-fg);
251
+ background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00)); color:var(--dx-fg,#fff4ea);
251
252
  }
252
253
  .dx-wchip__avatar img{ width:100%; height:100%; object-fit:cover; }
253
254
  .dx-wchip__label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
254
255
 
255
- .dx-wmenu{ --dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; display:flex; flex-direction:column; min-width:248px; }
256
+ .dx-wmenu{ display:flex; flex-direction:column; min-width:248px; }
256
257
  .dx-wmenu__id{ display:flex; align-items:center; gap:10px; padding:13px 15px; border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent); }
257
- .dx-wmenu__avatar{ width:32px; height:32px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center; border-radius:50%; font-weight:700; font-size:.85rem; line-height:1; background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2)); color:var(--dx-fg); }
258
+ .dx-wmenu__avatar{ width:32px; height:32px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center; border-radius:50%; font-weight:700; font-size:.85rem; line-height:1; background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00)); color:var(--dx-fg,#fff4ea); }
258
259
  .dx-wmenu__meta{ display:flex; flex-direction:column; gap:2px; min-width:0; }
259
260
  .dx-wmenu__name{ font-weight:700; font-size:.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
260
261
  .dx-wmenu__sub{ font-size:.66rem; letter-spacing:.12em; text-transform:uppercase; opacity:.55; }
261
262
  .dx-wmenu__list{ display:flex; flex-direction:column; padding:6px; }
262
263
  .dx-wmenu__item{ display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; padding:10px 12px; font:inherit; font-size:.82rem; font-weight:600; text-align:left; background:transparent; border:none; color:inherit; cursor:pointer; transition:background .12s ease; }
263
- .dx-wmenu__item:hover{ background:color-mix(in srgb,var(--dx-ember) 10%,transparent); }
264
- .dx-wmenu__item--danger{ color:#e5552e; }
264
+ .dx-wmenu__item:hover{ background:color-mix(in srgb,var(--dx-ember,#f26c18) 10%,transparent); }
265
+ .dx-wmenu__item--danger{ color:var(--dx-danger,#e5552e); }
265
266
  .dx-wmenu__icon{ opacity:.45; font-size:.9rem; }
266
267
  .dx-wmenu__back{ border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent); opacity:.85; }
267
268
  .dx-wmenu__save{ display:flex; flex-direction:column; gap:9px; padding:13px 15px; }
@@ -302,7 +303,16 @@ function DexterWalletChip(props) {
302
303
  import { useEffect as useEffect5, useState as useState3 } from "react";
303
304
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
304
305
  function DexterWalletMenu(props) {
305
- const { walletLabel, avatarInitial, onManageWallet, onStartFresh, saveSlot, saveHint, className } = props;
306
+ const {
307
+ walletLabel,
308
+ avatarInitial,
309
+ onManageWallet,
310
+ onStartFresh,
311
+ startFreshLabel = "Eject wallet",
312
+ saveSlot,
313
+ saveHint,
314
+ className
315
+ } = props;
306
316
  useEffect5(ensureWalletKitStyles, []);
307
317
  const [showSave, setShowSave] = useState3(false);
308
318
  const initial = (avatarInitial ?? walletLabel.trim().charAt(0) ?? "D").toUpperCase();
@@ -347,7 +357,7 @@ function DexterWalletMenu(props) {
347
357
  className: "dx-wmenu__item dx-wmenu__item--danger",
348
358
  onClick: onStartFresh,
349
359
  children: [
350
- /* @__PURE__ */ jsx3("span", { children: "Start fresh" }),
360
+ /* @__PURE__ */ jsx3("span", { children: startFreshLabel }),
351
361
  /* @__PURE__ */ jsx3("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u27F5" })
352
362
  ]
353
363
  }
@@ -368,7 +378,221 @@ function useIdentity({ accountToken }) {
368
378
  [accountToken, activeHandle]
369
379
  );
370
380
  }
381
+
382
+ // src/AllowanceChips.tsx
383
+ import { useEffect as useEffect6, useState as useState4 } from "react";
384
+
385
+ // src/consentStyles.ts
386
+ var STYLE_ID2 = "dexter-connect-consent-styles";
387
+ var CONSENT_CSS = `
388
+ .dx-allow{
389
+ display:flex; flex-wrap:wrap; gap:8px; align-items:center;
390
+ }
391
+ .dx-allow__chip{
392
+ display:inline-flex; align-items:center; justify-content:center; padding:9px 15px;
393
+ font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em; text-transform:uppercase;
394
+ font-variant-numeric:tabular-nums; cursor:pointer; background:transparent; color:inherit;
395
+ border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent);
396
+ border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
397
+ transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease, color .16s ease;
398
+ }
399
+ .dx-allow__chip:hover{ border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 70%,transparent); filter:brightness(1.05); }
400
+ .dx-allow__chip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 34%,transparent); }
401
+ .dx-allow__chip--active{
402
+ background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00));
403
+ color:var(--dx-fg,#fff4ea); border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 55%,transparent);
404
+ box-shadow:0 10px 22px color-mix(in srgb,var(--dx-ember,#f26c18) 22%,transparent);
405
+ }
406
+ .dx-allow__input{
407
+ flex:1 1 130px; min-width:120px; padding:9px 13px; font:inherit; font-size:.9rem;
408
+ font-variant-numeric:tabular-nums; color:inherit; background:transparent;
409
+ border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent);
410
+ border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
411
+ transition:border-color .16s ease, box-shadow .16s ease;
412
+ }
413
+ .dx-allow__input:focus, .dx-allow__input:focus-visible{
414
+ outline:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 70%,transparent);
415
+ box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 30%,transparent);
416
+ }
417
+ .dx-allow__input::placeholder{ color:currentColor; opacity:.5; letter-spacing:.02em; text-transform:none; }
418
+
419
+ .dx-cwp{
420
+ display:flex; flex-direction:column; gap:16px; font:inherit;
421
+ }
422
+ .dx-cwp__field{ display:flex; flex-direction:column; gap:8px; }
423
+ .dx-cwp__label{ font-size:.7rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; opacity:.62; }
424
+ .dx-cwp__name{
425
+ padding:10px 13px; font:inherit; font-size:.95rem; color:inherit; background:transparent;
426
+ border:1px solid color-mix(in srgb,currentColor 22%,transparent); border-radius:var(--dx-radius,0px);
427
+ -webkit-tap-highlight-color:transparent; transition:border-color .16s ease, box-shadow .16s ease;
428
+ }
429
+ .dx-cwp__name:focus, .dx-cwp__name:focus-visible{
430
+ outline:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 60%,transparent);
431
+ box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 26%,transparent);
432
+ }
433
+ .dx-cwp__name::placeholder{ color:currentColor; opacity:.42; }
434
+ .dx-cwp__fine{ font-size:.76rem; line-height:1.5; opacity:.66; }
435
+ .dx-cwp__err{ font-size:.78rem; line-height:1.45; color:var(--dx-danger,#e5552e); }
436
+ `;
437
+ function ensureConsentStyles() {
438
+ if (typeof document === "undefined") return;
439
+ if (document.getElementById(STYLE_ID2)) return;
440
+ const el = document.createElement("style");
441
+ el.id = STYLE_ID2;
442
+ el.textContent = CONSENT_CSS;
443
+ document.head.appendChild(el);
444
+ }
445
+ ensureConsentStyles();
446
+
447
+ // src/AllowanceChips.tsx
448
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
449
+ var PRESETS = [
450
+ { label: "$5", num: "5" },
451
+ { label: "$20", num: "20" },
452
+ { label: "$50", num: "50" }
453
+ ];
454
+ var PRESET_NUMS = PRESETS.map((p) => p.num);
455
+ var isPreset = (v) => v !== null && PRESET_NUMS.includes(v);
456
+ function AllowanceChips(props) {
457
+ const { value, onChange, className } = props;
458
+ useEffect6(ensureConsentStyles, []);
459
+ const [customOpen, setCustomOpen] = useState4(false);
460
+ const customActive = customOpen || value !== null && !isPreset(value);
461
+ const selectPreset = (num) => {
462
+ setCustomOpen(false);
463
+ onChange(num);
464
+ };
465
+ const selectCustom = () => {
466
+ setCustomOpen(true);
467
+ onChange(isPreset(value) ? null : value);
468
+ };
469
+ const onChipKeyDown = (e, select) => {
470
+ if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
471
+ e.preventDefault();
472
+ select();
473
+ }
474
+ };
475
+ return /* @__PURE__ */ jsxs4("div", { className: cx("dx-allow", className), role: "radiogroup", "aria-label": "Monthly agent allowance", children: [
476
+ PRESETS.map(({ label, num }) => {
477
+ const checked = !customActive && value === num;
478
+ return /* @__PURE__ */ jsx4(
479
+ "div",
480
+ {
481
+ role: "radio",
482
+ "aria-checked": checked,
483
+ tabIndex: 0,
484
+ className: cx("dx-allow__chip", checked && "dx-allow__chip--active"),
485
+ onClick: () => selectPreset(num),
486
+ onKeyDown: (e) => onChipKeyDown(e, () => selectPreset(num)),
487
+ children: label
488
+ },
489
+ num
490
+ );
491
+ }),
492
+ /* @__PURE__ */ jsx4(
493
+ "div",
494
+ {
495
+ role: "radio",
496
+ "aria-checked": customActive,
497
+ tabIndex: 0,
498
+ className: cx("dx-allow__chip", customActive && "dx-allow__chip--active"),
499
+ onClick: selectCustom,
500
+ onKeyDown: (e) => onChipKeyDown(e, selectCustom),
501
+ children: "Custom"
502
+ }
503
+ ),
504
+ customActive && /* @__PURE__ */ jsx4(
505
+ "input",
506
+ {
507
+ className: "dx-allow__input",
508
+ inputMode: "decimal",
509
+ placeholder: "$ amount",
510
+ "aria-label": "Custom monthly allowance in USD",
511
+ value: value ?? "",
512
+ autoFocus: true,
513
+ onChange: (e) => onChange(e.target.value)
514
+ }
515
+ )
516
+ ] });
517
+ }
518
+
519
+ // src/CreateWalletPanel.tsx
520
+ import { useEffect as useEffect7, useState as useState5 } from "react";
521
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
522
+ var FINE_PRINT = "Your number, your tap. Agents can never spend past it, and you can revoke any time.";
523
+ function CreateWalletPanel(props) {
524
+ const { onCreated, onError, apiBase, transport, showName = true, className } = props;
525
+ useEffect7(ensureConsentStyles, []);
526
+ useEffect7(ensureDexterButtonStyles, []);
527
+ const [name, setName] = useState5("");
528
+ const [value, setValue] = useState5(null);
529
+ const [running, setRunning] = useState5(false);
530
+ const [phase, setPhase] = useState5(null);
531
+ const [error, setError] = useState5(null);
532
+ const policy = authoredPolicy(value ?? "");
533
+ const handleCreate = async () => {
534
+ if (running) return;
535
+ if (!policy) return;
536
+ setError(null);
537
+ setRunning(true);
538
+ setPhase(null);
539
+ try {
540
+ const result = await createWallet({
541
+ name: name.trim() || "Dexter Wallet",
542
+ spendPolicy: policy,
543
+ apiBase,
544
+ transport,
545
+ onPhase: setPhase
546
+ });
547
+ onCreated?.(result);
548
+ } catch (e) {
549
+ const err = e instanceof ConnectError ? e : new ConnectError("create_failed", e instanceof Error ? e.message : String(e));
550
+ setError(err);
551
+ onError?.(err);
552
+ } finally {
553
+ setRunning(false);
554
+ setPhase(null);
555
+ }
556
+ };
557
+ return /* @__PURE__ */ jsxs5("div", { className: cx("dx-cwp", className), children: [
558
+ showName && /* @__PURE__ */ jsxs5("div", { className: "dx-cwp__field", children: [
559
+ /* @__PURE__ */ jsx5("label", { className: "dx-cwp__label", htmlFor: "dx-cwp-name", children: "Name your wallet" }),
560
+ /* @__PURE__ */ jsx5(
561
+ "input",
562
+ {
563
+ id: "dx-cwp-name",
564
+ className: "dx-cwp__name",
565
+ maxLength: 40,
566
+ placeholder: "Dexter Wallet",
567
+ value: name,
568
+ disabled: running,
569
+ onChange: (e) => setName(e.target.value)
570
+ }
571
+ )
572
+ ] }),
573
+ /* @__PURE__ */ jsxs5("div", { className: "dx-cwp__field", children: [
574
+ /* @__PURE__ */ jsx5("span", { className: "dx-cwp__label", children: "What agents may spend, per 30 days" }),
575
+ /* @__PURE__ */ jsx5(AllowanceChips, { value, onChange: setValue })
576
+ ] }),
577
+ /* @__PURE__ */ jsx5("p", { className: "dx-cwp__fine", children: FINE_PRINT }),
578
+ error && /* @__PURE__ */ jsx5("div", { className: "dx-cwp__err", role: "alert", children: error.message || error.code }),
579
+ /* @__PURE__ */ jsx5(
580
+ DexterButton,
581
+ {
582
+ block: true,
583
+ className: "dx-cwp__cta",
584
+ loading: running,
585
+ loadingLabel: phase ? ceremonyPhaseLabel(phase) : "Creating\u2026",
586
+ disabled: !policy,
587
+ onClick: handleCreate,
588
+ children: error ? "Retry" : "Create your Dexter Wallet"
589
+ }
590
+ )
591
+ ] });
592
+ }
371
593
  export {
594
+ AllowanceChips,
595
+ CreateWalletPanel,
372
596
  DexterButton,
373
597
  DexterMark,
374
598
  DexterWalletChip,
@@ -60,6 +60,55 @@ declare class ConnectError extends Error {
60
60
  constructor(code: string, message?: string);
61
61
  }
62
62
 
63
+ /** Consent-at-birth allowance (Branch rulings 2026-07-02/03).
64
+ * The user authors the number; zero is not consent; TTL is fixed 30d and
65
+ * never user-editable; no caller may invent a default. */
66
+ declare const SESSION_TTL_30D = "2592000";
67
+ interface SpendPolicy {
68
+ /** Role-2 allowance, atomic USDC (6dp), decimal string. User-authored. */
69
+ spendLimitAtomic: string;
70
+ /** Fixed 30d. Present for wire compatibility; always SESSION_TTL_30D. */
71
+ sessionTtlSeconds: string;
72
+ }
73
+ /** Parse user-entered USD ("5", "$20", "1,000", "20.5") to atomic USDC.
74
+ * Null on anything invalid — callers must not invent a fallback. */
75
+ declare function usdToAtomic(input: string): bigint | null;
76
+ /** Null when invalid or zero (zero is not consent). */
77
+ declare function authoredPolicy(usdInput: string): SpendPolicy | null;
78
+
79
+ interface CreateWalletConfig extends DexterConnectConfig {
80
+ /** Label for the passkey in the OS keychain AND the wallet roster. Set at
81
+ * creation — the only moment naming is guaranteed to stick. Default "Dexter Wallet". */
82
+ name?: string;
83
+ /** RP id for the new credential. Default "dexter.cash". */
84
+ rpId?: string;
85
+ /** Consent-at-birth allowance the user authored at creation (chips $5/$20/$50
86
+ * or Custom; zero is not consent; build it with authoredPolicy()). When
87
+ * present it rides the /initialize body so the number becomes the server-side
88
+ * write-once consent record. The TTL is ruled fixed 30d — whatever the object
89
+ * carries, the wire always sends SESSION_TTL_30D. Absent → no policy authored
90
+ * (the vault initializes without one; nothing invents a default). */
91
+ spendPolicy?: SpendPolicy;
92
+ /** Called as the ceremony progresses, for live "connecting steps" UI:
93
+ * challenge → passkey → verifying → finalizing. */
94
+ onPhase?: (phase: CeremonyPhase) => void;
95
+ }
96
+ interface CreateWalletResult {
97
+ /** Server-minted 16-byte user handle, base64url — the vault identity. */
98
+ handle: string;
99
+ /** base64url credential id of the new passkey. */
100
+ credentialId: string;
101
+ /** The freshly initialized vault (swig not yet deployed; deploys lazily). */
102
+ vault: ConnectVault;
103
+ }
104
+ /**
105
+ * Mint a brand-new Dexter wallet (passkey + vault) and make it the active wallet.
106
+ *
107
+ * One passkey approval. Throws ConnectError on any failed leg (the `code` is the
108
+ * server's error string, or webauthn_failed / no_credential for the ceremony).
109
+ */
110
+ declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
111
+
63
112
  type IdentityKind = 'passkey-vault' | 'account' | 'none';
64
113
  interface IdentityInput {
65
114
  /** An account session token (e.g. Supabase access_token) when present, else null. */
@@ -180,4 +229,4 @@ declare function syncAcceptedPasskeys(args: {
180
229
  rpId?: string;
181
230
  }): Promise<boolean>;
182
231
 
183
- export { ACTIVE_WALLET_STORAGE_KEY as A, type ConnectVault as C, type DexterConnectConfig as D, type IdentityKind as I, type PasskeyLoginTokens as P, type ResolvedIdentity as R, type SignInResult as S, type CeremonyPhase as a, ConnectError as b, type IdentityInput as c, type PasskeySignalSupport as d, type StoredWallet as e, ejectActiveWallet as f, forgetWallet as g, getActiveHandle as h, getCredentialId as i, prunePasskey as j, resolveIdentity as k, listWallets as l, subscribe as m, switchWallet as n, syncAcceptedPasskeys as o, passkeySignalSupport as p, renamePasskey as r, setActiveHandle as s };
232
+ export { ACTIVE_WALLET_STORAGE_KEY as A, type CreateWalletResult as C, type DexterConnectConfig as D, type IdentityKind as I, type PasskeyLoginTokens as P, type ResolvedIdentity as R, type SignInResult as S, type CreateWalletConfig as a, type CeremonyPhase as b, type ConnectVault as c, ConnectError as d, type IdentityInput as e, type PasskeySignalSupport as f, SESSION_TTL_30D as g, type SpendPolicy as h, type StoredWallet as i, authoredPolicy as j, createWallet as k, ejectActiveWallet as l, forgetWallet as m, getActiveHandle as n, getCredentialId as o, listWallets as p, passkeySignalSupport as q, prunePasskey as r, renamePasskey as s, resolveIdentity as t, setActiveHandle as u, subscribe as v, switchWallet as w, syncAcceptedPasskeys as x, usdToAtomic as y };
package/dist/worldid.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DexterButton,
3
3
  ensureDexterButtonStyles
4
- } from "./chunk-S7G2ZZOO.js";
4
+ } from "./chunk-2WETR5DJ.js";
5
5
 
6
6
  // src/worldid.tsx
7
7
  import { useEffect, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -47,7 +47,9 @@
47
47
  "@dexterai/vault": "^0.30.0",
48
48
  "@solana/web3.js": "^1.98.4",
49
49
  "@types/react": "^19.1.12",
50
+ "@types/react-dom": "^19.2.3",
50
51
  "@worldcoin/idkit": "^4.1.8",
52
+ "happy-dom": "^15.11.7",
51
53
  "react": "^19.2.5",
52
54
  "tsup": "^8.5.0",
53
55
  "typescript": "^5.6.2",