@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 +29 -0
- package/README.md +102 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +407 -0
- package/package.json +38 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|