@tari-project/ootle-wallet-daemon-signer 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,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, The Tari Developer Community
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # @tari-project/ootle-wallet-daemon-signer
2
+
3
+ Remote `Signer` for the Tari Ootle SDK — delegates signing to a running
4
+ `tari_ootle_walletd` process via JRPC. The secret key never enters JavaScript
5
+ memory.
6
+
7
+ ## Runtime support
8
+
9
+ | Package | Browser | Node ≥ 22 | Notes |
10
+ | ------------------------------------------ | ------- | -------------------- | ---------------------------------- |
11
+ | `@tari-project/ootle-wallet-daemon-signer` | ✓ | ✓ (with `authToken`) | WebAuthn passkeys are browser-only |
12
+
13
+ > **Node note:** Node ≥ 22 currently requires
14
+ > `NODE_OPTIONS=--experimental-wasm-modules` when running under `tsx` or plain
15
+ > `node` because the SDK loads `@tari-project/ootle-wasm` as an ES module. See
16
+ > [`examples/node/README.md`](../../examples/node/README.md) for the rationale
17
+ > and forward plan.
18
+
19
+ ## Browser vs Node
20
+
21
+ The daemon's auth method is configured server-side and decides the runtime
22
+ profile of this signer.
23
+
24
+ ### Browser
25
+
26
+ The daemon's auth method is typically `"webauthn"` — passkeys via
27
+ `navigator.credentials`. Pass just the URL and `WalletDaemonSigner.connect`
28
+ runs the WebAuthn flow automatically:
29
+
30
+ ```ts
31
+ import { WalletDaemonSigner } from "@tari-project/ootle-wallet-daemon-signer";
32
+
33
+ const signer = await WalletDaemonSigner.connect({ url: "http://localhost:18103" });
34
+ ```
35
+
36
+ ### Node
37
+
38
+ WebAuthn is browser-only. If the daemon is configured for `method: "webauthn"`
39
+ and you call `connect({ url })` from Node, the SDK throws the canonical
40
+ actionable error:
41
+
42
+ ```text
43
+ WebAuthn is browser-only. Run in a browser, or pass `authToken` explicitly to `WalletDaemonSigner.connect({ url, authToken })` from Node.
44
+ ```
45
+
46
+ The Node answer is to **pre-fetch the auth token via your own flow** (or use
47
+ the daemon's `method: "none"` if you control its config) and pass it
48
+ explicitly to `connect`:
49
+
50
+ ```ts
51
+ import { WalletDaemonSigner } from "@tari-project/ootle-wallet-daemon-signer";
52
+
53
+ const signer = await WalletDaemonSigner.connect({
54
+ url: "http://localhost:18103",
55
+ authToken: process.env.OOTLE_DAEMON_AUTH_TOKEN, // pre-fetched out-of-band
56
+ });
57
+ ```
58
+
59
+ When `authToken` is supplied, the signer bypasses the WebAuthn challenge
60
+ entirely and presents the token directly to the daemon.
61
+
62
+ ## Install
63
+
64
+ ```sh
65
+ pnpm add @tari-project/ootle-wallet-daemon-signer
66
+ ```
67
+
68
+ ## Hello world
69
+
70
+ Connect, read the account address, and sign a transaction:
71
+
72
+ ```ts
73
+ import { WalletDaemonSigner } from "@tari-project/ootle-wallet-daemon-signer";
74
+
75
+ const signer = await WalletDaemonSigner.connect({
76
+ url: "http://localhost:18103",
77
+ authToken: process.env.OOTLE_DAEMON_AUTH_TOKEN,
78
+ });
79
+
80
+ const address = await signer.getAddress();
81
+ const publicKey = await signer.getPublicKey();
82
+
83
+ const signatures = await signer.signTransaction(unsignedTx);
84
+ ```
85
+
86
+ For the in-browser passkey flow end-to-end, see the
87
+ [`connect-button/`](../../examples/connect-button/) example app.
88
+
89
+ ## Deep dive
90
+
91
+ For the full `WalletDaemonSignerOptions` surface and the daemon-side
92
+ `tari_ootle_walletd` invocation, see the
93
+ [root README's "`@tari-project/ootle-wallet-daemon-signer`" section](../../README.md#tari-projectootle-wallet-daemon-signer).
94
+
95
+ ## Examples
96
+
97
+ Runnable browser apps and Node scripts live under
98
+ [`examples/`](../../examples/README.md). The
99
+ [`connect-button/`](../../examples/connect-button/) React app demonstrates the
100
+ browser passkey flow; for headless Node usage see
101
+ [`examples/node/README.md`](../../examples/node/README.md) and pass an
102
+ `authToken` as shown above.
@@ -0,0 +1,121 @@
1
+ import { JrpcPermission } from '@tari-project/ootle-ts-bindings';
2
+ import { Signer } from '@tari-project/ootle';
3
+ import { TransactionSignature } from '@tari-project/ootle-ts-bindings';
4
+ import { UnsignedTransactionV1 } from '@tari-project/ootle-ts-bindings';
5
+ import { WalletDaemonClient } from '@tari-project/wallet_jrpc_client';
6
+
7
+ /**
8
+ * Authenticate with the wallet daemon, automatically handling the configured
9
+ * auth method ("none" or "webauthn").
10
+ *
11
+ * For "none", a token is requested directly. Works in both browsers and Node.
12
+ * For "webauthn", the browser's WebAuthn API is used to register or login,
13
+ * then the resulting credential is exchanged for a token. **Browser-only** —
14
+ * in Node, pass `authToken` to `WalletDaemonSigner.connect({ url, authToken })`
15
+ * instead of calling this flow.
16
+ *
17
+ * @returns The JWT auth token.
18
+ * @throws {SignerError} if the daemon advertises an unsupported auth method, the
19
+ * WebAuthn flow is invoked outside a browser, the WebAuthn challenge is malformed,
20
+ * or the user cancels the WebAuthn prompt.
21
+ */
22
+ export declare function authenticate(client: WalletDaemonClient, options?: AuthOptions): Promise<string>;
23
+
24
+ export declare interface AuthOptions {
25
+ /** Permissions to request from the wallet daemon. Defaults to `["Admin"]`. */
26
+ permissions?: JrpcPermission[];
27
+ /** Application name used as the WebAuthn username/identifier. Defaults to `"tari-wallet-sdk"`. */
28
+ appName?: string;
29
+ }
30
+
31
+ export { WalletDaemonClient }
32
+
33
+ /**
34
+ * A Signer that delegates signing to a running wallet daemon over its JRPC interface,
35
+ * using `@tari-project/wallet_jrpc_client` for all communication.
36
+ *
37
+ * The wallet daemon holds the secret key and returns signatures, so the key never
38
+ * lives in JavaScript memory. This signer is suitable for server-side or trusted
39
+ * environment usage where the wallet daemon is reachable.
40
+ *
41
+ * **Stealth boundary (intentionally not implemented).** Client-side stealth spending and
42
+ * scanning need the raw view secret, which the daemon never exports — it keeps the key and
43
+ * exposes its own *server-side* stealth JRPC instead (`stealthTransfer`, `stealthUtxosList`,
44
+ * `stealthUtxosDecryptValue`, `accountsAssociateStealthResource`, `viewVaultBalance` on
45
+ * `@tari-project/wallet_jrpc_client`). This signer therefore does **not** implement
46
+ * `Signer.addStealthSignature` and its {@link getViewSecret} throws. For client-side stealth
47
+ * spend, use {@link SecretKeyWallet}; for daemon-managed stealth, call the daemon's stealth
48
+ * JRPC methods directly. Daemon-delegated stealth wiring is future work.
49
+ */
50
+ export declare class WalletDaemonSigner implements Signer {
51
+ private readonly client;
52
+ private _publicKey;
53
+ private _address;
54
+ private constructor();
55
+ /**
56
+ * Creates a new signer with the given auth token, without verifying connectivity.
57
+ * Prefer `connect()` for eager validation and automatic authentication.
58
+ */
59
+ static new(options: WalletDaemonSignerOptions & {
60
+ authToken: string;
61
+ }): WalletDaemonSigner;
62
+ /**
63
+ * Connect to the daemon and cache the public key / address.
64
+ *
65
+ * If no `authToken` is provided, automatically authenticates using the daemon's
66
+ * configured auth method. For WebAuthn, this triggers the browser's passkey
67
+ * registration or login flow.
68
+ */
69
+ static connect(options: WalletDaemonSignerOptions & AuthOptions): Promise<WalletDaemonSigner>;
70
+ getAddress(): Promise<string>;
71
+ getPublicKey(): Promise<Uint8Array>;
72
+ /**
73
+ * Signs `unsignedTx` via the daemon's `transactions.sign` JRPC. `sealPublicKey` is
74
+ * forwarded as hex so the daemon hashes the transaction with the SAME seal public key the
75
+ * SDK's seal step uses — otherwise the per-signer hash and the seal hash diverge and the
76
+ * engine rejects the envelope.
77
+ *
78
+ * TODO(daemon): the `transactions.sign` JRPC's acceptance of `seal_public_key` is unverified
79
+ * against a running `tari_ootle_walletd`, and there is no capability/version handshake on the
80
+ * typed client (v1.14.0 exposes only the generic `sendRequest`) to feature-detect support, so
81
+ * this call cannot fail fast the way {@link getViewSecret} does. If the daemon ignores the
82
+ * parameter and hashes with its own pk, the per-signer signatures will not verify and the
83
+ * failure surfaces only LATE — at submit time — as the opaque engine error
84
+ * `"Transaction has one or more invalid signature(s)"`, not here. An unconditional throw is
85
+ * deliberately NOT added: it would break a daemon build that *does* honour `seal_public_key`.
86
+ * Until a daemon-side capability signal lands (tracked by this TODO), use
87
+ * {@link SecretKeyWallet} for client-side signing — it is the supported signer for sealed
88
+ * transactions.
89
+ */
90
+ signTransaction(unsignedTx: UnsignedTransactionV1, sealPublicKey: Uint8Array): Promise<TransactionSignature[]>;
91
+ /**
92
+ * The daemon never exports its view secret, so client-side stealth scanning/spending is
93
+ * unsupported via this signer. Always throws — see the class doc for the server-side
94
+ * stealth JRPC alternative. {@link Signer.addStealthSignature} is likewise not implemented.
95
+ *
96
+ * @throws {SignerError} always — the daemon never exports its view secret.
97
+ */
98
+ getViewSecret(): Promise<Uint8Array>;
99
+ /**
100
+ * Fetches the default account info from the daemon and caches it on this signer.
101
+ * Re-calls are idempotent — once cached, subsequent calls do not re-fetch.
102
+ *
103
+ * @throws {SignerError} if the daemon's `accounts.get_default` response is missing the
104
+ * public key or address.
105
+ */
106
+ fetchAccountInfo(): Promise<void>;
107
+ private ensureAccountInfo;
108
+ }
109
+
110
+ export declare interface WalletDaemonSignerOptions {
111
+ /** Base URL of the wallet daemon HTTP endpoint, e.g. "http://localhost:18103" */
112
+ url: string;
113
+ /**
114
+ * Token used to authenticate JRPC calls to the wallet daemon.
115
+ * If omitted, {@link WalletDaemonSigner.connect} will automatically authenticate
116
+ * using the daemon's configured auth method (none or WebAuthn).
117
+ */
118
+ authToken?: string;
119
+ }
120
+
121
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,407 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { SignerError as e, assertByteLength as t, fromHexStr as n, toHexStr as r } from "@tari-project/ootle";
5
+ BigInt.prototype.toJSON = function() {
6
+ return this > BigInt(2 ** 53 - 1) ? this.toString() : Number(this);
7
+ };
8
+ var i = class e2 {
9
+ constructor(e4) {
10
+ __publicField(this, "url");
11
+ this.url = e4;
12
+ }
13
+ static new(t2) {
14
+ return new e2(t2);
15
+ }
16
+ async sendRequest(e4, t2) {
17
+ let n2 = { "Content-Type": "application/json" };
18
+ (t2 == null ? void 0 : t2.token) && (n2.Authorization = `Bearer ${t2.token}`);
19
+ let r2 = new AbortController(), i3 = r2.signal, a2 = (t2 == null ? void 0 : t2.timeout_millis) ? setTimeout(() => {
20
+ r2.abort("Timeout");
21
+ }, t2.timeout_millis) : null, o2 = await fetch(this.url, {
22
+ method: "POST",
23
+ body: JSON.stringify(e4),
24
+ headers: n2,
25
+ signal: i3
26
+ });
27
+ if (a2 && clearTimeout(a2), !o2.ok) {
28
+ let e5 = await o2.text();
29
+ throw Error(`HTTP error ${o2.status}: ${e5}`);
30
+ }
31
+ return await o2.json();
32
+ }
33
+ }, a = class e3 {
34
+ constructor(e4) {
35
+ __publicField(this, "token");
36
+ __publicField(this, "transport");
37
+ __publicField(this, "id");
38
+ __publicField(this, "reauthEnabled", true);
39
+ this.token = null, this.transport = e4, this.id = 0;
40
+ }
41
+ static new(t2) {
42
+ return new e3(t2);
43
+ }
44
+ static usingFetchTransport(t2) {
45
+ return e3.new(i.new(t2));
46
+ }
47
+ setReauthenticationEnabled(e4) {
48
+ this.reauthEnabled = e4;
49
+ }
50
+ isAuthenticated() {
51
+ return !!this.token;
52
+ }
53
+ setToken(e4) {
54
+ this.token = e4;
55
+ }
56
+ getToken() {
57
+ return this.token;
58
+ }
59
+ getTransport() {
60
+ return this.transport;
61
+ }
62
+ authGetMethod() {
63
+ return this.sendRequest("auth.method", {});
64
+ }
65
+ authListSessions(e4) {
66
+ return this.sendRequest("auth.list_sessions", e4);
67
+ }
68
+ async authRequest(e4, t2) {
69
+ let n2 = {
70
+ permissions: e4,
71
+ credentials: t2
72
+ };
73
+ return (await this.sendRequest("auth.request", n2)).token;
74
+ }
75
+ authRevoke(e4) {
76
+ return this.sendRequest("auth.revoke", e4);
77
+ }
78
+ authRefresh() {
79
+ return this.sendRequest("auth.refresh");
80
+ }
81
+ walletGetInfo() {
82
+ return this.sendRequest("wallet.get_info", {});
83
+ }
84
+ accountsCreate(e4) {
85
+ return this.sendRequest("accounts.create", e4);
86
+ }
87
+ accountsRename(e4) {
88
+ return this.sendRequest("accounts.rename", e4);
89
+ }
90
+ burnProofsList(e4) {
91
+ return this.sendRequest("burn_proofs.list", e4);
92
+ }
93
+ accountsClaimBurn(e4) {
94
+ return this.sendRequest("accounts.claim_burn", e4);
95
+ }
96
+ accountsAssociateStealthResource(e4) {
97
+ return this.sendRequest("accounts.associate_stealth_resource", e4);
98
+ }
99
+ accountsGetBalances(e4) {
100
+ return this.sendRequest("accounts.get_balances", e4);
101
+ }
102
+ accountsList(e4) {
103
+ return this.sendRequest("accounts.list", e4);
104
+ }
105
+ accountsGet(e4) {
106
+ return this.sendRequest("accounts.get", e4);
107
+ }
108
+ accountsTransfer(e4) {
109
+ return this.sendRequest("accounts.transfer", e4);
110
+ }
111
+ confidentialTransfer(e4) {
112
+ return this.sendRequest("accounts.confidential_transfer", e4);
113
+ }
114
+ stealthTransfer(e4) {
115
+ return this.sendRequest("accounts.stealth_transfer", e4);
116
+ }
117
+ accountsGetDefault(e4) {
118
+ return this.sendRequest("accounts.get_default", e4);
119
+ }
120
+ accountsSetDefault(e4) {
121
+ return this.sendRequest("accounts.set_default", e4);
122
+ }
123
+ submitTransaction(e4) {
124
+ return this.sendRequest("transactions.submit", e4);
125
+ }
126
+ submitTransactionDryRun(e4) {
127
+ return this.sendRequest("transactions.submit_dry_run", e4);
128
+ }
129
+ submitTransactionManifest(e4) {
130
+ return this.sendRequest("transactions.submit_manifest", e4);
131
+ }
132
+ publishTemplate(e4) {
133
+ return this.sendRequest("transactions.publish_template", e4);
134
+ }
135
+ substatesGet(e4) {
136
+ return this.sendRequest("substates.get", e4);
137
+ }
138
+ substatesList(e4) {
139
+ return this.sendRequest("substates.list", e4);
140
+ }
141
+ transactionsList(e4) {
142
+ return this.sendRequest("transactions.list", e4);
143
+ }
144
+ transactionsGet(e4) {
145
+ return this.sendRequest("transactions.get", e4);
146
+ }
147
+ getTransactionResult(e4) {
148
+ return this.sendRequest("transactions.get_result", e4);
149
+ }
150
+ waitForTransactionResult(e4) {
151
+ return this.sendRequest("transactions.wait_result", e4);
152
+ }
153
+ templatesGet(e4) {
154
+ return this.sendRequest("templates.get", e4);
155
+ }
156
+ templatesListAuthored(e4) {
157
+ return this.sendRequest("templates.list_authored", e4);
158
+ }
159
+ createFreeTestCoins(e4) {
160
+ return this.sendRequest("accounts.create_free_test_coins", e4);
161
+ }
162
+ createKey(e4) {
163
+ return this.sendRequest("keys.create", e4);
164
+ }
165
+ keysSetActive(e4) {
166
+ return this.sendRequest("keys.set_active", e4);
167
+ }
168
+ listKeys(e4) {
169
+ return this.sendRequest("keys.list", e4);
170
+ }
171
+ viewVaultBalance(e4) {
172
+ return this.sendRequest("confidential.view_vault_balance", e4);
173
+ }
174
+ nftsList(e4) {
175
+ return this.sendRequest("nfts.list", e4);
176
+ }
177
+ nftTransfer(e4) {
178
+ return this.sendRequest("nfts.transfer", e4);
179
+ }
180
+ mintFaucetNfts(e4) {
181
+ return this.sendRequest("nfts.mint_faucet_nft", e4);
182
+ }
183
+ validatorsClaimFees(e4) {
184
+ return this.sendRequest("validators.claim_fees", e4);
185
+ }
186
+ validatorsGetFees(e4) {
187
+ return this.sendRequest("validators.get_fees", e4);
188
+ }
189
+ webrtcStart(e4) {
190
+ return this.sendRequest("webrtc.start", e4);
191
+ }
192
+ settingsGet() {
193
+ return this.sendRequest("settings.get");
194
+ }
195
+ settingsSet(e4) {
196
+ return this.sendRequest("settings.set", e4);
197
+ }
198
+ webauthnAlreadyRegistered(e4) {
199
+ return this.sendRequest("webauthn.already_registered", e4);
200
+ }
201
+ webauthnStartRegistration(e4) {
202
+ return this.sendRequest("webauthn.reg_start", e4);
203
+ }
204
+ webauthnFinishRegistration(e4) {
205
+ return this.sendRequest("webauthn.reg_finish", e4);
206
+ }
207
+ webauthnAuthStart(e4) {
208
+ return this.sendRequest("webauthn.auth_start", e4);
209
+ }
210
+ stealthUtxosList(e4) {
211
+ return this.sendRequest("stealth_utxos.list", e4);
212
+ }
213
+ stealthUtxosDecryptValue(e4) {
214
+ return this.sendRequest("stealth_utxos.decrypt_value", e4);
215
+ }
216
+ async sendRequest(e4, t2 = null) {
217
+ let n2 = this.id++, r2 = await this.transport.sendRequest({
218
+ method: e4,
219
+ jsonrpc: "2.0",
220
+ id: n2,
221
+ params: t2 || {}
222
+ }, {
223
+ token: this.token,
224
+ timeout_millis: null
225
+ });
226
+ if (!this.reauthEnabled) {
227
+ if (r2.error) throw Error(`RPC Error ${r2.error.code}: ${r2.error.message}`, { cause: {
228
+ method: e4,
229
+ ...r2.error
230
+ } });
231
+ return r2.result;
232
+ }
233
+ if ((r2 == null ? void 0 : r2.error) && r2.error.code === 401) {
234
+ let n3 = Error(`RPC Error ${r2.error.code}: ${r2.error.message}`, { cause: {
235
+ method: e4,
236
+ ...r2.error
237
+ } });
238
+ if (e4.startsWith("auth.")) throw console.warn("Token refresh failed"), this.token = null, n3;
239
+ let i3 = this.id++, a2 = await this.transport.sendRequest({
240
+ method: "auth.refresh",
241
+ jsonrpc: "2.0",
242
+ id: i3,
243
+ params: {}
244
+ });
245
+ if (a2.error) throw console.debug("Refresh resp", a2), this.token = null, n3;
246
+ console.debug("Token refreshed successfully. Retrying original request."), this.token = a2.result.token, r2 = await this.transport.sendRequest({
247
+ method: e4,
248
+ jsonrpc: "2.0",
249
+ id: i3,
250
+ params: t2 || {}
251
+ }, {
252
+ token: this.token,
253
+ timeout_millis: null
254
+ });
255
+ }
256
+ if (r2.error) throw Error(`RPC Error ${r2.error.code}: ${r2.error.message}`, { cause: {
257
+ method: e4,
258
+ ...r2.error
259
+ } });
260
+ return r2.result;
261
+ }
262
+ }, o = "tari-wallet-sdk", s = ["Admin"];
263
+ function c(e4) {
264
+ let t2 = e4.replace(/-/g, "+").replace(/_/g, "/"), n2 = t2.padEnd(Math.ceil(t2.length / 4) * 4, "=");
265
+ return Uint8Array.from(atob(n2), (e5) => e5.charCodeAt(0));
266
+ }
267
+ async function l(t2, n2) {
268
+ let r2 = (n2 == null ? void 0 : n2.permissions) ?? s, i3 = (n2 == null ? void 0 : n2.appName) ?? o, { method: a2 } = await t2.authGetMethod();
269
+ switch (a2) {
270
+ case "none":
271
+ return t2.authRequest(r2, "None");
272
+ case "webauthn":
273
+ return u(t2, i3, r2);
274
+ default:
275
+ throw new e(`Unsupported wallet daemon auth method: ${a2}. Configure the daemon for one of: "none" or "webauthn", and pass that value to WalletDaemonSigner.connect.`);
276
+ }
277
+ }
278
+ async function u(t2, n2, r2) {
279
+ if (globalThis.navigator === void 0 || !globalThis.navigator.credentials) throw new e("WebAuthn is browser-only. Run in a browser, or pass `authToken` explicitly to `WalletDaemonSigner.connect({ url, authToken })` from Node.");
280
+ let { registered: i3 } = await t2.webauthnAlreadyRegistered({ username: n2 });
281
+ return i3 ? d(t2, n2, r2) : f(t2, n2, r2);
282
+ }
283
+ async function d(t2, n2, r2) {
284
+ var _a;
285
+ let i3 = await t2.webauthnAuthStart({ username: n2 });
286
+ if (!i3.challenge) throw new e("WebAuthn auth start: missing challenge");
287
+ let a2 = i3.challenge;
288
+ if (!((_a = a2.publicKey) == null ? void 0 : _a.challenge)) throw new e("WebAuthn auth start: malformed challenge response");
289
+ let { publicKey: o2 } = a2, s2 = c(o2.challenge), l2 = (o2.allowCredentials ?? []).map((e4) => ({
290
+ id: c(e4.id),
291
+ type: e4.type
292
+ })), u2 = await navigator.credentials.get({ publicKey: {
293
+ challenge: s2,
294
+ allowCredentials: l2,
295
+ timeout: 6e4,
296
+ userVerification: "required"
297
+ } });
298
+ if (!u2) throw new e("WebAuthn authentication was cancelled");
299
+ return t2.authRequest(r2, { WebAuthN: {
300
+ session_id: i3.session_id,
301
+ credential: u2
302
+ } });
303
+ }
304
+ async function f(t2, n2, r2) {
305
+ var _a, _b, _c;
306
+ let i3 = await t2.webauthnStartRegistration({ username: n2 });
307
+ if (!i3.public_key) throw new e("WebAuthn registration start: missing public_key");
308
+ let a2 = i3.public_key;
309
+ if (!a2.challenge) throw new e("WebAuthn registration start: malformed public_key response");
310
+ let o2 = c(a2.challenge), s2 = await navigator.credentials.create({ publicKey: {
311
+ rp: {
312
+ name: ((_a = a2.rp) == null ? void 0 : _a.name) ?? "Tari Wallet",
313
+ id: ((_b = a2.rp) == null ? void 0 : _b.id) ?? ((_c = globalThis.location) == null ? void 0 : _c.hostname)
314
+ },
315
+ user: {
316
+ id: Uint8Array.from(n2, (e4) => e4.charCodeAt(0)),
317
+ name: n2,
318
+ displayName: n2
319
+ },
320
+ challenge: o2,
321
+ pubKeyCredParams: a2.pubKeyCredParams ?? [
322
+ {
323
+ type: "public-key",
324
+ alg: -8
325
+ },
326
+ {
327
+ type: "public-key",
328
+ alg: -7
329
+ },
330
+ {
331
+ type: "public-key",
332
+ alg: -257
333
+ }
334
+ ],
335
+ timeout: 6e4,
336
+ excludeCredentials: [],
337
+ attestation: "none",
338
+ authenticatorSelection: { userVerification: "required" }
339
+ } });
340
+ if (!s2) throw new e("WebAuthn registration was cancelled");
341
+ let { token: l2 } = await t2.webauthnFinishRegistration({
342
+ session_id: i3.session_id,
343
+ credential: s2,
344
+ requested_permissions: r2
345
+ });
346
+ return l2;
347
+ }
348
+ var p = class i2 {
349
+ constructor(e4) {
350
+ __publicField(this, "client");
351
+ __publicField(this, "_publicKey", null);
352
+ __publicField(this, "_address", null);
353
+ this.client = e4;
354
+ }
355
+ static new(e4) {
356
+ let t2 = a.usingFetchTransport(e4.url);
357
+ return t2.setToken(e4.authToken), new i2(t2);
358
+ }
359
+ static async connect(e4) {
360
+ let t2 = a.usingFetchTransport(e4.url);
361
+ if (e4.authToken) t2.setToken(e4.authToken);
362
+ else {
363
+ let n3 = await l(t2, e4);
364
+ t2.setToken(n3);
365
+ }
366
+ t2.setReauthenticationEnabled(true);
367
+ let n2 = new i2(t2);
368
+ return await n2.fetchAccountInfo(), n2;
369
+ }
370
+ async getAddress() {
371
+ return (await this.ensureAccountInfo()).address;
372
+ }
373
+ async getPublicKey() {
374
+ return (await this.ensureAccountInfo()).publicKey;
375
+ }
376
+ async signTransaction(e4, n2) {
377
+ return t(n2, 32, "WalletDaemonSigner.signTransaction sealPublicKey"), (await this.client.sendRequest("transactions.sign", {
378
+ transaction: e4,
379
+ seal_public_key: r(n2)
380
+ })).signatures;
381
+ }
382
+ getViewSecret() {
383
+ return Promise.reject(new e("WalletDaemonSigner cannot export a view secret: daemon stealth is server-side. Use the daemon's stealth JRPC methods (e.g. stealthTransfer, stealthUtxosList), or use SecretKeyWallet for client-side stealth spending."));
384
+ }
385
+ async fetchAccountInfo() {
386
+ await this.ensureAccountInfo();
387
+ }
388
+ async ensureAccountInfo() {
389
+ var _a;
390
+ if (this._publicKey !== null && this._address !== null) return {
391
+ publicKey: this._publicKey,
392
+ address: this._address
393
+ };
394
+ let r2 = await this.client.accountsGetDefault({});
395
+ if (!((_a = r2.account) == null ? void 0 : _a.owner_public_key) || !r2.address) throw new e("Wallet daemon response missing public_key or address");
396
+ let i3 = t(n(r2.account.owner_public_key), 32, "daemon publicKey"), a2 = r2.address;
397
+ return this._publicKey = i3, this._address = a2, {
398
+ publicKey: i3,
399
+ address: a2
400
+ };
401
+ }
402
+ };
403
+ export {
404
+ a as WalletDaemonClient,
405
+ p as WalletDaemonSigner,
406
+ l as authenticate
407
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@tari-project/ootle-wallet-daemon-signer",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "browser": "./dist/index.js",
14
+ "node": "./dist/index.js",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "dependencies": {
20
+ "@tari-project/ootle-ts-bindings": "^1.30.0",
21
+ "@tari-project/wallet_jrpc_client": "^1.14.0",
22
+ "@tari-project/ootle": "0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@microsoft/api-extractor": "^7.57.7",
26
+ "@types/node": "^25.5.0",
27
+ "typescript": "^5.9.3",
28
+ "unplugin-dts": "^1.0.0-beta.6",
29
+ "vite": "^8.0.1",
30
+ "vite-plugin-top-level-await": "^1.6.0",
31
+ "vite-plugin-wasm": "^3.6.0",
32
+ "vitest": "^4.1.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -b && vite build",
36
+ "test": "vitest run --passWithNoTests"
37
+ }
38
+ }