@theoo/sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Theoo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @theoo/sdk
2
+
3
+ The client for [Theoo](https://github.com/) — smart accounts owned by a
4
+ passkey that never leaves the user's device. No seed phrase, no extension, and
5
+ no key on a server.
6
+
7
+ ```sh
8
+ npm install @theoo/sdk
9
+ ```
10
+
11
+ ```ts
12
+ import { Theoo } from "@theoo/sdk";
13
+ import { browserPasskeys, LocalStorageTokenStore } from "@theoo/sdk/browser";
14
+
15
+ const theoo = new Theoo({
16
+ baseUrl: "https://theoo.example.com",
17
+ apiKey: "theoo_pub_…", // publishable: safe in a page
18
+ passkeys: browserPasskeys,
19
+ tokens: new LocalStorageTokenStore(),
20
+ });
21
+
22
+ await theoo.auth.begin({ provider: "email", destination: "you@example.com" });
23
+ await theoo.auth.complete({ kind: "code", challengeId, code });
24
+ await theoo.auth.enrolPasskey(); // this passkey now owns the account
25
+
26
+ await theoo.me.operations.run({
27
+ kind: "call",
28
+ call: { to: "0x…", value: "0", data: "0x" },
29
+ });
30
+ ```
31
+
32
+ Works in browsers and on servers. React bindings are in
33
+ [`@theoo/react`](https://www.npmjs.com/package/@theoo/react); native
34
+ ones in
35
+ [`@theoo/react-native`](https://www.npmjs.com/package/@theoo/react-native).
36
+
37
+ Theoo is self-hosted: this package talks to a service you run. See the
38
+ `docs/` directory of the repository for running one, the HTTP API, and the
39
+ full SDK reference.
40
+
41
+ ## Security
42
+
43
+ **Which key goes where.** A **publishable** key (`theoo_pub_…`) is meant to
44
+ ship in a page: it reaches only the sign-in routes and the routes an end
45
+ user's own bearer token already scopes to that user, and only from the origins
46
+ its tenant registered. A **secret** key (`theoo_…`) can act for *any* of your
47
+ users and must never reach a browser.
48
+
49
+ **Where sessions are stored.** `LocalStorageTokenStore` keeps the access and
50
+ refresh tokens in `localStorage`, which any script running on your page can
51
+ read. If your app has an XSS hole, that hole reaches the session.
52
+
53
+ It is worth being precise about what that does and does not mean. A stolen
54
+ session can act as the user *within what a session is allowed to do* — read
55
+ their account, start operations that then need a passkey. It **cannot** move
56
+ funds or take the account: every operation is signed on the user's device by
57
+ a passkey this package never sees and the server never holds. There is no
58
+ seed phrase to steal and nothing to export.
59
+
60
+ Choose accordingly:
61
+
62
+ | Store | Use it when |
63
+ | :--- | :--- |
64
+ | `LocalStorageTokenStore` | The session should survive a reload. The usual choice. |
65
+ | `MemoryTokenStore` | You would rather a reload sign the user out than leave a token readable by script. |
66
+ | Your own `TokenStore` | You want the tokens somewhere else — a worker, an encrypted store, your own backend. |
67
+
68
+ **Transport.** Every request sends the API key and, when acting for a user,
69
+ a bearer token. So the client:
70
+
71
+ * **refuses a plaintext `http://` base URL** unless it is loopback. Pass
72
+ `allowInsecureHttp: true` only for a server crossing a network you control.
73
+ * **does not follow redirects** (`redirect: "error"`). The API never issues
74
+ one, and a redirect would hand `x-api-key` to whatever host it named.
75
+ * **sends no cookies** (`credentials: "omit"`), so nothing here is a CSRF
76
+ target.
77
+ * **logs nothing.** There are no `console` calls in this package, so no token
78
+ reaches a log by accident.
79
+
80
+ **Dependencies.** This package has none. `@theoo/react` depends only on this
81
+ one. Nothing third-party enters your bundle through Theoo.
@@ -0,0 +1,14 @@
1
+ import type { AssuranceLevel, Caller } from "./types.js";
2
+ /**
3
+ * How strongly a caller is authenticated *right now*.
4
+ *
5
+ * A step-up — proving possession again on the strongest factor the account
6
+ * has — raises a caller to `verified`, and that elevation decays. Reading
7
+ * `subject.aal` gives the base level and silently ignores whether the
8
+ * elevation has since gone stale, which is the wrong answer for anything
9
+ * worth gating.
10
+ *
11
+ * @param now Unix seconds. Defaults to this machine's clock.
12
+ */
13
+ export declare function effectiveAssurance(caller: Caller, now?: number): AssuranceLevel;
14
+ //# sourceMappingURL=assurance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAgC,GAAG,cAAc,CAMtG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * How strongly a caller is authenticated *right now*.
3
+ *
4
+ * A step-up — proving possession again on the strongest factor the account
5
+ * has — raises a caller to `verified`, and that elevation decays. Reading
6
+ * `subject.aal` gives the base level and silently ignores whether the
7
+ * elevation has since gone stale, which is the wrong answer for anything
8
+ * worth gating.
9
+ *
10
+ * @param now Unix seconds. Defaults to this machine's clock.
11
+ */
12
+ export function effectiveAssurance(caller, now = Math.floor(Date.now() / 1000)) {
13
+ const fresh = caller.subject.stepUpFreshUntil;
14
+ if (fresh !== null && fresh > now)
15
+ return "verified";
16
+ // A subject whose base level is already `verified` was elevated at some
17
+ // point and has no live step-up now, so it falls back to possession.
18
+ return caller.subject.aal === "verified" ? "possession" : caller.subject.aal;
19
+ }
20
+ //# sourceMappingURL=assurance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assurance.js","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACpF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAC9C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,UAAU,CAAC;IACrD,wEAAwE;IACxE,qEAAqE;IACrE,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** base64url without padding, the WebAuthn JSON encoding. Works in browsers, Node and React Native. */
2
+ export declare function encode(input: ArrayBuffer | Uint8Array): string;
3
+ export declare function decode(input: string): Uint8Array;
4
+ //# sourceMappingURL=base64url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../src/base64url.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAMvG,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAa9D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAchD"}
@@ -0,0 +1,45 @@
1
+ /** base64url without padding, the WebAuthn JSON encoding. Works in browsers, Node and React Native. */
2
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3
+ const LOOKUP = {};
4
+ for (let i = 0; i < ALPHABET.length; i++)
5
+ LOOKUP[ALPHABET[i]] = i;
6
+ export function encode(input) {
7
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
8
+ let out = "";
9
+ for (let i = 0; i < bytes.length; i += 3) {
10
+ const a = bytes[i];
11
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
12
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
13
+ out += ALPHABET[a >> 2];
14
+ out += ALPHABET[((a & 3) << 4) | (b >> 4)];
15
+ if (i + 1 < bytes.length)
16
+ out += ALPHABET[((b & 15) << 2) | (c >> 6)];
17
+ if (i + 2 < bytes.length)
18
+ out += ALPHABET[c & 63];
19
+ }
20
+ return out;
21
+ }
22
+ export function decode(input) {
23
+ const s = input.replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
24
+ const out = new Uint8Array(Math.floor((s.length * 3) / 4));
25
+ let o = 0;
26
+ for (let i = 0; i < s.length; i += 4) {
27
+ const a = at(s, i);
28
+ const b = at(s, i + 1);
29
+ const c = i + 2 < s.length ? at(s, i + 2) : 0;
30
+ const d = i + 3 < s.length ? at(s, i + 3) : 0;
31
+ out[o++] = (a << 2) | (b >> 4);
32
+ if (i + 2 < s.length)
33
+ out[o++] = ((b & 15) << 4) | (c >> 2);
34
+ if (i + 3 < s.length)
35
+ out[o++] = ((c & 3) << 6) | d;
36
+ }
37
+ return out;
38
+ }
39
+ function at(s, i) {
40
+ const v = LOOKUP[s[i] ?? ""];
41
+ if (v === undefined)
42
+ throw new Error(`base64url: bad character at ${i}`);
43
+ return v;
44
+ }
45
+ //# sourceMappingURL=base64url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.js","sourceRoot":"","sources":["../src/base64url.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The browser's passkey API, as a `PasskeyProvider`. Import from
3
+ * `@theoo/sdk/browser`; the main entry stays free of DOM assumptions so
4
+ * servers and React Native can use it.
5
+ */
6
+ import { type PasskeyProvider } from "./webauthn.js";
7
+ import type { TokenStore, StoredTokens } from "./tokens.js";
8
+ export declare const browserPasskeys: PasskeyProvider;
9
+ /** Whether this browser can do a passkey ceremony at all. */
10
+ export declare function passkeysAvailable(): Promise<boolean>;
11
+ /** Tokens in `localStorage`, under one key. Survives reloads; cleared on sign-out. */
12
+ export declare class LocalStorageTokenStore implements TokenStore {
13
+ private readonly key;
14
+ constructor(key?: string);
15
+ load(): Promise<StoredTokens | null>;
16
+ save(tokens: StoredTokens | null): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAyD,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5G,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoB5D,eAAO,MAAM,eAAe,EAAE,eAe7B,CAAC;AAEF,6DAA6D;AAC7D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO1D;AAED,sFAAsF;AACtF,qBAAa,sBAAuB,YAAW,UAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,SAAkB;IAC5C,IAAI,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAQpC,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAQvD"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The browser's passkey API, as a `PasskeyProvider`. Import from
3
+ * `@theoo/sdk/browser`; the main entry stays free of DOM assumptions so
4
+ * servers and React Native can use it.
5
+ */
6
+ import { credentialToJSON, toCreationOptions, toRequestOptions } from "./webauthn.js";
7
+ function pkc() {
8
+ if (typeof PublicKeyCredential === "undefined") {
9
+ throw new Error("Passkeys are not available in this browser.");
10
+ }
11
+ return PublicKeyCredential;
12
+ }
13
+ function toJSON(cred) {
14
+ if (!cred)
15
+ throw new Error("The passkey ceremony was cancelled.");
16
+ const c = cred;
17
+ return typeof c.toJSON === "function" ? c.toJSON() : credentialToJSON(c);
18
+ }
19
+ export const browserPasskeys = {
20
+ async create(options) {
21
+ const P = pkc();
22
+ const publicKey = P.parseCreationOptionsFromJSON
23
+ ? P.parseCreationOptionsFromJSON(options.publicKey ?? options)
24
+ : toCreationOptions(options);
25
+ return toJSON(await navigator.credentials.create({ publicKey }));
26
+ },
27
+ async get(options) {
28
+ const P = pkc();
29
+ const publicKey = P.parseRequestOptionsFromJSON
30
+ ? P.parseRequestOptionsFromJSON(options.publicKey ?? options)
31
+ : toRequestOptions(options);
32
+ return toJSON(await navigator.credentials.get({ publicKey }));
33
+ },
34
+ };
35
+ /** Whether this browser can do a passkey ceremony at all. */
36
+ export async function passkeysAvailable() {
37
+ if (typeof PublicKeyCredential === "undefined")
38
+ return false;
39
+ try {
40
+ return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /** Tokens in `localStorage`, under one key. Survives reloads; cleared on sign-out. */
47
+ export class LocalStorageTokenStore {
48
+ constructor(key = "theoo.session") {
49
+ this.key = key;
50
+ }
51
+ async load() {
52
+ try {
53
+ const raw = localStorage.getItem(this.key);
54
+ return raw ? JSON.parse(raw) : null;
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ async save(tokens) {
61
+ try {
62
+ if (tokens)
63
+ localStorage.setItem(this.key, JSON.stringify(tokens));
64
+ else
65
+ localStorage.removeItem(this.key);
66
+ }
67
+ catch {
68
+ /* private mode, quota: the session simply does not persist */
69
+ }
70
+ }
71
+ }
72
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAwB,MAAM,eAAe,CAAC;AAS5G,SAAS,GAAG;IACV,IAAI,OAAO,mBAAmB,KAAK,WAAW,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,mBAA0B,CAAC;AACpC,CAAC;AAED,SAAS,MAAM,CAAC,IAAuB;IACrC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAClE,MAAM,CAAC,GAAG,IAAwD,CAAC;IACnE,OAAO,OAAO,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAU,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C,KAAK,CAAC,MAAM,CAAC,OAAwB;QACnC,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,CAAC,CAAC,4BAA4B;YAC9C,CAAC,CAAC,CAAC,CAAC,4BAA4B,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;YAC9D,CAAC,CAAE,iBAAiB,CAAC,OAAO,CAAmD,CAAC;QAClF,OAAO,MAAM,CAAC,MAAM,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,OAAwB;QAChC,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,CAAC,CAAC,2BAA2B;YAC7C,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;YAC7D,CAAC,CAAE,gBAAgB,CAAC,OAAO,CAAkD,CAAC;QAChF,OAAO,MAAM,CAAC,MAAM,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;CACF,CAAC;AAEF,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,OAAO,mBAAmB,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IAC7D,IAAI,CAAC;QACH,OAAO,MAAM,mBAAmB,CAAC,6CAA6C,EAAE,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,MAAM,OAAO,sBAAsB;IACjC,YAA6B,MAAM,eAAe;QAArB,QAAG,GAAH,GAAG,CAAkB;IAAG,CAAC;IACtD,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3C,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC,CAAC,CAAC,IAAI,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,MAA2B;QACpC,IAAI,CAAC;YACH,IAAI,MAAM;gBAAE,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;;gBAC9D,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,331 @@
1
+ import type { StoredTokens, TokenStore } from "./tokens.js";
2
+ import { Transport, type FetchLike } from "./transport.js";
3
+ import type { AuthChallenge, AuthOutcome, AvailableChain, BeginAuthRequest, ChainCall, ClientInfo, CompleteAuthRequest, Elevation, IdentitySummary, Me, OperationKind, OperationStatus, PreparedOperation, RecoveryStatus, ApiKeySummary, Caller, ChainOption, IssuedKey, NewProject, NewProjectKey, Project, SessionGrant, SessionRenewal, SessionKey, SmartAccount, SolanaAccount, SolanaAction, SolanaOperationStatus, SolanaRecoveryStatus, SolanaSessionGrant, SolanaSessionKey, PreparedSolanaOperation } from "./types.js";
4
+ import type { PasskeyProvider } from "./webauthn.js";
5
+ export interface TheooOptions {
6
+ /** Where Theoo is served, e.g. `https://theoo.example.com`. */
7
+ baseUrl: string;
8
+ /** A publishable key in an app; a secret key on your server. */
9
+ apiKey: string;
10
+ /** How passkey ceremonies run here. `browserPasskeys` from `@theoo/sdk/browser`, or the React Native provider. */
11
+ passkeys?: PasskeyProvider;
12
+ tokens?: TokenStore;
13
+ fetch?: FetchLike;
14
+ /** What the end user's client looks like, sent with every request that acts for them. */
15
+ client?: ClientInfo;
16
+ onSession?: (tokens: StoredTokens | null) => void;
17
+ }
18
+ export interface WaitOptions {
19
+ intervalMs?: number;
20
+ timeoutMs?: number;
21
+ }
22
+ /**
23
+ * The Theoo client.
24
+ *
25
+ * With a publishable key it is an end user's client: `auth`, then everything
26
+ * under `me`. With a secret key it is the tenant's server: `users(id)` to act
27
+ * for any user.
28
+ */
29
+ export declare class Theoo {
30
+ readonly transport: Transport;
31
+ private readonly passkeys?;
32
+ private readonly client;
33
+ constructor(opts: TheooOptions);
34
+ private ceremony;
35
+ readonly auth: {
36
+ /** Start a sign-in (or, with a signed-in user, an add-this-factor). */
37
+ begin: (req: BeginAuthRequest) => Promise<AuthChallenge>;
38
+ /** Finish a sign-in. Stores the session. */
39
+ complete: (req: CompleteAuthRequest) => Promise<AuthOutcome>;
40
+ /** Email or phone: send a code, then `submit(code)`. */
41
+ withCode: (provider: "email" | "phone", destination: string, purpose?: BeginAuthRequest["purpose"]) => Promise<{
42
+ challenge: {
43
+ kind: "code";
44
+ challengeId: string;
45
+ destinationMasked: string;
46
+ expiresAt: number;
47
+ devCode?: string | null;
48
+ };
49
+ submit: (code: string) => Promise<AuthOutcome>;
50
+ }>;
51
+ /** Google or KingsChat: where to send the user, then `submit` with what comes back. */
52
+ withProvider: (provider: "google" | "kingschat") => Promise<{
53
+ challenge: {
54
+ kind: "redirect";
55
+ state: string;
56
+ url: string;
57
+ } | {
58
+ kind: "popup";
59
+ state: string;
60
+ url: string;
61
+ expectsPostMessage: boolean;
62
+ };
63
+ /** After the provider's callback: the `code` it returned. */
64
+ submitCode: (code: string) => Promise<AuthOutcome>;
65
+ /** A token the provider's own SDK already produced. */
66
+ submitToken: (accessToken: string) => Promise<AuthOutcome>;
67
+ }>;
68
+ /** A passkey the user already has. One ceremony, no typing. */
69
+ withPasskey: () => Promise<AuthOutcome>;
70
+ /** Enrol a passkey on the signed-in user. The account on chain is owned by the first one. */
71
+ enrolPasskey: (label?: string) => Promise<AuthOutcome>;
72
+ refresh: () => Promise<StoredTokens | null>;
73
+ signOut: () => Promise<void>;
74
+ session: () => Promise<StoredTokens | null>;
75
+ };
76
+ readonly me: {
77
+ get: () => Promise<Me>;
78
+ account: (chain?: number) => Promise<SmartAccount | null>;
79
+ identities: () => Promise<IdentitySummary[]>;
80
+ unlinkIdentity: (provider: IdentitySummary["provider"], subject: string) => Promise<void>;
81
+ signOutEverywhere: () => Promise<void>;
82
+ /** Prove presence again with a passkey, for actions that demand freshness. */
83
+ stepUp: () => Promise<Elevation>;
84
+ operations: {
85
+ prepare: (kind: OperationKind, chain?: number) => Promise<PreparedOperation>;
86
+ submit: (id: string, assertion: unknown) => Promise<OperationStatus>;
87
+ get: (id: string) => Promise<OperationStatus>;
88
+ list: () => Promise<OperationStatus[]>;
89
+ /** Prepare, have the passkey sign, submit, and wait for inclusion. */
90
+ run: (kind: OperationKind, o?: {
91
+ chain?: number;
92
+ wait?: WaitOptions | false;
93
+ }) => Promise<OperationStatus>;
94
+ wait: (id: string, w?: WaitOptions) => Promise<OperationStatus>;
95
+ };
96
+ recovery: {
97
+ status: (chain?: number) => Promise<RecoveryStatus>;
98
+ /** Start moving the account to the newest passkey. Needs a fresh step-up on `/me`. */
99
+ begin: (chain?: number) => Promise<RecoveryStatus>;
100
+ complete: (chain?: number) => Promise<RecoveryStatus>;
101
+ };
102
+ sessions: {
103
+ list: () => Promise<SessionKey[]>;
104
+ get: (id: string) => Promise<SessionKey>;
105
+ /** Act under a session key: no passkey, within the grant. */
106
+ execute: (id: string, call: ChainCall) => Promise<OperationStatus>;
107
+ /** Grant a session: a passkey-signed operation. Returns the session once the grant is on chain. */
108
+ grant: (session: SessionGrant, o?: {
109
+ chain?: number;
110
+ wait?: WaitOptions;
111
+ }) => Promise<{
112
+ session: SessionKey;
113
+ operation: OperationStatus;
114
+ }>;
115
+ /**
116
+ * Extend a key that already exists, keeping its address. One passkey
117
+ * ceremony and no new key to distribute — and the spent counter
118
+ * restarts, because that is what the account does with a fresh grant.
119
+ */
120
+ renew: (session: SessionRenewal, o?: {
121
+ chain?: number;
122
+ wait?: WaitOptions;
123
+ }) => Promise<{
124
+ session: SessionKey;
125
+ operation: OperationStatus;
126
+ }>;
127
+ revoke: (id: string, o?: {
128
+ chain?: number;
129
+ wait?: WaitOptions;
130
+ }) => Promise<OperationStatus>;
131
+ };
132
+ solana: {
133
+ account: (cluster?: string) => Promise<SolanaAccount | null>;
134
+ operations: {
135
+ prepare: (action: SolanaAction, cluster?: string) => Promise<PreparedSolanaOperation>;
136
+ submit: (id: string, assertion: unknown) => Promise<SolanaOperationStatus>;
137
+ get: (id: string) => Promise<SolanaOperationStatus>;
138
+ list: () => Promise<SolanaOperationStatus[]>;
139
+ run: (action: SolanaAction, cluster?: string) => Promise<SolanaOperationStatus>;
140
+ };
141
+ /**
142
+ * Session keys on Solana. The key signs the transaction itself, so
143
+ * `execute` is one call: no ceremony, no confirmation to wait for.
144
+ */
145
+ sessions: {
146
+ list: () => Promise<SolanaSessionKey[]>;
147
+ get: (id: string) => Promise<SolanaSessionKey>;
148
+ /** Act under a session key: no passkey, within the grant. */
149
+ execute: (id: string, action: SolanaAction) => Promise<SolanaOperationStatus>;
150
+ /** Grant a session: a passkey-signed operation, as every grant is. */
151
+ grant: (session: SolanaSessionGrant, cluster?: string) => Promise<{
152
+ session: SolanaSessionKey;
153
+ operation: SolanaOperationStatus;
154
+ }>;
155
+ /** The same, on Solana: one ceremony, the same key, the tally reset. */
156
+ renew: (session: SessionRenewal, cluster?: string) => Promise<{
157
+ session: SolanaSessionKey;
158
+ operation: SolanaOperationStatus;
159
+ }>;
160
+ revoke: (id: string, cluster?: string) => Promise<SolanaOperationStatus>;
161
+ };
162
+ /** Guardian recovery on Solana: the same shape as the EVM side. */
163
+ recovery: {
164
+ status: (cluster?: string) => Promise<SolanaRecoveryStatus>;
165
+ /** Needs a fresh step-up on `/me`. */
166
+ begin: (cluster?: string) => Promise<SolanaRecoveryStatus>;
167
+ complete: (cluster?: string) => Promise<SolanaRecoveryStatus>;
168
+ };
169
+ };
170
+ };
171
+ /**
172
+ * Self-serve projects, for a console app.
173
+ *
174
+ * These act as the signed-in *developer* — a user of the console tenant —
175
+ * so they need that tenant's publishable key and the developer's own bearer
176
+ * token, exactly like any other `/v1/me` call. A developer can only ever
177
+ * reach projects they created.
178
+ */
179
+ console: {
180
+ /**
181
+ * Every chain a project could be pointed at, across both account models.
182
+ *
183
+ * Not the same as {@link chains}, which answers for the calling tenant.
184
+ * A project does not exist yet when this is asked.
185
+ */
186
+ chains: () => Promise<AvailableChain[]>;
187
+ projects: () => Promise<Project[]>;
188
+ create: (project: NewProject) => Promise<Project>;
189
+ keys: (slug: string) => Promise<ApiKeySummary[]>;
190
+ /** The key is in the response and never again. Show it once, then store it. */
191
+ issueKey: (slug: string, key: NewProjectKey) => Promise<IssuedKey>;
192
+ revokeKey: (slug: string, prefix: string) => Promise<{
193
+ revoked: boolean;
194
+ }>;
195
+ /** Rename a project. Cosmetic — the slug does not move. */
196
+ rename: (slug: string, name: string) => Promise<Project>;
197
+ /**
198
+ * Change which chain a call that names none gets. Safe at any time: it
199
+ * moves no address. Refused if the chain is not registered for the
200
+ * project's account model.
201
+ */
202
+ setDefaultChain: (slug: string, chain: number) => Promise<Project>;
203
+ /**
204
+ * Put a project away, or take it back out. Not a delete: the accounts it
205
+ * owns exist on a chain and archiving does not touch them. An archived
206
+ * project's keys stop working and it stops counting against the project
207
+ * limit.
208
+ */
209
+ setArchived: (slug: string, archived: boolean) => Promise<Project>;
210
+ setOrigins: (slug: string, origins: string[]) => Promise<Project>;
211
+ /** Lowering is always allowed; raising is capped at the operator's ceiling. */
212
+ setBudget: (slug: string, dailyWei: string | null) => Promise<Project>;
213
+ };
214
+ /**
215
+ * The chains this tenant's users can hold accounts on.
216
+ *
217
+ * Readable with a publishable key, so an app can offer a choice rather
218
+ * than hardcoding what an operator may change. Pass `chain` to any
219
+ * account or operation call to use one.
220
+ */
221
+ chains(): Promise<ChainOption[]>;
222
+ /**
223
+ * Resolve an end user's access token into who they are.
224
+ *
225
+ * For a server holding a token its front end sent: it says which user, in
226
+ * which session, and how strongly they are authenticated. Needs a secret
227
+ * key, because it speaks for the tenant rather than for a user.
228
+ *
229
+ * Use {@link effectiveAssurance} on the result rather than reading
230
+ * `subject.aal`, which is the base level and ignores a step-up that has
231
+ * since gone stale.
232
+ */
233
+ authenticate(accessToken: string): Promise<Caller>;
234
+ users(userId: string): {
235
+ account: (chain?: number) => Promise<SmartAccount | null>;
236
+ identities: () => Promise<IdentitySummary[]>;
237
+ signOutEverywhere: (reason?: string) => Promise<void>;
238
+ operations: {
239
+ prepare: (kind: OperationKind, chain?: number) => Promise<PreparedOperation>;
240
+ submit: (id: string, assertion: unknown) => Promise<OperationStatus>;
241
+ get: (id: string) => Promise<OperationStatus>;
242
+ list: () => Promise<OperationStatus[]>;
243
+ /** Prepare, have the passkey sign, submit, and wait for inclusion. */
244
+ run: (kind: OperationKind, o?: {
245
+ chain?: number;
246
+ wait?: WaitOptions | false;
247
+ }) => Promise<OperationStatus>;
248
+ wait: (id: string, w?: WaitOptions) => Promise<OperationStatus>;
249
+ };
250
+ recovery: {
251
+ status: (chain?: number) => Promise<RecoveryStatus>;
252
+ /** Start moving the account to the newest passkey. Needs a fresh step-up on `/me`. */
253
+ begin: (chain?: number) => Promise<RecoveryStatus>;
254
+ complete: (chain?: number) => Promise<RecoveryStatus>;
255
+ };
256
+ sessions: {
257
+ list: () => Promise<SessionKey[]>;
258
+ get: (id: string) => Promise<SessionKey>;
259
+ /** Act under a session key: no passkey, within the grant. */
260
+ execute: (id: string, call: ChainCall) => Promise<OperationStatus>;
261
+ /** Grant a session: a passkey-signed operation. Returns the session once the grant is on chain. */
262
+ grant: (session: SessionGrant, o?: {
263
+ chain?: number;
264
+ wait?: WaitOptions;
265
+ }) => Promise<{
266
+ session: SessionKey;
267
+ operation: OperationStatus;
268
+ }>;
269
+ /**
270
+ * Extend a key that already exists, keeping its address. One passkey
271
+ * ceremony and no new key to distribute — and the spent counter
272
+ * restarts, because that is what the account does with a fresh grant.
273
+ */
274
+ renew: (session: SessionRenewal, o?: {
275
+ chain?: number;
276
+ wait?: WaitOptions;
277
+ }) => Promise<{
278
+ session: SessionKey;
279
+ operation: OperationStatus;
280
+ }>;
281
+ revoke: (id: string, o?: {
282
+ chain?: number;
283
+ wait?: WaitOptions;
284
+ }) => Promise<OperationStatus>;
285
+ };
286
+ solana: {
287
+ account: (cluster?: string) => Promise<SolanaAccount | null>;
288
+ operations: {
289
+ prepare: (action: SolanaAction, cluster?: string) => Promise<PreparedSolanaOperation>;
290
+ submit: (id: string, assertion: unknown) => Promise<SolanaOperationStatus>;
291
+ get: (id: string) => Promise<SolanaOperationStatus>;
292
+ list: () => Promise<SolanaOperationStatus[]>;
293
+ run: (action: SolanaAction, cluster?: string) => Promise<SolanaOperationStatus>;
294
+ };
295
+ /**
296
+ * Session keys on Solana. The key signs the transaction itself, so
297
+ * `execute` is one call: no ceremony, no confirmation to wait for.
298
+ */
299
+ sessions: {
300
+ list: () => Promise<SolanaSessionKey[]>;
301
+ get: (id: string) => Promise<SolanaSessionKey>;
302
+ /** Act under a session key: no passkey, within the grant. */
303
+ execute: (id: string, action: SolanaAction) => Promise<SolanaOperationStatus>;
304
+ /** Grant a session: a passkey-signed operation, as every grant is. */
305
+ grant: (session: SolanaSessionGrant, cluster?: string) => Promise<{
306
+ session: SolanaSessionKey;
307
+ operation: SolanaOperationStatus;
308
+ }>;
309
+ /** The same, on Solana: one ceremony, the same key, the tally reset. */
310
+ renew: (session: SessionRenewal, cluster?: string) => Promise<{
311
+ session: SolanaSessionKey;
312
+ operation: SolanaOperationStatus;
313
+ }>;
314
+ revoke: (id: string, cluster?: string) => Promise<SolanaOperationStatus>;
315
+ };
316
+ /** Guardian recovery on Solana: the same shape as the EVM side. */
317
+ recovery: {
318
+ status: (cluster?: string) => Promise<SolanaRecoveryStatus>;
319
+ /** Needs a fresh step-up on `/me`. */
320
+ begin: (cluster?: string) => Promise<SolanaRecoveryStatus>;
321
+ complete: (cluster?: string) => Promise<SolanaRecoveryStatus>;
322
+ };
323
+ };
324
+ };
325
+ /** The user's Solana account and operations: the same passkey, a second ledger. */
326
+ private solanaAt;
327
+ private operationsAt;
328
+ private recoveryAt;
329
+ private sessionsAt;
330
+ }
331
+ //# sourceMappingURL=client.d.ts.map