@dexterai/connect 0.18.0 → 0.19.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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,33 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ACTIVE_WALLET_STORAGE_KEY,
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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';
|
|
@@ -168,4 +168,34 @@ interface UseIdentityConfig {
|
|
|
168
168
|
}
|
|
169
169
|
declare function useIdentity({ accountToken }: UseIdentityConfig): ResolvedIdentity;
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
interface AllowanceChipsProps {
|
|
172
|
+
/** The authored USD amount as a raw string, or null when nothing is chosen.
|
|
173
|
+
* NONE selected initially → pass null. Preset chips echo '5' / '20' / '50';
|
|
174
|
+
* Custom echoes whatever the user types. */
|
|
175
|
+
value: string | null;
|
|
176
|
+
/** Fired with the raw USD string (or null when Custom is opened empty). */
|
|
177
|
+
onChange: (usd: string | null) => void;
|
|
178
|
+
/** Extra className composed after the brand classes. */
|
|
179
|
+
className?: string;
|
|
180
|
+
}
|
|
181
|
+
/** The consent-at-birth allowance chips. */
|
|
182
|
+
declare function AllowanceChips(props: AllowanceChipsProps): ReactElement;
|
|
183
|
+
|
|
184
|
+
interface CreateWalletPanelProps {
|
|
185
|
+
/** Fired with the minted wallet the moment creation succeeds. */
|
|
186
|
+
onCreated?: (result: CreateWalletResult) => void;
|
|
187
|
+
/** Fired with the typed error if the ceremony fails. */
|
|
188
|
+
onError?: (error: ConnectError) => void;
|
|
189
|
+
/** dexter-api base. Default https://api.dexter.cash (createWallet's default). */
|
|
190
|
+
apiBase?: string;
|
|
191
|
+
/** Where the WebAuthn ceremony runs. Default 'auto' (createWallet's default). */
|
|
192
|
+
transport?: 'auto' | 'popup' | 'inline';
|
|
193
|
+
/** Render the optional "Name your wallet" field. Default true. */
|
|
194
|
+
showName?: boolean;
|
|
195
|
+
/** Extra className composed after the brand classes. */
|
|
196
|
+
className?: string;
|
|
197
|
+
}
|
|
198
|
+
/** The turnkey consent-at-birth create panel. */
|
|
199
|
+
declare function CreateWalletPanel(props: CreateWalletPanelProps): ReactElement;
|
|
200
|
+
|
|
201
|
+
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,7 +16,7 @@ import {
|
|
|
14
16
|
setActiveHandle,
|
|
15
17
|
subscribe,
|
|
16
18
|
switchWallet
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-2QZ5QLGI.js";
|
|
18
20
|
import {
|
|
19
21
|
DexterButton,
|
|
20
22
|
DexterMark,
|
|
@@ -368,7 +370,223 @@ function useIdentity({ accountToken }) {
|
|
|
368
370
|
[accountToken, activeHandle]
|
|
369
371
|
);
|
|
370
372
|
}
|
|
373
|
+
|
|
374
|
+
// src/AllowanceChips.tsx
|
|
375
|
+
import { useEffect as useEffect6, useState as useState4 } from "react";
|
|
376
|
+
|
|
377
|
+
// src/consentStyles.ts
|
|
378
|
+
var STYLE_ID2 = "dexter-connect-consent-styles";
|
|
379
|
+
var CONSENT_CSS = `
|
|
380
|
+
.dx-allow{
|
|
381
|
+
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
382
|
+
display:flex; flex-wrap:wrap; gap:8px; align-items:center;
|
|
383
|
+
}
|
|
384
|
+
.dx-allow__chip{
|
|
385
|
+
display:inline-flex; align-items:center; justify-content:center; padding:9px 15px;
|
|
386
|
+
font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em; text-transform:uppercase;
|
|
387
|
+
font-variant-numeric:tabular-nums; cursor:pointer; background:transparent; color:inherit;
|
|
388
|
+
border:1px solid color-mix(in srgb,var(--dx-ember) 45%,transparent);
|
|
389
|
+
border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
|
|
390
|
+
transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease, color .16s ease;
|
|
391
|
+
}
|
|
392
|
+
.dx-allow__chip:hover{ border-color:color-mix(in srgb,var(--dx-ember) 70%,transparent); filter:brightness(1.05); }
|
|
393
|
+
.dx-allow__chip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 34%,transparent); }
|
|
394
|
+
.dx-allow__chip--active{
|
|
395
|
+
background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00));
|
|
396
|
+
color:var(--dx-fg,#fff4ea); border-color:color-mix(in srgb,var(--dx-ember) 55%,transparent);
|
|
397
|
+
box-shadow:0 10px 22px color-mix(in srgb,var(--dx-ember) 22%,transparent);
|
|
398
|
+
}
|
|
399
|
+
.dx-allow__input{
|
|
400
|
+
flex:1 1 130px; min-width:120px; padding:9px 13px; font:inherit; font-size:.9rem;
|
|
401
|
+
font-variant-numeric:tabular-nums; color:inherit; background:transparent;
|
|
402
|
+
border:1px solid color-mix(in srgb,var(--dx-ember) 45%,transparent);
|
|
403
|
+
border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
|
|
404
|
+
transition:border-color .16s ease, box-shadow .16s ease;
|
|
405
|
+
}
|
|
406
|
+
.dx-allow__input:focus, .dx-allow__input:focus-visible{
|
|
407
|
+
outline:none; border-color:color-mix(in srgb,var(--dx-ember) 70%,transparent);
|
|
408
|
+
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 30%,transparent);
|
|
409
|
+
}
|
|
410
|
+
.dx-allow__input::placeholder{ color:currentColor; opacity:.5; letter-spacing:.02em; text-transform:none; }
|
|
411
|
+
|
|
412
|
+
.dx-cwp{
|
|
413
|
+
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
414
|
+
display:flex; flex-direction:column; gap:16px; font:inherit;
|
|
415
|
+
}
|
|
416
|
+
.dx-cwp__field{ display:flex; flex-direction:column; gap:8px; }
|
|
417
|
+
.dx-cwp__label{ font-size:.7rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; opacity:.62; }
|
|
418
|
+
.dx-cwp__name{
|
|
419
|
+
padding:10px 13px; font:inherit; font-size:.95rem; color:inherit; background:transparent;
|
|
420
|
+
border:1px solid color-mix(in srgb,currentColor 22%,transparent); border-radius:var(--dx-radius,0px);
|
|
421
|
+
-webkit-tap-highlight-color:transparent; transition:border-color .16s ease, box-shadow .16s ease;
|
|
422
|
+
}
|
|
423
|
+
.dx-cwp__name:focus, .dx-cwp__name:focus-visible{
|
|
424
|
+
outline:none; border-color:color-mix(in srgb,var(--dx-ember) 60%,transparent);
|
|
425
|
+
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 26%,transparent);
|
|
426
|
+
}
|
|
427
|
+
.dx-cwp__name::placeholder{ color:currentColor; opacity:.42; }
|
|
428
|
+
.dx-cwp__fine{ font-size:.76rem; line-height:1.5; opacity:.66; }
|
|
429
|
+
.dx-cwp__err{ font-size:.78rem; line-height:1.45; color:#e5552e; }
|
|
430
|
+
`;
|
|
431
|
+
function ensureConsentStyles() {
|
|
432
|
+
if (typeof document === "undefined") return;
|
|
433
|
+
if (document.getElementById(STYLE_ID2)) return;
|
|
434
|
+
const el = document.createElement("style");
|
|
435
|
+
el.id = STYLE_ID2;
|
|
436
|
+
el.textContent = CONSENT_CSS;
|
|
437
|
+
document.head.appendChild(el);
|
|
438
|
+
}
|
|
439
|
+
ensureConsentStyles();
|
|
440
|
+
|
|
441
|
+
// src/AllowanceChips.tsx
|
|
442
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
443
|
+
var PRESETS = [
|
|
444
|
+
{ label: "$5", num: "5" },
|
|
445
|
+
{ label: "$20", num: "20" },
|
|
446
|
+
{ label: "$50", num: "50" }
|
|
447
|
+
];
|
|
448
|
+
var PRESET_NUMS = PRESETS.map((p) => p.num);
|
|
449
|
+
var isPreset = (v) => v !== null && PRESET_NUMS.includes(v);
|
|
450
|
+
function AllowanceChips(props) {
|
|
451
|
+
const { value, onChange, className } = props;
|
|
452
|
+
useEffect6(ensureConsentStyles, []);
|
|
453
|
+
const [customOpen, setCustomOpen] = useState4(false);
|
|
454
|
+
const customActive = customOpen || value !== null && !isPreset(value);
|
|
455
|
+
const selectPreset = (num) => {
|
|
456
|
+
setCustomOpen(false);
|
|
457
|
+
onChange(num);
|
|
458
|
+
};
|
|
459
|
+
const selectCustom = () => {
|
|
460
|
+
setCustomOpen(true);
|
|
461
|
+
onChange(isPreset(value) ? null : value);
|
|
462
|
+
};
|
|
463
|
+
const onChipKeyDown = (e, select) => {
|
|
464
|
+
if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
|
|
465
|
+
e.preventDefault();
|
|
466
|
+
select();
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
return /* @__PURE__ */ jsxs4("div", { className: cx("dx-allow", className), role: "radiogroup", "aria-label": "Monthly agent allowance", children: [
|
|
470
|
+
PRESETS.map(({ label, num }) => {
|
|
471
|
+
const checked = !customActive && value === num;
|
|
472
|
+
return /* @__PURE__ */ jsx4(
|
|
473
|
+
"div",
|
|
474
|
+
{
|
|
475
|
+
role: "radio",
|
|
476
|
+
"aria-checked": checked,
|
|
477
|
+
tabIndex: 0,
|
|
478
|
+
className: cx("dx-allow__chip", checked && "dx-allow__chip--active"),
|
|
479
|
+
onClick: () => selectPreset(num),
|
|
480
|
+
onKeyDown: (e) => onChipKeyDown(e, () => selectPreset(num)),
|
|
481
|
+
children: label
|
|
482
|
+
},
|
|
483
|
+
num
|
|
484
|
+
);
|
|
485
|
+
}),
|
|
486
|
+
/* @__PURE__ */ jsx4(
|
|
487
|
+
"div",
|
|
488
|
+
{
|
|
489
|
+
role: "radio",
|
|
490
|
+
"aria-checked": customActive,
|
|
491
|
+
tabIndex: 0,
|
|
492
|
+
className: cx("dx-allow__chip", customActive && "dx-allow__chip--active"),
|
|
493
|
+
onClick: selectCustom,
|
|
494
|
+
onKeyDown: (e) => onChipKeyDown(e, selectCustom),
|
|
495
|
+
children: "Custom"
|
|
496
|
+
}
|
|
497
|
+
),
|
|
498
|
+
customActive && /* @__PURE__ */ jsx4(
|
|
499
|
+
"input",
|
|
500
|
+
{
|
|
501
|
+
className: "dx-allow__input",
|
|
502
|
+
inputMode: "decimal",
|
|
503
|
+
placeholder: "$ amount",
|
|
504
|
+
"aria-label": "Custom monthly allowance in USD",
|
|
505
|
+
value: value ?? "",
|
|
506
|
+
autoFocus: true,
|
|
507
|
+
onChange: (e) => onChange(e.target.value)
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
] });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/CreateWalletPanel.tsx
|
|
514
|
+
import { useEffect as useEffect7, useState as useState5 } from "react";
|
|
515
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
516
|
+
var FINE_PRINT = "Your number, your tap. Agents can never spend past it, and you can revoke any time.";
|
|
517
|
+
function CreateWalletPanel(props) {
|
|
518
|
+
const { onCreated, onError, apiBase, transport, showName = true, className } = props;
|
|
519
|
+
useEffect7(ensureConsentStyles, []);
|
|
520
|
+
useEffect7(ensureDexterButtonStyles, []);
|
|
521
|
+
const [name, setName] = useState5("");
|
|
522
|
+
const [value, setValue] = useState5(null);
|
|
523
|
+
const [running, setRunning] = useState5(false);
|
|
524
|
+
const [phase, setPhase] = useState5(null);
|
|
525
|
+
const [error, setError] = useState5(null);
|
|
526
|
+
const policy = authoredPolicy(value ?? "");
|
|
527
|
+
const handleCreate = async () => {
|
|
528
|
+
if (running) return;
|
|
529
|
+
if (!policy) return;
|
|
530
|
+
setError(null);
|
|
531
|
+
setRunning(true);
|
|
532
|
+
setPhase(null);
|
|
533
|
+
try {
|
|
534
|
+
const result = await createWallet({
|
|
535
|
+
name: name.trim() || "Dexter Wallet",
|
|
536
|
+
spendPolicy: policy,
|
|
537
|
+
apiBase,
|
|
538
|
+
transport,
|
|
539
|
+
onPhase: setPhase
|
|
540
|
+
});
|
|
541
|
+
onCreated?.(result);
|
|
542
|
+
} catch (e) {
|
|
543
|
+
const err = e instanceof ConnectError ? e : new ConnectError("create_failed", e instanceof Error ? e.message : String(e));
|
|
544
|
+
setError(err);
|
|
545
|
+
onError?.(err);
|
|
546
|
+
} finally {
|
|
547
|
+
setRunning(false);
|
|
548
|
+
setPhase(null);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
return /* @__PURE__ */ jsxs5("div", { className: cx("dx-cwp", className), children: [
|
|
552
|
+
showName && /* @__PURE__ */ jsxs5("div", { className: "dx-cwp__field", children: [
|
|
553
|
+
/* @__PURE__ */ jsx5("label", { className: "dx-cwp__label", htmlFor: "dx-cwp-name", children: "Name your wallet" }),
|
|
554
|
+
/* @__PURE__ */ jsx5(
|
|
555
|
+
"input",
|
|
556
|
+
{
|
|
557
|
+
id: "dx-cwp-name",
|
|
558
|
+
className: "dx-cwp__name",
|
|
559
|
+
maxLength: 40,
|
|
560
|
+
placeholder: "Dexter Wallet",
|
|
561
|
+
value: name,
|
|
562
|
+
disabled: running,
|
|
563
|
+
onChange: (e) => setName(e.target.value)
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
] }),
|
|
567
|
+
/* @__PURE__ */ jsxs5("div", { className: "dx-cwp__field", children: [
|
|
568
|
+
/* @__PURE__ */ jsx5("span", { className: "dx-cwp__label", children: "What agents may spend, per 30 days" }),
|
|
569
|
+
/* @__PURE__ */ jsx5(AllowanceChips, { value, onChange: setValue })
|
|
570
|
+
] }),
|
|
571
|
+
/* @__PURE__ */ jsx5("p", { className: "dx-cwp__fine", children: FINE_PRINT }),
|
|
572
|
+
error && /* @__PURE__ */ jsx5("div", { className: "dx-cwp__err", role: "alert", children: error.message || error.code }),
|
|
573
|
+
/* @__PURE__ */ jsx5(
|
|
574
|
+
DexterButton,
|
|
575
|
+
{
|
|
576
|
+
block: true,
|
|
577
|
+
className: "dx-cwp__cta",
|
|
578
|
+
loading: running,
|
|
579
|
+
loadingLabel: phase ? ceremonyPhaseLabel(phase) : "Creating\u2026",
|
|
580
|
+
disabled: !policy,
|
|
581
|
+
onClick: handleCreate,
|
|
582
|
+
children: error ? "Retry" : "Create your Dexter Wallet"
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
] });
|
|
586
|
+
}
|
|
371
587
|
export {
|
|
588
|
+
AllowanceChips,
|
|
589
|
+
CreateWalletPanel,
|
|
372
590
|
DexterButton,
|
|
373
591
|
DexterMark,
|
|
374
592
|
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
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|