@dexterai/connect 0.15.0 → 0.17.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-S7G2ZZOO.js +107 -0
- package/dist/{chunk-RLVMYLHC.js → chunk-Y22JFT53.js} +6 -0
- package/dist/index.d.ts +134 -3
- package/dist/index.js +156 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +50 -147
- package/dist/{signals-BuP-7inZ.d.ts → signals-BaXobUfB.d.ts} +1 -1
- package/dist/worldid.d.ts +61 -0
- package/dist/worldid.js +108 -0
- package/package.json +19 -3
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/DexterButton.tsx
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var STYLE_ID = "dexter-connect-button-styles";
|
|
5
|
+
var BUTTON_CSS = `
|
|
6
|
+
@keyframes dx-spin { to { transform: rotate(360deg); } }
|
|
7
|
+
@keyframes dx-pulse { 0%,100% { opacity: 1; } 50% { opacity: .6; } }
|
|
8
|
+
.dx-btn{
|
|
9
|
+
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
10
|
+
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;
|
|
15
|
+
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);
|
|
17
|
+
transition:transform .16s ease, box-shadow .16s ease, filter .16s ease, background .16s ease;
|
|
18
|
+
}
|
|
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); }
|
|
22
|
+
.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; }
|
|
25
|
+
.dx-btn--block{ width:100%; }
|
|
26
|
+
.dx-btn__mark{ flex-shrink:0; }
|
|
27
|
+
.dx-btn__spin{ width:15px; height:15px; flex-shrink:0; border-radius:50%;
|
|
28
|
+
border:2px solid color-mix(in srgb,currentColor 30%,transparent); border-top-color:currentColor;
|
|
29
|
+
animation:dx-spin .7s linear infinite; }
|
|
30
|
+
.dx-btn__doing{ animation:dx-pulse 1.4s ease-in-out infinite; }
|
|
31
|
+
.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);
|
|
33
|
+
border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 35%,transparent); }
|
|
34
|
+
.dx-chip__dot{ width:7px; height:7px; border-radius:50%; background:var(--dx-ember,#f26c18); }
|
|
35
|
+
.dx-chip__bal{ font-weight:600; opacity:.85; }
|
|
36
|
+
.dx-chip__x{ margin-left:2px; border:none; background:transparent; color:inherit; cursor:pointer; font-size:16px; line-height:1; opacity:.6; }
|
|
37
|
+
.dx-chip__x:hover{ opacity:1; }
|
|
38
|
+
`;
|
|
39
|
+
function ensureDexterButtonStyles() {
|
|
40
|
+
if (typeof document === "undefined") return;
|
|
41
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
42
|
+
const el = document.createElement("style");
|
|
43
|
+
el.id = STYLE_ID;
|
|
44
|
+
el.textContent = BUTTON_CSS;
|
|
45
|
+
document.head.appendChild(el);
|
|
46
|
+
}
|
|
47
|
+
ensureDexterButtonStyles();
|
|
48
|
+
function cx(...parts) {
|
|
49
|
+
return parts.filter(Boolean).join(" ");
|
|
50
|
+
}
|
|
51
|
+
function DexterMark() {
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"svg",
|
|
54
|
+
{
|
|
55
|
+
className: "dx-btn__mark",
|
|
56
|
+
width: "18",
|
|
57
|
+
height: "18",
|
|
58
|
+
viewBox: "0 0 300 300",
|
|
59
|
+
fill: "currentColor",
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
children: [
|
|
62
|
+
/* @__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" }),
|
|
63
|
+
/* @__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" }),
|
|
64
|
+
/* @__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" })
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
function DexterButton(props) {
|
|
70
|
+
const {
|
|
71
|
+
children,
|
|
72
|
+
loading = false,
|
|
73
|
+
loadingLabel = "Connecting\u2026",
|
|
74
|
+
variant = "primary",
|
|
75
|
+
block = false,
|
|
76
|
+
withMark = true,
|
|
77
|
+
onClick,
|
|
78
|
+
disabled = false,
|
|
79
|
+
className,
|
|
80
|
+
type = "button"
|
|
81
|
+
} = props;
|
|
82
|
+
useEffect(ensureDexterButtonStyles, []);
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
"button",
|
|
85
|
+
{
|
|
86
|
+
type,
|
|
87
|
+
className: cx("dx-btn", variant === "secondary" && "dx-btn--secondary", block && "dx-btn--block", className),
|
|
88
|
+
onClick,
|
|
89
|
+
disabled: disabled || loading,
|
|
90
|
+
"aria-busy": loading,
|
|
91
|
+
children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx("span", { className: "dx-btn__spin", "aria-hidden": true }),
|
|
93
|
+
/* @__PURE__ */ jsx("span", { className: "dx-btn__doing", children: loadingLabel })
|
|
94
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
95
|
+
withMark && /* @__PURE__ */ jsx(DexterMark, {}),
|
|
96
|
+
children
|
|
97
|
+
] })
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
ensureDexterButtonStyles,
|
|
104
|
+
cx,
|
|
105
|
+
DexterMark,
|
|
106
|
+
DexterButton
|
|
107
|
+
};
|
|
@@ -411,6 +411,10 @@ function bytesToBase64(bytes) {
|
|
|
411
411
|
function bytesToBase64url(bytes) {
|
|
412
412
|
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
413
413
|
}
|
|
414
|
+
function base64urlToBase64(s) {
|
|
415
|
+
const t = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
416
|
+
return t + "=".repeat((4 - t.length % 4) % 4);
|
|
417
|
+
}
|
|
414
418
|
function compactSignatureToDer(compact) {
|
|
415
419
|
if (compact.length !== 64) {
|
|
416
420
|
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
@@ -622,6 +626,8 @@ export {
|
|
|
622
626
|
createWallet,
|
|
623
627
|
passkeyLogin,
|
|
624
628
|
continueWithDexter,
|
|
629
|
+
bytesToBase64url,
|
|
630
|
+
base64urlToBase64,
|
|
625
631
|
createAnonServerPolicy,
|
|
626
632
|
createPasskeySigner,
|
|
627
633
|
resolveIdentity,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ConnectVault, D as DexterConnectConfig, a as CeremonyPhase, S as SignInResult } from './signals-
|
|
2
|
-
export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError,
|
|
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';
|
|
3
3
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
4
4
|
|
|
5
5
|
interface CreateWalletConfig extends DexterConnectConfig {
|
|
@@ -116,4 +116,135 @@ declare function createPasskeySigner(vault: ConnectVault, apiBase?: string, opts
|
|
|
116
116
|
*/
|
|
117
117
|
declare function ceremonyPhaseLabel(phase: CeremonyPhase): string;
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/** The automatic role-2 agent-spend rail. */
|
|
120
|
+
interface AutomaticAgentSpend {
|
|
121
|
+
/** true = agent-spend is ON (not revoked). Derived from revokedAt === null. */
|
|
122
|
+
active: boolean;
|
|
123
|
+
/** ISO timestamp the rail was revoked, or null when active. */
|
|
124
|
+
revokedAt: string | null;
|
|
125
|
+
/**
|
|
126
|
+
* On-chain role-2 arm state, decoded from authority.signer by the backend:
|
|
127
|
+
* true = armed (the rail is live and can spend)
|
|
128
|
+
* false = dormant (granted but not yet armed — arms on first pay)
|
|
129
|
+
* null = indeterminate (vault not activated, or a transient chain read failed)
|
|
130
|
+
* NEVER derived from liveSessionCount (that counts the wrong session model).
|
|
131
|
+
*/
|
|
132
|
+
armed: boolean | null;
|
|
133
|
+
spentTodayAtomic?: string;
|
|
134
|
+
dailyCapAtomic?: string;
|
|
135
|
+
perCallCapAtomic?: string;
|
|
136
|
+
lifetimeSpentAtomic?: string;
|
|
137
|
+
}
|
|
138
|
+
/** One explicit user-opened Tab (a V6 per-counterparty session). */
|
|
139
|
+
interface AgentSpendTab {
|
|
140
|
+
/** The session pubkey — the handle a Tab revoke targets. */
|
|
141
|
+
id: string;
|
|
142
|
+
/** The counterparty (agent/app) address this Tab authorizes. */
|
|
143
|
+
counterparty: string;
|
|
144
|
+
/** Display label: the Dexter-verified app name, else a shortened address. */
|
|
145
|
+
label: string;
|
|
146
|
+
/** Whether the Tab is currently live (not expired/spent-out/revoked). */
|
|
147
|
+
live: boolean;
|
|
148
|
+
/** Spent so far against this Tab's cap, atomic USDC (6dp) string. */
|
|
149
|
+
spentAtomic: string;
|
|
150
|
+
/** This Tab's spending cap, atomic USDC (6dp) string. */
|
|
151
|
+
capAtomic: string;
|
|
152
|
+
/** Unix seconds when the Tab expires. */
|
|
153
|
+
expiresAt: number;
|
|
154
|
+
}
|
|
155
|
+
/** The honest two-mode status: one balance, two separately-accounted rails. */
|
|
156
|
+
interface AgentSpendStatus {
|
|
157
|
+
/** Vault USDC balance, atomic (6dp) string — the ONE pool both rails draw. */
|
|
158
|
+
balanceAtomic: string | null;
|
|
159
|
+
/** The automatic role-2 rail. */
|
|
160
|
+
automatic: AutomaticAgentSpend;
|
|
161
|
+
/** The explicit Tabs. */
|
|
162
|
+
tabs: AgentSpendTab[];
|
|
163
|
+
}
|
|
164
|
+
/** The fields of GET /status the two-mode read consumes. */
|
|
165
|
+
interface RawAgentSpendStatus {
|
|
166
|
+
/** ISO timestamp when revoked, null when active. Top-level on /status. */
|
|
167
|
+
agentSpendRevokedAt?: string | null;
|
|
168
|
+
/** On-chain armed read (authority.signer). true/false/null. Top-level on /status. */
|
|
169
|
+
agentSpendArmed?: boolean | null;
|
|
170
|
+
/** On-chain block; usdcAtomic is the vault balance. */
|
|
171
|
+
onchain?: {
|
|
172
|
+
usdcAtomic?: string | null;
|
|
173
|
+
/** Present but DELIBERATELY UNUSED here — counts the wrong session model. */
|
|
174
|
+
liveSessionCount?: number;
|
|
175
|
+
} | null;
|
|
176
|
+
agentSpendDaily?: {
|
|
177
|
+
spentTodayAtomic?: string;
|
|
178
|
+
dailyCapAtomic?: string;
|
|
179
|
+
perCallCapAtomic?: string;
|
|
180
|
+
lifetimeSpentAtomic?: string;
|
|
181
|
+
} | null;
|
|
182
|
+
}
|
|
183
|
+
/** The fields of one GET /sessions row the Tabs rail consumes. */
|
|
184
|
+
interface RawAgentSpendSession {
|
|
185
|
+
sessionPubkey: string;
|
|
186
|
+
counterparty: string;
|
|
187
|
+
appName?: string | null;
|
|
188
|
+
live: boolean;
|
|
189
|
+
spent: string;
|
|
190
|
+
maxAmount: string;
|
|
191
|
+
expiresAt: number;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Assemble the honest two-mode agent-spend status from the raw /status response
|
|
195
|
+
* and the raw /sessions rows. Pure: no fetch, no clock, no I/O.
|
|
196
|
+
*/
|
|
197
|
+
declare function assembleAgentSpendStatus(status: RawAgentSpendStatus, sessions?: RawAgentSpendSession[]): AgentSpendStatus;
|
|
198
|
+
/**
|
|
199
|
+
* The minimal identity the off/on switch needs: WHO is active + the wallet
|
|
200
|
+
* handle the anon router keys on. Structurally satisfied by connect's
|
|
201
|
+
* ResolvedIdentity (pass it straight through), or hand-build `{ kind, userHandle }`.
|
|
202
|
+
*/
|
|
203
|
+
interface AgentSpendIdentity {
|
|
204
|
+
/** Passkey-vault-first identity axis. Agent-spend is Dexter-Wallet-only. */
|
|
205
|
+
kind: IdentityKind;
|
|
206
|
+
/** The passkey-vault user handle the anon router addresses, or null. */
|
|
207
|
+
userHandle: string | null;
|
|
208
|
+
}
|
|
209
|
+
/** Typed error whose `code` is the server's snake_case error string. */
|
|
210
|
+
declare class AgentSpendError extends Error {
|
|
211
|
+
readonly code: string;
|
|
212
|
+
constructor(code: string, message?: string);
|
|
213
|
+
}
|
|
214
|
+
/** Map an AgentSpendError.code to plain, user-facing copy. */
|
|
215
|
+
declare function describeAgentSpendError(code: string): string;
|
|
216
|
+
interface RevokeAgentSpendResult {
|
|
217
|
+
revoked: boolean;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Revoke the AUTOMATIC role-2 agent-spend rail — the off-switch. Takes effect on
|
|
221
|
+
* the very next agent payment (the spend path reads agent_spend_revoked_at fresh
|
|
222
|
+
* per spend). Dexter-Wallet (anon-vault) only; `credentialId` (base64url) targets
|
|
223
|
+
* the biometric prompt.
|
|
224
|
+
*
|
|
225
|
+
* @param id WHO is active — must be the passkey-vault (Dexter Wallet).
|
|
226
|
+
* @param vaultPda The vault PDA, base58 string. Becomes the signed message.
|
|
227
|
+
* @param apiOrigin The dexter-api origin (e.g. https://api.dexter.cash). The
|
|
228
|
+
* caller owns env; connect reads none.
|
|
229
|
+
* @param credentialId The wallet's passkey credential id (base64url), to make
|
|
230
|
+
* the assertion a direct biometric, not an account picker.
|
|
231
|
+
*/
|
|
232
|
+
declare function revokeAgentSpend(id: AgentSpendIdentity, vaultPda: string, apiOrigin: string, credentialId?: string | null): Promise<RevokeAgentSpendResult>;
|
|
233
|
+
interface EnableAgentSpendResult {
|
|
234
|
+
enabled: boolean;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Re-enable the AUTOMATIC role-2 agent-spend rail — the ON switch. Turning spend
|
|
238
|
+
* back ON is the dangerous direction, so it is a two-step, replay-protected nonce
|
|
239
|
+
* flow: fetch a server-minted nonce+expiry (inert until redeemed), sign
|
|
240
|
+
* enableAgentSpendMessage over those EXACT values as the WebAuthn challenge,
|
|
241
|
+
* submit. Dexter-Wallet (anon-vault) only.
|
|
242
|
+
*
|
|
243
|
+
* @param id WHO is active — must be the passkey-vault (Dexter Wallet).
|
|
244
|
+
* @param vaultPda The vault PDA, base58 string.
|
|
245
|
+
* @param apiOrigin The dexter-api origin (e.g. https://api.dexter.cash).
|
|
246
|
+
* @param credentialId The wallet's passkey credential id (base64url).
|
|
247
|
+
*/
|
|
248
|
+
declare function enableAgentSpend(id: AgentSpendIdentity, vaultPda: string, apiOrigin: string, credentialId?: string | null): Promise<EnableAgentSpendResult>;
|
|
249
|
+
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
3
3
|
ConnectError,
|
|
4
|
+
base64urlToBase64,
|
|
5
|
+
bytesToBase64url,
|
|
4
6
|
ceremonyPhaseLabel,
|
|
5
7
|
continueWithDexter,
|
|
6
8
|
createAnonServerPolicy,
|
|
@@ -20,16 +22,168 @@ import {
|
|
|
20
22
|
subscribe,
|
|
21
23
|
switchWallet,
|
|
22
24
|
syncAcceptedPasskeys
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-Y22JFT53.js";
|
|
26
|
+
|
|
27
|
+
// src/agentSpend.ts
|
|
28
|
+
import { PublicKey } from "@solana/web3.js";
|
|
29
|
+
import { startAuthentication } from "@simplewebauthn/browser";
|
|
30
|
+
import { revokeAgentSpendMessage, enableAgentSpendMessage } from "@dexterai/vault/messages";
|
|
31
|
+
import { DEXTER_VAULT_PROGRAM_ID } from "@dexterai/vault/constants";
|
|
32
|
+
function shortCounterparty(a) {
|
|
33
|
+
return a.length > 12 ? `${a.slice(0, 4)}\u2026${a.slice(-4)}` : a;
|
|
34
|
+
}
|
|
35
|
+
function assembleAgentSpendStatus(status, sessions = []) {
|
|
36
|
+
const revokedAt = status.agentSpendRevokedAt ?? null;
|
|
37
|
+
const daily = status.agentSpendDaily ?? null;
|
|
38
|
+
const automatic = {
|
|
39
|
+
active: revokedAt === null,
|
|
40
|
+
revokedAt,
|
|
41
|
+
// THE honest read: the dedicated armed field, never liveSessionCount.
|
|
42
|
+
armed: status.agentSpendArmed ?? null
|
|
43
|
+
};
|
|
44
|
+
if (daily) {
|
|
45
|
+
if (daily.spentTodayAtomic !== void 0) automatic.spentTodayAtomic = daily.spentTodayAtomic;
|
|
46
|
+
if (daily.dailyCapAtomic !== void 0) automatic.dailyCapAtomic = daily.dailyCapAtomic;
|
|
47
|
+
if (daily.perCallCapAtomic !== void 0) automatic.perCallCapAtomic = daily.perCallCapAtomic;
|
|
48
|
+
if (daily.lifetimeSpentAtomic !== void 0) automatic.lifetimeSpentAtomic = daily.lifetimeSpentAtomic;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
balanceAtomic: status.onchain?.usdcAtomic ?? null,
|
|
52
|
+
automatic,
|
|
53
|
+
tabs: sessions.map((s) => ({
|
|
54
|
+
id: s.sessionPubkey,
|
|
55
|
+
counterparty: s.counterparty,
|
|
56
|
+
label: s.appName?.trim() || shortCounterparty(s.counterparty),
|
|
57
|
+
live: s.live,
|
|
58
|
+
spentAtomic: s.spent,
|
|
59
|
+
capAtomic: s.maxAmount,
|
|
60
|
+
expiresAt: s.expiresAt
|
|
61
|
+
}))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
var AgentSpendError = class extends Error {
|
|
65
|
+
code;
|
|
66
|
+
constructor(code, message) {
|
|
67
|
+
super(message ?? code);
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.name = "AgentSpendError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function describeAgentSpendError(code) {
|
|
73
|
+
switch (code) {
|
|
74
|
+
case "verification_failed":
|
|
75
|
+
return "That passkey didn't verify \u2014 try again.";
|
|
76
|
+
case "missing_fields":
|
|
77
|
+
return "The request was incomplete \u2014 try again.";
|
|
78
|
+
case "vault_not_found":
|
|
79
|
+
return "No wallet found for this passkey.";
|
|
80
|
+
case "nonce_not_found":
|
|
81
|
+
case "nonce_already_used":
|
|
82
|
+
return "That confirmation expired \u2014 tap again to retry.";
|
|
83
|
+
case "revoke_failed":
|
|
84
|
+
case "enable_failed":
|
|
85
|
+
return "The server couldn't complete it \u2014 try again shortly.";
|
|
86
|
+
case "not_guest":
|
|
87
|
+
return "This control is only available on a Dexter Wallet.";
|
|
88
|
+
default:
|
|
89
|
+
return code;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function agentSpendError(res) {
|
|
93
|
+
let code = `http_${res.status}`;
|
|
94
|
+
try {
|
|
95
|
+
const body = await res.json();
|
|
96
|
+
if (body?.error) code = String(body.error);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
return new AgentSpendError(code, `agent-spend ${res.status}: ${code}`);
|
|
100
|
+
}
|
|
101
|
+
function assertDexterWallet(id) {
|
|
102
|
+
if (id.kind !== "passkey-vault") {
|
|
103
|
+
throw new AgentSpendError("not_guest", "agent-spend off/on switch is Dexter Wallet only");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function normalizeOrigin(apiOrigin) {
|
|
107
|
+
return apiOrigin.trim().replace(/\/$/, "");
|
|
108
|
+
}
|
|
109
|
+
function rpId() {
|
|
110
|
+
return typeof window !== "undefined" ? window.location.hostname : "dexter.cash";
|
|
111
|
+
}
|
|
112
|
+
async function assertOverMessage(messageBytes, credentialId) {
|
|
113
|
+
const resp = await startAuthentication({
|
|
114
|
+
optionsJSON: {
|
|
115
|
+
challenge: bytesToBase64url(messageBytes),
|
|
116
|
+
rpId: rpId(),
|
|
117
|
+
userVerification: "required",
|
|
118
|
+
...credentialId ? { allowCredentials: [{ id: credentialId, type: "public-key" }] } : {}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
clientDataJSON: base64urlToBase64(resp.response.clientDataJSON),
|
|
123
|
+
authenticatorData: base64urlToBase64(resp.response.authenticatorData),
|
|
124
|
+
signature: base64urlToBase64(resp.response.signature)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function revokeAgentSpend(id, vaultPda, apiOrigin, credentialId) {
|
|
128
|
+
assertDexterWallet(id);
|
|
129
|
+
const origin = normalizeOrigin(apiOrigin);
|
|
130
|
+
const message = revokeAgentSpendMessage({
|
|
131
|
+
programId: DEXTER_VAULT_PROGRAM_ID,
|
|
132
|
+
vaultPda: new PublicKey(vaultPda)
|
|
133
|
+
});
|
|
134
|
+
const signed = await assertOverMessage(message, credentialId);
|
|
135
|
+
const res = await fetch(`${origin}/api/passkey-vault-anon/revoke-agent-spend`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "content-type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ userHandle: id.userHandle, signedPasskeyPayload: signed })
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) throw await agentSpendError(res);
|
|
141
|
+
return await res.json();
|
|
142
|
+
}
|
|
143
|
+
async function enableAgentSpend(id, vaultPda, apiOrigin, credentialId) {
|
|
144
|
+
assertDexterWallet(id);
|
|
145
|
+
const origin = normalizeOrigin(apiOrigin);
|
|
146
|
+
const challengeRes = await fetch(
|
|
147
|
+
`${origin}/api/passkey-vault-anon/enable-agent-spend/challenge`,
|
|
148
|
+
{
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "content-type": "application/json" },
|
|
151
|
+
body: JSON.stringify({ userHandle: id.userHandle })
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
if (!challengeRes.ok) throw await agentSpendError(challengeRes);
|
|
155
|
+
const { nonce, expiry } = await challengeRes.json();
|
|
156
|
+
const message = enableAgentSpendMessage({
|
|
157
|
+
programId: DEXTER_VAULT_PROGRAM_ID,
|
|
158
|
+
vaultPda: new PublicKey(vaultPda),
|
|
159
|
+
nonce: BigInt(nonce),
|
|
160
|
+
expiry: BigInt(expiry)
|
|
161
|
+
});
|
|
162
|
+
const signed = await assertOverMessage(message, credentialId);
|
|
163
|
+
const verifyRes = await fetch(
|
|
164
|
+
`${origin}/api/passkey-vault-anon/enable-agent-spend/verify`,
|
|
165
|
+
{
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "content-type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ userHandle: id.userHandle, nonce, signedPasskeyPayload: signed })
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
if (!verifyRes.ok) throw await agentSpendError(verifyRes);
|
|
172
|
+
return await verifyRes.json();
|
|
173
|
+
}
|
|
24
174
|
export {
|
|
25
175
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
176
|
+
AgentSpendError,
|
|
26
177
|
ConnectError,
|
|
178
|
+
assembleAgentSpendStatus,
|
|
27
179
|
ceremonyPhaseLabel,
|
|
28
180
|
continueWithDexter,
|
|
29
181
|
createAnonServerPolicy,
|
|
30
182
|
createPasskeySigner,
|
|
31
183
|
createWallet,
|
|
184
|
+
describeAgentSpendError,
|
|
32
185
|
ejectActiveWallet,
|
|
186
|
+
enableAgentSpend,
|
|
33
187
|
forgetWallet,
|
|
34
188
|
getActiveHandle,
|
|
35
189
|
getCredentialId,
|
|
@@ -39,6 +193,7 @@ export {
|
|
|
39
193
|
prunePasskey,
|
|
40
194
|
renamePasskey,
|
|
41
195
|
resolveIdentity,
|
|
196
|
+
revokeAgentSpend,
|
|
42
197
|
setActiveHandle,
|
|
43
198
|
subscribe as subscribeWallet,
|
|
44
199
|
switchWallet,
|
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-
|
|
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';
|
|
3
3
|
import { ReactElement, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
|
package/dist/react.js
CHANGED
|
@@ -14,7 +14,13 @@ import {
|
|
|
14
14
|
setActiveHandle,
|
|
15
15
|
subscribe,
|
|
16
16
|
switchWallet
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-Y22JFT53.js";
|
|
18
|
+
import {
|
|
19
|
+
DexterButton,
|
|
20
|
+
DexterMark,
|
|
21
|
+
cx,
|
|
22
|
+
ensureDexterButtonStyles
|
|
23
|
+
} from "./chunk-S7G2ZZOO.js";
|
|
18
24
|
|
|
19
25
|
// src/useSignInWithDexter.ts
|
|
20
26
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -123,111 +129,8 @@ function useSignInWithDexter(config = {}) {
|
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
// src/SignInWithDexter.tsx
|
|
126
|
-
import { useEffect as useEffect3 } from "react";
|
|
127
|
-
|
|
128
|
-
// src/DexterButton.tsx
|
|
129
132
|
import { useEffect as useEffect2 } from "react";
|
|
130
|
-
import {
|
|
131
|
-
var STYLE_ID = "dexter-connect-button-styles";
|
|
132
|
-
var BUTTON_CSS = `
|
|
133
|
-
@keyframes dx-spin { to { transform: rotate(360deg); } }
|
|
134
|
-
@keyframes dx-pulse { 0%,100% { opacity: 1; } 50% { opacity: .6; } }
|
|
135
|
-
.dx-btn{
|
|
136
|
-
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
137
|
-
position:relative; display:inline-flex; align-items:center; justify-content:center; gap:10px;
|
|
138
|
-
padding:11px 22px; border:1px solid color-mix(in srgb,var(--dx-ember) 55%,transparent);
|
|
139
|
-
border-radius:var(--dx-radius);
|
|
140
|
-
background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2));
|
|
141
|
-
color:var(--dx-fg); font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em;
|
|
142
|
-
text-transform:uppercase; cursor:pointer; -webkit-tap-highlight-color:transparent;
|
|
143
|
-
box-shadow:0 14px 26px color-mix(in srgb,var(--dx-ember) 24%,transparent);
|
|
144
|
-
transition:transform .16s ease, box-shadow .16s ease, filter .16s ease, background .16s ease;
|
|
145
|
-
}
|
|
146
|
-
.dx-btn:hover{ transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 20px 34px color-mix(in srgb,var(--dx-ember) 32%,transparent); }
|
|
147
|
-
.dx-btn:active{ transform:translateY(0); filter:brightness(.97); box-shadow:0 8px 16px color-mix(in srgb,var(--dx-ember) 22%,transparent); }
|
|
148
|
-
.dx-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 38%,transparent); }
|
|
149
|
-
.dx-btn:disabled{ cursor:default; filter:saturate(.85) brightness(.98); }
|
|
150
|
-
.dx-btn--secondary{ background:transparent; color:var(--dx-ember); box-shadow:none; border-color:color-mix(in srgb,var(--dx-ember) 45%,transparent); }
|
|
151
|
-
.dx-btn--secondary:hover{ background:color-mix(in srgb,var(--dx-ember) 9%,transparent); filter:none; box-shadow:none; }
|
|
152
|
-
.dx-btn--block{ width:100%; }
|
|
153
|
-
.dx-btn__mark{ flex-shrink:0; }
|
|
154
|
-
.dx-btn__spin{ width:15px; height:15px; flex-shrink:0; border-radius:50%;
|
|
155
|
-
border:2px solid color-mix(in srgb,currentColor 30%,transparent); border-top-color:currentColor;
|
|
156
|
-
animation:dx-spin .7s linear infinite; }
|
|
157
|
-
.dx-btn__doing{ animation:dx-pulse 1.4s ease-in-out infinite; }
|
|
158
|
-
.dx-chip{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; font:inherit;
|
|
159
|
-
font-variant-numeric:tabular-nums; border-radius:var(--dx-radius,0);
|
|
160
|
-
border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 35%,transparent); }
|
|
161
|
-
.dx-chip__dot{ width:7px; height:7px; border-radius:50%; background:var(--dx-ember,#f26c18); }
|
|
162
|
-
.dx-chip__bal{ font-weight:600; opacity:.85; }
|
|
163
|
-
.dx-chip__x{ margin-left:2px; border:none; background:transparent; color:inherit; cursor:pointer; font-size:16px; line-height:1; opacity:.6; }
|
|
164
|
-
.dx-chip__x:hover{ opacity:1; }
|
|
165
|
-
`;
|
|
166
|
-
function ensureDexterButtonStyles() {
|
|
167
|
-
if (typeof document === "undefined") return;
|
|
168
|
-
if (document.getElementById(STYLE_ID)) return;
|
|
169
|
-
const el = document.createElement("style");
|
|
170
|
-
el.id = STYLE_ID;
|
|
171
|
-
el.textContent = BUTTON_CSS;
|
|
172
|
-
document.head.appendChild(el);
|
|
173
|
-
}
|
|
174
|
-
ensureDexterButtonStyles();
|
|
175
|
-
function cx(...parts) {
|
|
176
|
-
return parts.filter(Boolean).join(" ");
|
|
177
|
-
}
|
|
178
|
-
function DexterMark() {
|
|
179
|
-
return /* @__PURE__ */ jsxs(
|
|
180
|
-
"svg",
|
|
181
|
-
{
|
|
182
|
-
className: "dx-btn__mark",
|
|
183
|
-
width: "18",
|
|
184
|
-
height: "18",
|
|
185
|
-
viewBox: "0 0 300 300",
|
|
186
|
-
fill: "currentColor",
|
|
187
|
-
"aria-hidden": "true",
|
|
188
|
-
children: [
|
|
189
|
-
/* @__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" }),
|
|
190
|
-
/* @__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" }),
|
|
191
|
-
/* @__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" })
|
|
192
|
-
]
|
|
193
|
-
}
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
function DexterButton(props) {
|
|
197
|
-
const {
|
|
198
|
-
children,
|
|
199
|
-
loading = false,
|
|
200
|
-
loadingLabel = "Connecting\u2026",
|
|
201
|
-
variant = "primary",
|
|
202
|
-
block = false,
|
|
203
|
-
withMark = true,
|
|
204
|
-
onClick,
|
|
205
|
-
disabled = false,
|
|
206
|
-
className,
|
|
207
|
-
type = "button"
|
|
208
|
-
} = props;
|
|
209
|
-
useEffect2(ensureDexterButtonStyles, []);
|
|
210
|
-
return /* @__PURE__ */ jsx(
|
|
211
|
-
"button",
|
|
212
|
-
{
|
|
213
|
-
type,
|
|
214
|
-
className: cx("dx-btn", variant === "secondary" && "dx-btn--secondary", block && "dx-btn--block", className),
|
|
215
|
-
onClick,
|
|
216
|
-
disabled: disabled || loading,
|
|
217
|
-
"aria-busy": loading,
|
|
218
|
-
children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
219
|
-
/* @__PURE__ */ jsx("span", { className: "dx-btn__spin", "aria-hidden": true }),
|
|
220
|
-
/* @__PURE__ */ jsx("span", { className: "dx-btn__doing", children: loadingLabel })
|
|
221
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
222
|
-
withMark && /* @__PURE__ */ jsx(DexterMark, {}),
|
|
223
|
-
children
|
|
224
|
-
] })
|
|
225
|
-
}
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// src/SignInWithDexter.tsx
|
|
230
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
133
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
231
134
|
function shortAddress(addr) {
|
|
232
135
|
return addr.length > 10 ? `${addr.slice(0, 4)}\u2026${addr.slice(-4)}` : addr;
|
|
233
136
|
}
|
|
@@ -245,7 +148,7 @@ function SignInWithDexter(props) {
|
|
|
245
148
|
showConnectedChip = true,
|
|
246
149
|
...config
|
|
247
150
|
} = props;
|
|
248
|
-
|
|
151
|
+
useEffect2(ensureDexterButtonStyles, []);
|
|
249
152
|
const c = useSignInWithDexter(config);
|
|
250
153
|
const handleClick = async () => {
|
|
251
154
|
try {
|
|
@@ -256,17 +159,17 @@ function SignInWithDexter(props) {
|
|
|
256
159
|
};
|
|
257
160
|
if (c.isVaultConnected) {
|
|
258
161
|
if (!showConnectedChip) return null;
|
|
259
|
-
return /* @__PURE__ */
|
|
260
|
-
/* @__PURE__ */
|
|
261
|
-
/* @__PURE__ */
|
|
262
|
-
c.usdcBalance !== null && /* @__PURE__ */
|
|
162
|
+
return /* @__PURE__ */ jsxs("span", { className: cx("dx-chip", className), children: [
|
|
163
|
+
/* @__PURE__ */ jsx("span", { className: "dx-chip__dot", "aria-hidden": true }),
|
|
164
|
+
/* @__PURE__ */ jsx("span", { children: c.vaultAddress ? shortAddress(c.vaultAddress) : "Connected" }),
|
|
165
|
+
c.usdcBalance !== null && /* @__PURE__ */ jsxs("span", { className: "dx-chip__bal", children: [
|
|
263
166
|
formatUsd(c.usdcBalance),
|
|
264
167
|
" available"
|
|
265
168
|
] }),
|
|
266
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "dx-chip__x", onClick: c.disconnect, "aria-label": "Disconnect", children: "\xD7" })
|
|
267
170
|
] });
|
|
268
171
|
}
|
|
269
|
-
return /* @__PURE__ */
|
|
172
|
+
return /* @__PURE__ */ jsx(
|
|
270
173
|
DexterButton,
|
|
271
174
|
{
|
|
272
175
|
loading: c.status === "pending",
|
|
@@ -281,13 +184,13 @@ function SignInWithDexter(props) {
|
|
|
281
184
|
}
|
|
282
185
|
|
|
283
186
|
// src/useDexterWallet.ts
|
|
284
|
-
import { useCallback as useCallback2, useEffect as
|
|
187
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
285
188
|
var NO_SUPPORT = { rename: false, prune: false, syncAccepted: false };
|
|
286
189
|
function useDexterWallet() {
|
|
287
190
|
const [activeHandle, setHandle] = useState2(() => getActiveHandle());
|
|
288
191
|
const [wallets, setWallets] = useState2(() => listWallets());
|
|
289
192
|
const [support, setSupport] = useState2(NO_SUPPORT);
|
|
290
|
-
|
|
193
|
+
useEffect3(() => {
|
|
291
194
|
const sync = () => {
|
|
292
195
|
setHandle(getActiveHandle());
|
|
293
196
|
setWallets(listWallets());
|
|
@@ -324,10 +227,10 @@ function useDexterWallet() {
|
|
|
324
227
|
}
|
|
325
228
|
|
|
326
229
|
// src/DexterWalletChip.tsx
|
|
327
|
-
import { useEffect as
|
|
230
|
+
import { useEffect as useEffect4 } from "react";
|
|
328
231
|
|
|
329
232
|
// src/walletKitStyles.ts
|
|
330
|
-
var
|
|
233
|
+
var STYLE_ID = "dexter-connect-wallet-kit-styles";
|
|
331
234
|
var WALLET_KIT_CSS = `
|
|
332
235
|
.dx-wchip{
|
|
333
236
|
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
@@ -366,21 +269,21 @@ var WALLET_KIT_CSS = `
|
|
|
366
269
|
`;
|
|
367
270
|
function ensureWalletKitStyles() {
|
|
368
271
|
if (typeof document === "undefined") return;
|
|
369
|
-
if (document.getElementById(
|
|
272
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
370
273
|
const el = document.createElement("style");
|
|
371
|
-
el.id =
|
|
274
|
+
el.id = STYLE_ID;
|
|
372
275
|
el.textContent = WALLET_KIT_CSS;
|
|
373
276
|
document.head.appendChild(el);
|
|
374
277
|
}
|
|
375
278
|
ensureWalletKitStyles();
|
|
376
279
|
|
|
377
280
|
// src/DexterWalletChip.tsx
|
|
378
|
-
import { jsx as
|
|
281
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
379
282
|
function DexterWalletChip(props) {
|
|
380
283
|
const { connected, label, avatarUrl, avatarInitial, onClick, className, ariaExpanded } = props;
|
|
381
|
-
|
|
284
|
+
useEffect4(ensureWalletKitStyles, []);
|
|
382
285
|
const initial = (avatarInitial ?? label.trim().charAt(0) ?? "D").toUpperCase();
|
|
383
|
-
return /* @__PURE__ */
|
|
286
|
+
return /* @__PURE__ */ jsxs2(
|
|
384
287
|
"button",
|
|
385
288
|
{
|
|
386
289
|
type: "button",
|
|
@@ -388,64 +291,64 @@ function DexterWalletChip(props) {
|
|
|
388
291
|
onClick,
|
|
389
292
|
"aria-expanded": ariaExpanded,
|
|
390
293
|
children: [
|
|
391
|
-
connected ? /* @__PURE__ */
|
|
392
|
-
/* @__PURE__ */
|
|
294
|
+
connected ? /* @__PURE__ */ jsx2("span", { className: "dx-wchip__avatar", "aria-hidden": "true", children: avatarUrl ? /* @__PURE__ */ jsx2("img", { src: avatarUrl, alt: "" }) : initial }) : null,
|
|
295
|
+
/* @__PURE__ */ jsx2("span", { className: "dx-wchip__label", children: label })
|
|
393
296
|
]
|
|
394
297
|
}
|
|
395
298
|
);
|
|
396
299
|
}
|
|
397
300
|
|
|
398
301
|
// src/DexterWalletMenu.tsx
|
|
399
|
-
import { useEffect as
|
|
400
|
-
import { jsx as
|
|
302
|
+
import { useEffect as useEffect5, useState as useState3 } from "react";
|
|
303
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
401
304
|
function DexterWalletMenu(props) {
|
|
402
305
|
const { walletLabel, avatarInitial, onManageWallet, onStartFresh, saveSlot, saveHint, className } = props;
|
|
403
|
-
|
|
306
|
+
useEffect5(ensureWalletKitStyles, []);
|
|
404
307
|
const [showSave, setShowSave] = useState3(false);
|
|
405
308
|
const initial = (avatarInitial ?? walletLabel.trim().charAt(0) ?? "D").toUpperCase();
|
|
406
309
|
if (showSave && saveSlot) {
|
|
407
|
-
return /* @__PURE__ */
|
|
408
|
-
/* @__PURE__ */
|
|
310
|
+
return /* @__PURE__ */ jsxs3("div", { className: cx("dx-wmenu", className), children: [
|
|
311
|
+
/* @__PURE__ */ jsx3(
|
|
409
312
|
"button",
|
|
410
313
|
{
|
|
411
314
|
type: "button",
|
|
412
315
|
className: "dx-wmenu__item dx-wmenu__back",
|
|
413
316
|
onClick: () => setShowSave(false),
|
|
414
|
-
children: /* @__PURE__ */
|
|
317
|
+
children: /* @__PURE__ */ jsx3("span", { children: "\u2190 Back to wallet" })
|
|
415
318
|
}
|
|
416
319
|
),
|
|
417
|
-
/* @__PURE__ */
|
|
418
|
-
saveHint ? /* @__PURE__ */
|
|
320
|
+
/* @__PURE__ */ jsxs3("div", { className: "dx-wmenu__save", children: [
|
|
321
|
+
saveHint ? /* @__PURE__ */ jsx3("span", { className: "dx-wmenu__savehint", children: saveHint }) : null,
|
|
419
322
|
saveSlot
|
|
420
323
|
] })
|
|
421
324
|
] });
|
|
422
325
|
}
|
|
423
|
-
return /* @__PURE__ */
|
|
424
|
-
/* @__PURE__ */
|
|
425
|
-
/* @__PURE__ */
|
|
426
|
-
/* @__PURE__ */
|
|
427
|
-
/* @__PURE__ */
|
|
428
|
-
/* @__PURE__ */
|
|
326
|
+
return /* @__PURE__ */ jsxs3("div", { className: cx("dx-wmenu", className), children: [
|
|
327
|
+
/* @__PURE__ */ jsxs3("div", { className: "dx-wmenu__id", children: [
|
|
328
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__avatar", "aria-hidden": "true", children: initial }),
|
|
329
|
+
/* @__PURE__ */ jsxs3("span", { className: "dx-wmenu__meta", children: [
|
|
330
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__name", children: walletLabel }),
|
|
331
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__sub", children: "Your Dexter Wallet" })
|
|
429
332
|
] })
|
|
430
333
|
] }),
|
|
431
|
-
/* @__PURE__ */
|
|
432
|
-
onManageWallet ? /* @__PURE__ */
|
|
433
|
-
/* @__PURE__ */
|
|
434
|
-
/* @__PURE__ */
|
|
334
|
+
/* @__PURE__ */ jsxs3("div", { className: "dx-wmenu__list", children: [
|
|
335
|
+
onManageWallet ? /* @__PURE__ */ jsxs3("button", { type: "button", className: "dx-wmenu__item", onClick: onManageWallet, children: [
|
|
336
|
+
/* @__PURE__ */ jsx3("span", { children: "Manage wallet" }),
|
|
337
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u2197" })
|
|
435
338
|
] }) : null,
|
|
436
|
-
saveSlot ? /* @__PURE__ */
|
|
437
|
-
/* @__PURE__ */
|
|
438
|
-
/* @__PURE__ */
|
|
339
|
+
saveSlot ? /* @__PURE__ */ jsxs3("button", { type: "button", className: "dx-wmenu__item", onClick: () => setShowSave(true), children: [
|
|
340
|
+
/* @__PURE__ */ jsx3("span", { children: "Save your wallet" }),
|
|
341
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u2197" })
|
|
439
342
|
] }) : null,
|
|
440
|
-
onStartFresh ? /* @__PURE__ */
|
|
343
|
+
onStartFresh ? /* @__PURE__ */ jsxs3(
|
|
441
344
|
"button",
|
|
442
345
|
{
|
|
443
346
|
type: "button",
|
|
444
347
|
className: "dx-wmenu__item dx-wmenu__item--danger",
|
|
445
348
|
onClick: onStartFresh,
|
|
446
349
|
children: [
|
|
447
|
-
/* @__PURE__ */
|
|
448
|
-
/* @__PURE__ */
|
|
350
|
+
/* @__PURE__ */ jsx3("span", { children: "Start fresh" }),
|
|
351
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u27F5" })
|
|
449
352
|
]
|
|
450
353
|
}
|
|
451
354
|
) : null
|
|
@@ -180,4 +180,4 @@ declare function syncAcceptedPasskeys(args: {
|
|
|
180
180
|
rpId?: string;
|
|
181
181
|
}): Promise<boolean>;
|
|
182
182
|
|
|
183
|
-
export { ACTIVE_WALLET_STORAGE_KEY as A, type ConnectVault as C, type DexterConnectConfig as D, type
|
|
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 };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { IDKitResult } from '@worldcoin/idkit';
|
|
3
|
+
|
|
4
|
+
type VerifyPersonhoodPhase = 'loading' | 'ready' | 'rp_error' | 'verified';
|
|
5
|
+
interface VerifyPersonhoodConfig {
|
|
6
|
+
/** World ID app id (`app_…`), from the developer portal. */
|
|
7
|
+
appId: `app_${string}`;
|
|
8
|
+
/** The registered action (e.g. `dexter-credit-root-v2`) — pins the nullifier namespace. */
|
|
9
|
+
action: string;
|
|
10
|
+
/** `production` | `staging`. Default `production`. */
|
|
11
|
+
environment?: 'production' | 'staging';
|
|
12
|
+
/**
|
|
13
|
+
* URL that returns the SERVER-SIGNED RP context as `{ rp_context: RpContext }`.
|
|
14
|
+
* The RP signing key is server-side only (never the browser), so the consumer
|
|
15
|
+
* exposes an endpoint that signs the request and returns just the signed
|
|
16
|
+
* context. Mirrors the capture rig's `/api/rp-context`.
|
|
17
|
+
*/
|
|
18
|
+
rpContextUrl: string;
|
|
19
|
+
/** Optional `fetch` init for the RP-context request (auth headers, etc.). */
|
|
20
|
+
rpContextInit?: RequestInit;
|
|
21
|
+
}
|
|
22
|
+
interface UseVerifyPersonhood {
|
|
23
|
+
phase: VerifyPersonhoodPhase;
|
|
24
|
+
error: string | null;
|
|
25
|
+
result: IDKitResult | null;
|
|
26
|
+
/** Open the World App verification sheet. No-op until phase is 'ready'/'verified'. */
|
|
27
|
+
open: () => void;
|
|
28
|
+
/** The IDKitRequestWidget element to render (null until the RP context loads). */
|
|
29
|
+
widget: ReactElement | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Headless hook: manages the RP-context fetch + IDKit lifecycle and hands back
|
|
33
|
+
* the raw proof. Render `widget` and call `open()` from your own UI, or use the
|
|
34
|
+
* turnkey <VerifyPersonhood> below.
|
|
35
|
+
*/
|
|
36
|
+
declare function useVerifyPersonhood(config: VerifyPersonhoodConfig, onProof?: (result: IDKitResult) => void, onError?: (error: Error) => void): UseVerifyPersonhood;
|
|
37
|
+
interface VerifyPersonhoodProps extends VerifyPersonhoodConfig {
|
|
38
|
+
/** Fired with the raw World ID v4 proof the moment verification completes. */
|
|
39
|
+
onProof?: (result: IDKitResult) => void;
|
|
40
|
+
/** Fired with the typed error if RP signing or verification fails. */
|
|
41
|
+
onError?: (error: Error) => void;
|
|
42
|
+
/** Button label when armed. Default "Verify your personhood". */
|
|
43
|
+
label?: string;
|
|
44
|
+
/** Label after a proof is captured. Default "Verify again". */
|
|
45
|
+
verifiedLabel?: string;
|
|
46
|
+
/** 'primary' = filled ember (default), 'secondary' = outline. */
|
|
47
|
+
variant?: 'primary' | 'secondary';
|
|
48
|
+
/** Full-width button. */
|
|
49
|
+
block?: boolean;
|
|
50
|
+
/** Extra className composed after the brand classes. */
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Turnkey "Verify your personhood" element — the personhood sibling of
|
|
55
|
+
* <SignInWithDexter>. Renders the branded DexterButton (loading while the RP
|
|
56
|
+
* context signs server-side) that opens World App; on success hands the raw
|
|
57
|
+
* IDKitResult to `onProof`. Brand voice: no emojis. Themeable via --dx-* vars.
|
|
58
|
+
*/
|
|
59
|
+
declare function VerifyPersonhood(props: VerifyPersonhoodProps): ReactElement;
|
|
60
|
+
|
|
61
|
+
export { type UseVerifyPersonhood, VerifyPersonhood, type VerifyPersonhoodConfig, type VerifyPersonhoodPhase, type VerifyPersonhoodProps, useVerifyPersonhood };
|
package/dist/worldid.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DexterButton,
|
|
3
|
+
ensureDexterButtonStyles
|
|
4
|
+
} from "./chunk-S7G2ZZOO.js";
|
|
5
|
+
|
|
6
|
+
// src/worldid.tsx
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
IDKitRequestWidget,
|
|
10
|
+
proofOfHuman
|
|
11
|
+
} from "@worldcoin/idkit";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
function useVerifyPersonhood(config, onProof, onError) {
|
|
14
|
+
const { appId, action, environment = "production", rpContextUrl, rpContextInit } = config;
|
|
15
|
+
const [phase, setPhase] = useState("loading");
|
|
16
|
+
const [error, setError] = useState(null);
|
|
17
|
+
const [rpContext, setRpContext] = useState(null);
|
|
18
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
19
|
+
const [result, setResult] = useState(null);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
let cancelled = false;
|
|
22
|
+
void (async () => {
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(rpContextUrl, { cache: "no-store", ...rpContextInit });
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
if (!res.ok) throw new Error(data?.message ?? `RP context fetch failed (HTTP ${res.status})`);
|
|
27
|
+
if (cancelled) return;
|
|
28
|
+
setRpContext(data.rp_context);
|
|
29
|
+
setPhase("ready");
|
|
30
|
+
} catch (e) {
|
|
31
|
+
if (cancelled) return;
|
|
32
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
33
|
+
setError(err.message);
|
|
34
|
+
setPhase("rp_error");
|
|
35
|
+
onError?.(err);
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true;
|
|
40
|
+
};
|
|
41
|
+
}, [rpContextUrl]);
|
|
42
|
+
const widget = rpContext ? /* @__PURE__ */ jsx(
|
|
43
|
+
IDKitRequestWidget,
|
|
44
|
+
{
|
|
45
|
+
app_id: appId,
|
|
46
|
+
action,
|
|
47
|
+
rp_context: rpContext,
|
|
48
|
+
preset: proofOfHuman(),
|
|
49
|
+
allow_legacy_proofs: false,
|
|
50
|
+
environment,
|
|
51
|
+
open: isOpen,
|
|
52
|
+
onOpenChange: setIsOpen,
|
|
53
|
+
onSuccess: (res) => {
|
|
54
|
+
setResult(res);
|
|
55
|
+
setPhase("verified");
|
|
56
|
+
onProof?.(res);
|
|
57
|
+
},
|
|
58
|
+
onError: (code) => {
|
|
59
|
+
const err = new Error(`World App error: ${code}`);
|
|
60
|
+
setError(err.message);
|
|
61
|
+
onError?.(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
) : null;
|
|
65
|
+
return {
|
|
66
|
+
phase,
|
|
67
|
+
error,
|
|
68
|
+
result,
|
|
69
|
+
open: () => {
|
|
70
|
+
if (phase === "ready" || phase === "verified") setIsOpen(true);
|
|
71
|
+
},
|
|
72
|
+
widget
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function VerifyPersonhood(props) {
|
|
76
|
+
const {
|
|
77
|
+
onProof,
|
|
78
|
+
onError,
|
|
79
|
+
label = "Verify your personhood",
|
|
80
|
+
verifiedLabel = "Verify again",
|
|
81
|
+
variant = "primary",
|
|
82
|
+
block = false,
|
|
83
|
+
className,
|
|
84
|
+
...config
|
|
85
|
+
} = props;
|
|
86
|
+
useEffect(ensureDexterButtonStyles, []);
|
|
87
|
+
const v = useVerifyPersonhood(config, onProof, onError);
|
|
88
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
DexterButton,
|
|
91
|
+
{
|
|
92
|
+
onClick: v.open,
|
|
93
|
+
loading: v.phase === "loading",
|
|
94
|
+
loadingLabel: "Preparing\u2026",
|
|
95
|
+
variant,
|
|
96
|
+
block,
|
|
97
|
+
className,
|
|
98
|
+
disabled: v.phase === "rp_error",
|
|
99
|
+
children: v.phase === "verified" ? verifiedLabel : label
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
v.widget
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
VerifyPersonhood,
|
|
107
|
+
useVerifyPersonhood
|
|
108
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"./react": {
|
|
12
12
|
"types": "./dist/react.d.ts",
|
|
13
13
|
"import": "./dist/react.js"
|
|
14
|
+
},
|
|
15
|
+
"./worldid": {
|
|
16
|
+
"types": "./dist/worldid.d.ts",
|
|
17
|
+
"import": "./dist/worldid.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
@@ -22,12 +26,24 @@
|
|
|
22
26
|
"typecheck": "tsc --noEmit"
|
|
23
27
|
},
|
|
24
28
|
"peerDependencies": {
|
|
25
|
-
"@dexterai/vault": ">=0.
|
|
29
|
+
"@dexterai/vault": ">=0.22",
|
|
30
|
+
"@solana/web3.js": "^1.98.0",
|
|
31
|
+
"@worldcoin/idkit": "^4.1.8",
|
|
26
32
|
"react": ">=18"
|
|
27
33
|
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@solana/web3.js": {
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"@worldcoin/idkit": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
28
42
|
"devDependencies": {
|
|
29
|
-
"@dexterai/vault": "^0.
|
|
43
|
+
"@dexterai/vault": "^0.24.0",
|
|
44
|
+
"@solana/web3.js": "^1.98.4",
|
|
30
45
|
"@types/react": "^19.1.12",
|
|
46
|
+
"@worldcoin/idkit": "^4.1.8",
|
|
31
47
|
"react": "^19.2.5",
|
|
32
48
|
"tsup": "^8.5.0",
|
|
33
49
|
"typescript": "^5.6.2",
|