@dexterai/connect 0.4.0 → 0.6.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/{chunk-2V6EGIHV.js → chunk-LYIBVWRG.js} +2 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.js +130 -1
- package/dist/react.js +38 -10
- package/package.json +1 -1
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
|
-
|
|
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-
|
|
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-
|
|
15
|
+
} from "./chunk-LYIBVWRG.js";
|
|
16
16
|
|
|
17
17
|
// src/useSignInWithDexter.ts
|
|
18
18
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -116,13 +116,31 @@ function useSignInWithDexter(config = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// src/SignInWithDexter.tsx
|
|
119
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
119
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
120
120
|
function shortAddress(addr) {
|
|
121
121
|
return addr.length > 10 ? `${addr.slice(0, 4)}\u2026${addr.slice(-4)}` : addr;
|
|
122
122
|
}
|
|
123
123
|
function formatUsd(n) {
|
|
124
124
|
return n.toLocaleString("en-US", { style: "currency", currency: "USD" });
|
|
125
125
|
}
|
|
126
|
+
function DexterMark() {
|
|
127
|
+
return /* @__PURE__ */ jsxs(
|
|
128
|
+
"svg",
|
|
129
|
+
{
|
|
130
|
+
width: "18",
|
|
131
|
+
height: "18",
|
|
132
|
+
viewBox: "0 0 300 300",
|
|
133
|
+
fill: "currentColor",
|
|
134
|
+
"aria-hidden": "true",
|
|
135
|
+
style: { flexShrink: 0 },
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx("path", { d: "M143.18,22.65c35.41,7.66,68.19,23.6,94.89,48.28,5.22,4.86,11.17,10.45,15.18,16.1,1.38,1.93,1.94,3.6.99,5.23-1.08,1.92-4.22,3.41-6.56,4.17-28.39,9.43-61.55,8.26-88.62-4.69-13.81-7.66-17.02-5.76-31.67-3.48-21.89,2.38-46.67.37-65.06-12.31-6.07-4.99-9.33-12.71-8.8-20.52-.16-9.4,4.25-18.12,11.47-24.06,21.29-17.85,52.64-14.45,78-8.77l.18.04h0Z" }),
|
|
138
|
+
/* @__PURE__ */ jsx("path", { d: "M46.08,129.98c1.06-1.03,3.52-1.07,5.29-1.04,48.98-.05,98.1-.06,146.83-.1,17.53.14,35.01-.31,52.49.18,2.13.18,3.89.74,4.73,2.05,1.46,2.38.35,6.09-1.98,7.6-3.66,2.05-8.62,1.33-12.86,1.74-2.85.12-5.45.13-7.02,2.02-.91,1.07-1.28,2.56-1.56,3.95-.57,3.23-1.16,6.52-1.89,9.62-2.81,12.43-8.68,24.65-19.76,31.56-9.49,5.59-20.42,6.86-31.2,5.75-11.88-1.69-22.15-8.81-29.11-18.28-3.51-4.81-4.92-10.5-5.8-16.29-.47-2.56-.51-5.87-1.35-8-1.16-3.38-6.14-2.59-9.25-1.92-4.21.95-4.39,5.7-5.14,9.19-2.25,11.18-6.84,20.68-16.15,27.65-1.31,1.05-2.91,2.03-2.12,3.66,2.5,3.21,6.65,4.49,10.44,5.97,3.26,1.17,6.86,2.41,7.18,6.06.05,8.18-11.97,3.46-16.32,1.85-3.95-1.55-7.4-4.27-10.42-7.26-3.92-4.28-9.66-4.5-15.16-4.45-3.45-.07-6.99-.19-10.45-.82-21.29-4-31.08-21.3-30.9-42.01-.08-4.63.03-9.32.09-13.91.04-1.69.07-3.46,1.28-4.67l.1-.09h0Z" }),
|
|
139
|
+
/* @__PURE__ */ jsx("path", { d: "M173.06,203.11c9.24-.06,21.6,4.49,22.85,14.84.3,2.12-.67,4.34-2.92,4.73-1.38.29-2.88-.05-4.09-.75-1.64-.9-2.97-2.86-4.19-3.05-1.33-.2-1.99.81-2.93,1.94-10.27,13.34-28.04,20.92-44.83,20.42-15.41-.33-31.89-5.95-43.53-17.34-3.15-3.39-1.55-9.88,3.61-9.19,1.83.32,3.29,1.45,4.76,2.65,10.49,10.12,24.85,14.04,39.12,13.03,10.55-1.23,20.38-5.47,28.74-11.92,1.11-1.06,4.45-3.63,3.5-5.12-.76-.85-4.31-.47-5.92-2.01-2.25-1.92-1.39-6.22,1.16-7.36,1.36-.65,2.96-.81,4.47-.86h.2,0Z" })
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
126
144
|
function SignInWithDexter(props) {
|
|
127
145
|
const {
|
|
128
146
|
onSuccess,
|
|
@@ -160,7 +178,10 @@ function SignInWithDexter(props) {
|
|
|
160
178
|
onClick: handleClick,
|
|
161
179
|
disabled: c.status === "pending",
|
|
162
180
|
style: BUTTON,
|
|
163
|
-
children: c.status === "pending" ? "Signing in\u2026" :
|
|
181
|
+
children: c.status === "pending" ? "Signing in\u2026" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
182
|
+
/* @__PURE__ */ jsx(DexterMark, {}),
|
|
183
|
+
label
|
|
184
|
+
] })
|
|
164
185
|
}
|
|
165
186
|
);
|
|
166
187
|
}
|
|
@@ -168,14 +189,20 @@ var EMBER = "#ef6820";
|
|
|
168
189
|
var BUTTON = {
|
|
169
190
|
display: "inline-flex",
|
|
170
191
|
alignItems: "center",
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
justifyContent: "center",
|
|
193
|
+
gap: 10,
|
|
194
|
+
padding: "10px 22px",
|
|
195
|
+
border: "1px solid rgba(242, 108, 24, 0.55)",
|
|
196
|
+
borderRadius: 0,
|
|
197
|
+
// sharp corners — Dexter brand drops radius on structural elements
|
|
198
|
+
background: "linear-gradient(135deg, rgba(242, 108, 24, 0.95), rgba(186, 58, 0, 0.88))",
|
|
199
|
+
color: "#fff4ea",
|
|
177
200
|
font: "inherit",
|
|
178
201
|
fontWeight: 600,
|
|
202
|
+
fontSize: "0.78rem",
|
|
203
|
+
letterSpacing: "0.12em",
|
|
204
|
+
textTransform: "uppercase",
|
|
205
|
+
boxShadow: "0 16px 28px rgba(242, 108, 24, 0.25)",
|
|
179
206
|
cursor: "pointer"
|
|
180
207
|
};
|
|
181
208
|
var CHIP = {
|
|
@@ -183,7 +210,8 @@ var CHIP = {
|
|
|
183
210
|
alignItems: "center",
|
|
184
211
|
gap: 8,
|
|
185
212
|
padding: "6px 10px",
|
|
186
|
-
borderRadius:
|
|
213
|
+
borderRadius: 0,
|
|
214
|
+
// sharp — match the brand
|
|
187
215
|
border: "1px solid rgba(239,104,32,0.35)",
|
|
188
216
|
font: "inherit",
|
|
189
217
|
fontVariantNumeric: "tabular-nums"
|