@dexterai/connect 0.4.0 → 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.
@@ -406,6 +406,8 @@ async function syncAcceptedPasskeys(args) {
406
406
 
407
407
  export {
408
408
  ConnectError,
409
+ base64urlToBytes,
410
+ bytesToBase64url,
409
411
  passkeyLogin,
410
412
  createAnonServerPolicy,
411
413
  createPasskeySigner,
package/dist/index.d.ts CHANGED
@@ -76,4 +76,27 @@ declare function createPasskeySigner(vault: ConnectVault, apiBase?: string, opts
76
76
  __assertion?: AssertionLike;
77
77
  }): DexterApiBrowserPasskeySigner;
78
78
 
79
- export { type AnonChallengeResult, type AnonServerPolicy, ConnectVault, DexterConnectConfig, SignInResult, createAnonServerPolicy, createPasskeySigner, passkeyLogin };
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
+ }
86
+ interface CreateWalletResult {
87
+ /** Server-minted 16-byte user handle, base64url — the vault identity. */
88
+ handle: string;
89
+ /** base64url credential id of the new passkey. */
90
+ credentialId: string;
91
+ /** The freshly initialized vault (swig not yet deployed; deploys lazily). */
92
+ vault: ConnectVault;
93
+ }
94
+ /**
95
+ * Mint a brand-new Dexter wallet (passkey + vault) and make it the active wallet.
96
+ *
97
+ * One passkey approval. Throws ConnectError on any failed leg (the `code` is the
98
+ * server's error string, or webauthn_failed / no_credential for the ceremony).
99
+ */
100
+ declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
101
+
102
+ export { type AnonChallengeResult, type AnonServerPolicy, ConnectVault, type CreateWalletConfig, type CreateWalletResult, DexterConnectConfig, SignInResult, createAnonServerPolicy, createPasskeySigner, createWallet, passkeyLogin };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ACTIVE_WALLET_STORAGE_KEY,
3
3
  ConnectError,
4
+ base64urlToBytes,
5
+ bytesToBase64url,
4
6
  createAnonServerPolicy,
5
7
  createPasskeySigner,
6
8
  ejectActiveWallet,
