@dexterai/connect 0.14.0 → 0.16.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-IUZBHWTP.js → chunk-Y22JFT53.js} +87 -148
- package/dist/index.d.ts +134 -3
- package/dist/index.js +156 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{signals-BuP-7inZ.d.ts → signals-BaXobUfB.d.ts} +1 -1
- package/package.json +13 -3
|
@@ -124,57 +124,8 @@ function onStorageEvent(e) {
|
|
|
124
124
|
}
|
|
125
125
|
var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
|
|
126
126
|
|
|
127
|
-
// src/
|
|
128
|
-
|
|
129
|
-
const bin = atob(s);
|
|
130
|
-
const out = new Uint8Array(bin.length);
|
|
131
|
-
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
function base64urlToBytes(s) {
|
|
135
|
-
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
136
|
-
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
137
|
-
return base64ToBytes(b64);
|
|
138
|
-
}
|
|
139
|
-
function bytesToBase64(bytes) {
|
|
140
|
-
let bin = "";
|
|
141
|
-
for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
|
|
142
|
-
return btoa(bin);
|
|
143
|
-
}
|
|
144
|
-
function bytesToBase64url(bytes) {
|
|
145
|
-
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
146
|
-
}
|
|
147
|
-
function compactSignatureToDer(compact) {
|
|
148
|
-
if (compact.length !== 64) {
|
|
149
|
-
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
150
|
-
}
|
|
151
|
-
const r = derInteger(compact.subarray(0, 32));
|
|
152
|
-
const s = derInteger(compact.subarray(32, 64));
|
|
153
|
-
const body = new Uint8Array(r.length + s.length);
|
|
154
|
-
body.set(r, 0);
|
|
155
|
-
body.set(s, r.length);
|
|
156
|
-
const out = new Uint8Array(2 + body.length);
|
|
157
|
-
out[0] = 48;
|
|
158
|
-
out[1] = body.length;
|
|
159
|
-
out.set(body, 2);
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
|
-
function derInteger(component) {
|
|
163
|
-
let start = 0;
|
|
164
|
-
while (start < component.length - 1 && component[start] === 0) start += 1;
|
|
165
|
-
let content = component.subarray(start);
|
|
166
|
-
if (content[0] & 128) {
|
|
167
|
-
const padded = new Uint8Array(content.length + 1);
|
|
168
|
-
padded[0] = 0;
|
|
169
|
-
padded.set(content, 1);
|
|
170
|
-
content = padded;
|
|
171
|
-
}
|
|
172
|
-
const out = new Uint8Array(2 + content.length);
|
|
173
|
-
out[0] = 2;
|
|
174
|
-
out[1] = content.length;
|
|
175
|
-
out.set(content, 2);
|
|
176
|
-
return out;
|
|
177
|
-
}
|
|
127
|
+
// src/enroll.ts
|
|
128
|
+
import { startRegistration } from "@simplewebauthn/browser";
|
|
178
129
|
|
|
179
130
|
// src/popup.ts
|
|
180
131
|
var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
|
|
@@ -282,9 +233,19 @@ async function createWallet(config = {}) {
|
|
|
282
233
|
config.onPhase?.("challenge");
|
|
283
234
|
const options = await fetchEnrollChallenge(apiBase);
|
|
284
235
|
config.onPhase?.("passkey");
|
|
285
|
-
const
|
|
236
|
+
const optionsJSON = {
|
|
237
|
+
...options,
|
|
238
|
+
rp: { ...options.rp, id: options.rp.id ?? rpId, name: "Dexter" },
|
|
239
|
+
user: { ...options.user, name, displayName: name }
|
|
240
|
+
};
|
|
241
|
+
let regResponse;
|
|
242
|
+
try {
|
|
243
|
+
regResponse = await startRegistration({ optionsJSON });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
246
|
+
}
|
|
286
247
|
config.onPhase?.("verifying");
|
|
287
|
-
const enrolled = await submitEnrollComplete(apiBase,
|
|
248
|
+
const enrolled = await submitEnrollComplete(apiBase, regResponse);
|
|
288
249
|
config.onPhase?.("finalizing");
|
|
289
250
|
const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
|
|
290
251
|
setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
|
|
@@ -318,38 +279,11 @@ async function fetchEnrollChallenge(apiBase) {
|
|
|
318
279
|
}
|
|
319
280
|
return data.options;
|
|
320
281
|
}
|
|
321
|
-
async function
|
|
322
|
-
let credential;
|
|
323
|
-
try {
|
|
324
|
-
credential = await navigator.credentials.create({
|
|
325
|
-
publicKey: buildCreationOptions(options, name, rpId)
|
|
326
|
-
});
|
|
327
|
-
} catch (err) {
|
|
328
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
329
|
-
}
|
|
330
|
-
if (!credential || credential.type !== "public-key") {
|
|
331
|
-
throw new ConnectError("no_credential", "authenticator returned no credential");
|
|
332
|
-
}
|
|
333
|
-
return credential;
|
|
334
|
-
}
|
|
335
|
-
async function submitEnrollComplete(apiBase, credential) {
|
|
336
|
-
const attestation = credential.response;
|
|
337
|
-
const credentialJson = {
|
|
338
|
-
id: credential.id,
|
|
339
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
340
|
-
type: credential.type,
|
|
341
|
-
response: {
|
|
342
|
-
attestationObject: bytesToBase64url(new Uint8Array(attestation.attestationObject)),
|
|
343
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(attestation.clientDataJSON)),
|
|
344
|
-
transports: typeof attestation.getTransports === "function" ? attestation.getTransports() : []
|
|
345
|
-
},
|
|
346
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {},
|
|
347
|
-
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
348
|
-
};
|
|
282
|
+
async function submitEnrollComplete(apiBase, response) {
|
|
349
283
|
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
|
|
350
284
|
method: "POST",
|
|
351
285
|
headers: { "content-type": "application/json" },
|
|
352
|
-
body: JSON.stringify({ credential:
|
|
286
|
+
body: JSON.stringify({ credential: response })
|
|
353
287
|
});
|
|
354
288
|
if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
|
|
355
289
|
return await res.json();
|
|
@@ -363,32 +297,6 @@ async function initializeVault(apiBase, userHandle, credentialId) {
|
|
|
363
297
|
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
364
298
|
return await res.json();
|
|
365
299
|
}
|
|
366
|
-
function toBuf(b64url) {
|
|
367
|
-
return base64urlToBytes(b64url).buffer.slice(0);
|
|
368
|
-
}
|
|
369
|
-
function buildCreationOptions(o, name, rpId) {
|
|
370
|
-
return {
|
|
371
|
-
// rp.name = the site shown in the keychain; user.name/displayName = the
|
|
372
|
-
// wallet label the user sees. We override the server's user.name (a raw,
|
|
373
|
-
// unreadable handle) with the chosen wallet name.
|
|
374
|
-
rp: { id: o.rp.id ?? rpId, name: "Dexter" },
|
|
375
|
-
user: {
|
|
376
|
-
id: toBuf(o.user.id),
|
|
377
|
-
name,
|
|
378
|
-
displayName: name
|
|
379
|
-
},
|
|
380
|
-
challenge: toBuf(o.challenge),
|
|
381
|
-
pubKeyCredParams: o.pubKeyCredParams,
|
|
382
|
-
timeout: o.timeout,
|
|
383
|
-
excludeCredentials: o.excludeCredentials?.map((c) => ({
|
|
384
|
-
id: toBuf(c.id),
|
|
385
|
-
type: c.type,
|
|
386
|
-
transports: c.transports
|
|
387
|
-
})),
|
|
388
|
-
authenticatorSelection: o.authenticatorSelection,
|
|
389
|
-
attestation: o.attestation
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
300
|
async function readErrorCode(res) {
|
|
393
301
|
try {
|
|
394
302
|
const body = await res.json();
|
|
@@ -399,6 +307,7 @@ async function readErrorCode(res) {
|
|
|
399
307
|
}
|
|
400
308
|
|
|
401
309
|
// src/relay.ts
|
|
310
|
+
import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
|
|
402
311
|
var DEFAULT_API_BASE2 = "https://api.dexter.cash";
|
|
403
312
|
var ANON_SIGN_BASE = "/api/passkey-anon/sign";
|
|
404
313
|
async function passkeyLogin(config = {}, onPhase) {
|
|
@@ -408,13 +317,21 @@ async function passkeyLogin(config = {}, onPhase) {
|
|
|
408
317
|
apiBase: config.apiBase
|
|
409
318
|
});
|
|
410
319
|
}
|
|
320
|
+
if (!browserSupportsWebAuthn()) {
|
|
321
|
+
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
322
|
+
}
|
|
411
323
|
const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
|
|
412
324
|
onPhase?.("challenge");
|
|
413
325
|
const options = await fetchLoginChallenge(apiBase);
|
|
414
326
|
onPhase?.("passkey");
|
|
415
|
-
|
|
327
|
+
let response;
|
|
328
|
+
try {
|
|
329
|
+
response = await startAuthentication({ optionsJSON: options });
|
|
330
|
+
} catch (err) {
|
|
331
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
332
|
+
}
|
|
416
333
|
onPhase?.("verifying");
|
|
417
|
-
return submitLogin(apiBase,
|
|
334
|
+
return submitLogin(apiBase, response);
|
|
418
335
|
}
|
|
419
336
|
async function continueWithDexter(config = {}, onPhase) {
|
|
420
337
|
if (shouldUsePopup(config.transport)) {
|
|
@@ -446,47 +363,11 @@ async function fetchLoginChallenge(apiBase) {
|
|
|
446
363
|
}
|
|
447
364
|
return data.options;
|
|
448
365
|
}
|
|
449
|
-
async function
|
|
450
|
-
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
451
|
-
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
452
|
-
}
|
|
453
|
-
let credential;
|
|
454
|
-
try {
|
|
455
|
-
credential = await navigator.credentials.get({
|
|
456
|
-
publicKey: {
|
|
457
|
-
challenge: base64urlToBytes(options.challenge).buffer.slice(0),
|
|
458
|
-
rpId: options.rpId,
|
|
459
|
-
timeout: options.timeout ?? 6e4,
|
|
460
|
-
userVerification: options.userVerification ?? "required"
|
|
461
|
-
// No allowCredentials — discoverable resident-key login.
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
} catch (err) {
|
|
465
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
466
|
-
}
|
|
467
|
-
if (!credential || credential.type !== "public-key") {
|
|
468
|
-
throw new ConnectError("no_credential", "WebAuthn returned no credential");
|
|
469
|
-
}
|
|
470
|
-
return credential;
|
|
471
|
-
}
|
|
472
|
-
async function submitLogin(apiBase, credential) {
|
|
473
|
-
const assertion = credential.response;
|
|
474
|
-
const credentialJson = {
|
|
475
|
-
id: credential.id,
|
|
476
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
477
|
-
type: credential.type,
|
|
478
|
-
response: {
|
|
479
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(assertion.clientDataJSON)),
|
|
480
|
-
authenticatorData: bytesToBase64url(new Uint8Array(assertion.authenticatorData)),
|
|
481
|
-
signature: bytesToBase64url(new Uint8Array(assertion.signature)),
|
|
482
|
-
userHandle: assertion.userHandle ? bytesToBase64url(new Uint8Array(assertion.userHandle)) : null
|
|
483
|
-
},
|
|
484
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {}
|
|
485
|
-
};
|
|
366
|
+
async function submitLogin(apiBase, response) {
|
|
486
367
|
const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/passkey-login`, {
|
|
487
368
|
method: "POST",
|
|
488
369
|
headers: { "content-type": "application/json" },
|
|
489
|
-
body: JSON.stringify({ credential:
|
|
370
|
+
body: JSON.stringify({ credential: response })
|
|
490
371
|
});
|
|
491
372
|
if (!res.ok) {
|
|
492
373
|
throw new ConnectError(await readErrorCode2(res), `passkey-login ${res.status}`);
|
|
@@ -510,6 +391,62 @@ async function readErrorCode2(res) {
|
|
|
510
391
|
return `http_${res.status}`;
|
|
511
392
|
}
|
|
512
393
|
|
|
394
|
+
// src/base64.ts
|
|
395
|
+
function base64ToBytes(s) {
|
|
396
|
+
const bin = atob(s);
|
|
397
|
+
const out = new Uint8Array(bin.length);
|
|
398
|
+
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function base64urlToBytes(s) {
|
|
402
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
403
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
404
|
+
return base64ToBytes(b64);
|
|
405
|
+
}
|
|
406
|
+
function bytesToBase64(bytes) {
|
|
407
|
+
let bin = "";
|
|
408
|
+
for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
|
|
409
|
+
return btoa(bin);
|
|
410
|
+
}
|
|
411
|
+
function bytesToBase64url(bytes) {
|
|
412
|
+
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
413
|
+
}
|
|
414
|
+
function base64urlToBase64(s) {
|
|
415
|
+
const t = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
416
|
+
return t + "=".repeat((4 - t.length % 4) % 4);
|
|
417
|
+
}
|
|
418
|
+
function compactSignatureToDer(compact) {
|
|
419
|
+
if (compact.length !== 64) {
|
|
420
|
+
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
421
|
+
}
|
|
422
|
+
const r = derInteger(compact.subarray(0, 32));
|
|
423
|
+
const s = derInteger(compact.subarray(32, 64));
|
|
424
|
+
const body = new Uint8Array(r.length + s.length);
|
|
425
|
+
body.set(r, 0);
|
|
426
|
+
body.set(s, r.length);
|
|
427
|
+
const out = new Uint8Array(2 + body.length);
|
|
428
|
+
out[0] = 48;
|
|
429
|
+
out[1] = body.length;
|
|
430
|
+
out.set(body, 2);
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
function derInteger(component) {
|
|
434
|
+
let start = 0;
|
|
435
|
+
while (start < component.length - 1 && component[start] === 0) start += 1;
|
|
436
|
+
let content = component.subarray(start);
|
|
437
|
+
if (content[0] & 128) {
|
|
438
|
+
const padded = new Uint8Array(content.length + 1);
|
|
439
|
+
padded[0] = 0;
|
|
440
|
+
padded.set(content, 1);
|
|
441
|
+
content = padded;
|
|
442
|
+
}
|
|
443
|
+
const out = new Uint8Array(2 + content.length);
|
|
444
|
+
out[0] = 2;
|
|
445
|
+
out[1] = content.length;
|
|
446
|
+
out.set(content, 2);
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
|
|
513
450
|
// src/anon-policy.ts
|
|
514
451
|
var DEFAULT_API_BASE3 = "https://api.dexter.cash";
|
|
515
452
|
var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
|
|
@@ -689,6 +626,8 @@ export {
|
|
|
689
626
|
createWallet,
|
|
690
627
|
passkeyLogin,
|
|
691
628
|
continueWithDexter,
|
|
629
|
+
bytesToBase64url,
|
|
630
|
+
base64urlToBase64,
|
|
692
631
|
createAnonServerPolicy,
|
|
693
632
|
createPasskeySigner,
|
|
694
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
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,15 +22,25 @@
|
|
|
22
22
|
"typecheck": "tsc --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@dexterai/vault": ">=0.
|
|
25
|
+
"@dexterai/vault": ">=0.22",
|
|
26
|
+
"@solana/web3.js": "^1.98.0",
|
|
26
27
|
"react": ">=18"
|
|
27
28
|
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@solana/web3.js": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
28
34
|
"devDependencies": {
|
|
29
|
-
"@dexterai/vault": "^0.
|
|
35
|
+
"@dexterai/vault": "^0.24.0",
|
|
36
|
+
"@solana/web3.js": "^1.98.4",
|
|
30
37
|
"@types/react": "^19.1.12",
|
|
31
38
|
"react": "^19.2.5",
|
|
32
39
|
"tsup": "^8.5.0",
|
|
33
40
|
"typescript": "^5.6.2",
|
|
34
41
|
"vitest": "^4.1.9"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@simplewebauthn/browser": "^13.3.0"
|
|
35
45
|
}
|
|
36
46
|
}
|