@@ -16,12 +18,139 @@ import {
16
18
  subscribe,
17
19
  switchWallet,
18
20
  syncAcceptedPasskeys
19
- } from "./chunk-2V6EGIHV.js";
21
+ } from "./chunk-LYIBVWRG.js";
22
+
23
+ // src/enroll.ts
24
+ var DEFAULT_API_BASE = "https://api.dexter.cash";
25
+ var DEFAULT_RP_ID = "dexter.cash";
26
+ var DEFAULT_WALLET_NAME = "Dexter Wallet";
27
+ async function createWallet(config = {}) {
28
+ if (typeof navigator === "undefined" || !navigator.credentials) {
29
+ throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
30
+ }
31
+ const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
32
+ const rpId = config.rpId ?? DEFAULT_RP_ID;
33
+ const name = config.name && config.name.trim() || DEFAULT_WALLET_NAME;
34
+ const options = await fetchEnrollChallenge(apiBase);
35
+ const credential = await createCredential(options, name, rpId);
36
+ const enrolled = await submitEnrollComplete(apiBase, credential);
37
+ const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
38
+ setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
39
+ return {
40
+ handle: enrolled.userHandle,
41
+ credentialId: enrolled.credentialId,
42
+ vault: {
43
+ vaultPda: init.vaultPda,
44
+ swigAddress: init.swigStateAddress,
45
+ // FAIL SAFE: never invent a receive address — null until the server returns
46
+ // one (depositing to the config PDA would strand funds).
47
+ receiveAddress: init.receiveAddress ?? null,
48
+ usdcAta: null,
49
+ // swig not deployed yet (counterfactual pattern)
50
+ publicKey: enrolled.publicKey,
51
+ userHandle: enrolled.userHandle,
52
+ credentialId: enrolled.credentialId
53
+ }
54
+ };
55
+ }
56
+ async function fetchEnrollChallenge(apiBase) {
57
+ const res = await fetch(`${apiBase}/api/passkey-anon/enroll/challenge`, {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json" },
60
+ body: "{}"
61
+ });
62
+ if (!res.ok) throw new ConnectError("enroll_challenge_failed", `enroll/challenge ${res.status}`);
63
+ const data = await res.json();
64
+ if (!data?.options?.challenge) {
65
+ throw new ConnectError("enroll_challenge_malformed", "no creation options in response");
66
+ }
67
+ return data.options;
68
+ }
69
+ async function createCredential(options, name, rpId) {
70
+ let credential;
71
+ try {
72
+ credential = await navigator.credentials.create({
73
+ publicKey: buildCreationOptions(options, name, rpId)
74
+ });
75
+ } catch (err) {
76
+ throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
77
+ }
78
+ if (!credential || credential.type !== "public-key") {
79
+ throw new ConnectError("no_credential", "authenticator returned no credential");
80
+ }
81
+ return credential;
82
+ }
83
+ async function submitEnrollComplete(apiBase, credential) {
84
+ const attestation = credential.response;
85
+ const credentialJson = {
86
+ id: credential.id,
87
+ rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
88
+ type: credential.type,
89
+ response: {
90
+ attestationObject: bytesToBase64url(new Uint8Array(attestation.attestationObject)),
91
+ clientDataJSON: bytesToBase64url(new Uint8Array(attestation.clientDataJSON)),
92
+ transports: typeof attestation.getTransports === "function" ? attestation.getTransports() : []
93
+ },
94
+ clientExtensionResults: credential.getClientExtensionResults?.() ?? {},
95
+ authenticatorAttachment: credential.authenticatorAttachment ?? null
96
+ };
97
+ const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
98
+ method: "POST",
99
+ headers: { "content-type": "application/json" },
100
+ body: JSON.stringify({ credential: credentialJson })
101
+ });
102
+ if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
103
+ return await res.json();
104
+ }
105
+ async function initializeVault(apiBase, userHandle, credentialId) {
106
+ const res = await fetch(`${apiBase}/api/passkey-vault-anon/initialize`, {
107
+ method: "POST",
108
+ headers: { "content-type": "application/json" },
109
+ body: JSON.stringify({ userHandle, credentialId, coolingOffSeconds: 0 })
110
+ });
111
+ if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
112
+ return await res.json();
113
+ }
114
+ function toBuf(b64url) {
115
+ return base64urlToBytes(b64url).buffer.slice(0);
116
+ }
117
+ function buildCreationOptions(o, name, rpId) {
118
+ return {
119
+ // rp.name = the site shown in the keychain; user.name/displayName = the
120
+ // wallet label the user sees. We override the server's user.name (a raw,
121
+ // unreadable handle) with the chosen wallet name.
122
+ rp: { id: o.rp.id ?? rpId, name: "Dexter" },
123
+ user: {
124
+ id: toBuf(o.user.id),
125
+ name,
126
+ displayName: name
127
+ },
128
+ challenge: toBuf(o.challenge),
129
+ pubKeyCredParams: o.pubKeyCredParams,
130
+ timeout: o.timeout,
131
+ excludeCredentials: o.excludeCredentials?.map((c) => ({
132
+ id: toBuf(c.id),
133
+ type: c.type,
134
+ transports: c.transports
135
+ })),
136
+ authenticatorSelection: o.authenticatorSelection,
137
+ attestation: o.attestation
138
+ };
139
+ }
140
+ async function readErrorCode(res) {
141
+ try {
142
+ const body = await res.json();
143
+ if (body?.error) return body.error;
144
+ } catch {
145
+ }
146
+ return `http_${res.status}`;
147
+ }
20
148
  export {
21
149
  ACTIVE_WALLET_STORAGE_KEY,
22
150
  ConnectError,
23
151
  createAnonServerPolicy,
24
152
  createPasskeySigner,
153
+ createWallet,
25
154
  ejectActiveWallet,
26
155
  forgetWallet,
27
156
  getActiveHandle,
package/dist/react.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  setActiveHandle,
13
13
  subscribe,
14
14
  switchWallet
15
- } from "./chunk-2V6EGIHV.js";
15
+ } from "./chunk-LYIBVWRG.js";
16
16
 
17
17
  // src/useSignInWithDexter.ts
18
18
  import { useCallback, useEffect, useMemo, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {