kaspa-wallet-standard 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 Shawn Pearce
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,115 @@
1
+ # Kaspa Wallet Standard
2
+
3
+ **A proposed standard for dApp↔wallet interoperability on Kaspa.** One tiny, zero-dependency handshake so
4
+ any dApp can find any wallet — and any wallet can join every dApp — without either side hardcoding the
5
+ other. Inspired by Ethereum's [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) and Solana's
6
+ [Wallet Standard](https://github.com/wallet-standard/wallet-standard).
7
+
8
+ > **Status: proposed / draft**, open for community adoption and headed for a [KIP](https://github.com/kaspanet/kips).
9
+ > The full specification is in **[SPEC.md](SPEC.md)**. The wire contract is frozen — see SPEC §7.
10
+
11
+ ```bash
12
+ npm install kaspa-wallet-standard
13
+ ```
14
+
15
+ ## Why
16
+
17
+ Today every Kaspa dApp hardcodes each wallet's injected global (`window.kasware`, `window.kastle`, …) one
18
+ at a time, and every new wallet has to lobby every dApp to be added. This package replaces that with a
19
+ two-event handshake: wallets **announce** themselves, dApps **request** announcements. A wallet that ships
20
+ this appears in every adopting dApp automatically; a dApp that ships this lists every present wallet —
21
+ including ones that didn't exist when it was built.
22
+
23
+ ## Wallet side — announce yourself
24
+
25
+ ```ts
26
+ import { announceKaspaWallet } from 'kaspa-wallet-standard';
27
+
28
+ announceKaspaWallet(
29
+ {
30
+ uuid: crypto.randomUUID(), // fresh per page load
31
+ name: 'YourWallet',
32
+ icon: 'data:image/svg+xml;base64,…', // data: URI only
33
+ rdns: 'com.yourwallet', // STABLE id — enables silent session restore after reload
34
+ },
35
+ provider, // your provider object (see below)
36
+ );
37
+ ```
38
+
39
+ `provider` only needs `requestAccounts()`; everything else (`getAccounts`, `getNetwork`/`switchNetwork`,
40
+ `getPublicKey`, `signMessage`, `signPskt`, events) is optional and capability-checked by the dApp. See
41
+ [SPEC §3](SPEC.md#3-provider-interface).
42
+
43
+ ### If your wallet uses a `request(method, params)` bridge
44
+
45
+ Some wallets (e.g. Kastle) expose a single `request()` bridge instead of discrete methods. Wrap it in a
46
+ thin object that satisfies the interface — a few lines:
47
+
48
+ ```ts
49
+ const w = window.yourwallet; // { request(method, params), on, removeListener }
50
+ const provider = {
51
+ requestAccounts: () => w.request('kas:connect').then(() => w.request('kas:get_account')).then(a => [a.address]),
52
+ getAccounts: () => w.request('kas:get_account').then(a => [a.address]).catch(() => []),
53
+ getNetwork: () => w.request('kas:get_network'),
54
+ switchNetwork: (id) => w.request('kas:switch_network', id),
55
+ getPublicKey: () => w.request('kas:get_account').then(a => a.publicKey),
56
+ signMessage: (m) => w.request('kas:sign_message', m),
57
+ signPskt: ({ txJsonString, options }) => w.request('kas:sign_tx', {
58
+ networkId: /* your network id */ 'kaspa_testnet_10',
59
+ txJson: txJsonString,
60
+ scripts: options.signInputs.map(s => ({ inputIndex: s.index, scriptHex: '', signType: 'All' })),
61
+ }),
62
+ on: w.on?.bind(w),
63
+ removeListener: w.removeListener?.bind(w),
64
+ };
65
+ announceKaspaWallet(info, provider);
66
+ ```
67
+
68
+ ### No dependency? ~10 lines of raw JS
69
+
70
+ The package is a convenience, not a requirement. The handshake is small enough to inline:
71
+
72
+ ```js
73
+ const detail = Object.freeze({
74
+ info: Object.freeze({ uuid: crypto.randomUUID(), name: 'YourWallet', icon: 'data:…', rdns: 'com.yourwallet' }),
75
+ provider: window.yourwallet,
76
+ });
77
+ const announce = () => window.dispatchEvent(new CustomEvent('kaspa:announceProvider', { detail }));
78
+ window.addEventListener('kaspa:requestProvider', announce);
79
+ announce();
80
+ ```
81
+
82
+ ## dApp side — discover wallets
83
+
84
+ ```ts
85
+ import { requestKaspaWallets } from 'kaspa-wallet-standard';
86
+
87
+ const wallets = new Map(); // dedupe by rdns ?? uuid
88
+ const unsubscribe = requestKaspaWallets(({ info, provider }) => {
89
+ wallets.set(info.rdns ?? info.uuid, { info, provider });
90
+ renderWalletPicker([...wallets.values()]); // each has info.name, info.icon, and provider
91
+ });
92
+ // keep the subscription alive for the page lifetime to catch late-injecting wallets
93
+ ```
94
+
95
+ Then on click: `const [address] = await provider.requestAccounts();`. Capability-check optional methods
96
+ before using them, and refuse any `info.icon` that isn't a `data:` URI.
97
+
98
+ ## Who's using it
99
+
100
+ - **[KRON](https://kron.technology)** — native-L1 Kaspa launchpad + DEX. First production adopter;
101
+ consumes the discovery handshake and ships built-in adapters for KasWare and Kastle behind this
102
+ provider interface.
103
+
104
+ Using it in your wallet or dApp? Open a PR to add yourself.
105
+
106
+ ## Status & contributing
107
+
108
+ This is a **proposed** standard. The goal is ratification as a KIP once the handshake is proven across at
109
+ least two independently-developed wallets. If you're a wallet or dApp author — especially if you'd want a
110
+ field changed **before** it freezes into a KIP — please [open an issue](../../issues). See
111
+ [SPEC.md](SPEC.md) for the full contract, security model, and versioning policy.
112
+
113
+ ## License
114
+
115
+ [MIT](LICENSE)
package/SPEC.md ADDED
@@ -0,0 +1,144 @@
1
+ # Kaspa Wallet Standard
2
+
3
+ **Status: Proposed (draft).** This is an open proposal for how Kaspa dApps and wallets discover and talk
4
+ to each other. It is **not** (yet) a ratified [Kaspa Improvement Proposal](https://github.com/kaspanet/kips) —
5
+ it is offered for community adoption and feedback, with the explicit goal of becoming a KIP once it has
6
+ proven out across independent wallets. Comments, issues, and PRs are welcome.
7
+
8
+ **Version:** 0.1 · **Wire contract:** frozen (see §7).
9
+
10
+ ---
11
+
12
+ ## 1. Motivation
13
+
14
+ Kaspa has several good wallets (KasWare, Kastle, and more), but **no shared way for a dApp to connect to
15
+ them**. Today each dApp hardcodes each wallet's injected global (`window.kasware`, `window.kastle`, …)
16
+ one at a time, and each new wallet has to lobby every dApp to add it. This is the same dead end Ethereum
17
+ hit before [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) (a common provider shape) and
18
+ [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) (multi-wallet discovery), and that Solana solved with
19
+ its [Wallet Standard](https://github.com/wallet-standard/wallet-standard).
20
+
21
+ This standard removes the hardcoding on **both** sides:
22
+
23
+ - A **dApp** listens for one event and shows every wallet that answers — including wallets that did not
24
+ exist when the dApp shipped.
25
+ - A **wallet** dispatches one event and appears in every adopting dApp — no per-dApp integration.
26
+
27
+ It has two parts: a **provider interface** (§3–4) and a **discovery handshake** (§5).
28
+
29
+ ## 2. Terminology
30
+
31
+ - **Provider** — the object a wallet injects/exposes that a dApp calls to get accounts, network, and
32
+ signatures (§3).
33
+ - **Announce** — a wallet advertising its provider via the `kaspa:announceProvider` event (§5).
34
+ - **dApp** — any web page that wants to use a Kaspa wallet.
35
+ - Keywords **MUST**, **SHOULD**, **MAY** are used per [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
36
+
37
+ ## 3. Provider interface
38
+
39
+ A provider is an object with the following shape (TypeScript; see
40
+ [`src/index.ts`](src/index.ts) `KaspaProvider`). Only `requestAccounts` is **mandatory**; every other
41
+ method is **optional** and MUST be capability-checked by the dApp (`typeof p.signPskt === 'function'`).
42
+
43
+ | Method | Required | Purpose |
44
+ |---|---|---|
45
+ | `requestAccounts(): Promise<string[]>` | **yes** | Connect (prompt if needed); resolve to authorized addresses, active first. |
46
+ | `getAccounts(): Promise<string[]>` | no | Already-authorized accounts **without** prompting → silent session restore. |
47
+ | `getNetwork(): Promise<string>` | no | Current network id (§6). |
48
+ | `switchNetwork(id): Promise<void>` | no | Request a network switch. |
49
+ | `getPublicKey(): Promise<string>` | no | Active account public key hex (compressed or x-only). |
50
+ | `signMessage(msg): Promise<string>` | no | KIP-5 message signing → Schnorr signature hex. |
51
+ | `signPskt({ txJsonString, options }): Promise<string>` | no | Sign specific inputs of a transaction (§4). |
52
+ | `disconnect(origin?): Promise<void>` | no | Drop the site's authorization. |
53
+ | `on / removeListener(event, handler)` | no | `accountsChanged`, `networkChanged`. |
54
+
55
+ A wallet that implements only `requestAccounts` is valid — it will connect and display balances in a
56
+ dApp, which simply disables features that need the missing methods.
57
+
58
+ ## 4. Transaction signing (`signPskt`) — fund-safety rules
59
+
60
+ `signPskt(txJsonString, options)` takes a Kaspa **Safe-JSON** transaction and an `options.signInputs`
61
+ array of `{ index, sighashType }`, and returns the re-serialized signed transaction.
62
+
63
+ - The wallet **MUST** sign **only** the inputs listed in `signInputs`, and **MUST** leave all other
64
+ inputs untouched. Kaspa covenant transactions carry inputs that are pre-authorized by on-chain rules
65
+ (a curve, a pool, a presence-owned token UTXO); re-signing one corrupts the transaction.
66
+ - The wallet **MUST** honor the requested `sighashType` (1 = `SIGHASH_ALL`) and **MUST** refuse a type
67
+ it does not implement rather than substitute another. A signature over the wrong sighash, or over an
68
+ input the dApp did not list, is a **fund-safety defect**, not an API mismatch.
69
+ - Before enabling `signPskt`, a wallet **SHOULD** verify it against a transaction that contains **both**
70
+ a covenant input and a user P2PK input (e.g. a bonding-curve buy) — signing a plain send never
71
+ exercises the "sign only these, leave the rest" requirement.
72
+
73
+ ## 5. Discovery handshake
74
+
75
+ Two events on `window`, mirroring EIP-6963 replay semantics:
76
+
77
+ - **`kaspa:announceProvider`** — a `CustomEvent` dispatched by the **wallet**. `detail` is a **frozen**
78
+ `{ info, provider }` (see §5.1). MUST be dispatched on load, and re-dispatched on every
79
+ `kaspa:requestProvider`.
80
+ - **`kaspa:requestProvider`** — a plain `Event` dispatched by the **dApp** to ask present wallets to
81
+ (re-)announce.
82
+
83
+ **Sequence**
84
+
85
+ 1. On load, the wallet registers a `kaspa:requestProvider` listener that re-announces, then announces once.
86
+ 2. The dApp registers its `kaspa:announceProvider` listener (kept alive for the page lifetime), then
87
+ dispatches `kaspa:requestProvider`.
88
+ 3. Late arrival is covered from both directions: a late wallet announces unprompted on load; a late dApp's
89
+ request triggers a replay from every wallet already present.
90
+
91
+ ### 5.1 Announce payload
92
+
93
+ ```ts
94
+ type KaspaProviderInfo = {
95
+ uuid: string; // UUIDv4, fresh per page load — instance identity / dedupe
96
+ name: string; // human label, e.g. "Kastle"
97
+ icon: string; // data: URI (SVG/PNG); dApps MUST refuse remote URLs
98
+ rdns?: string; // reverse-DNS id, e.g. "com.kasware" — STABLE across loads; used for session restore
99
+ };
100
+ type KaspaProviderDetail = { info: KaspaProviderInfo; provider: KaspaProvider };
101
+ ```
102
+
103
+ - The wallet **MUST** freeze `detail` (and `detail.info`) so page scripts cannot swap the provider out.
104
+ - The wallet **SHOULD** provide a stable `rdns`; without it a dApp cannot silently restore the session
105
+ after a reload (there is no stable identity to match).
106
+ - The dApp **SHOULD** dedupe announces by `info.rdns ?? info.uuid`, first-announce-wins.
107
+
108
+ ## 6. Network ids
109
+
110
+ Canonical strings (also exported as `KASPA_NETWORKS`):
111
+
112
+ `kaspa_mainnet` · `kaspa_testnet_10` · `kaspa_testnet_11` · `kaspa_devnet`
113
+
114
+ ## 7. Compatibility and versioning (frozen contract)
115
+
116
+ The **wire contract is frozen**: the two event names, and every field defined above, never change
117
+ meaning or type. The standard evolves **only by adding new OPTIONAL fields/methods**. A wallet or dApp
118
+ built against this document keeps working against every future version. Breaking changes, if ever
119
+ unavoidable, would ship under a **new event name** — never by mutating these.
120
+
121
+ ## 8. Security considerations
122
+
123
+ - `name`/`icon` are **display hints, not trust signals**. An announce proves a provider is *present*, not
124
+ that it is *who it claims to be*. dApps MUST NOT grant trust based on them, and MUST refuse non-`data:`
125
+ icons (a remote URL is a tracking/spoofing vector).
126
+ - Any page script can dispatch `kaspa:announceProvider`. Treat the provider as untrusted until the user
127
+ explicitly connects; the connect prompt is the trust boundary.
128
+ - The fund-safety rules in §4 are the load-bearing security property. A wallet that signs sloppily can
129
+ lose user funds even though the handshake itself is benign.
130
+
131
+ ## 9. Reference implementation & adoption
132
+
133
+ - **Reference implementation:** this package (`kaspa-wallet-standard`) — `announceKaspaWallet` (wallet)
134
+ and `requestKaspaWallets` (dApp), plus the types above. ~70 lines, zero dependencies.
135
+ - **First adopter:** [KRON](https://kron.technology) (native-L1 Kaspa launchpad + DEX) consumes the
136
+ discovery handshake in production, and ships built-in adapters for KasWare and Kastle behind the same
137
+ provider interface.
138
+
139
+ ## 10. Path to standardization
140
+
141
+ This document is intended to graduate into a **KIP**. The bar we're aiming for before proposing
142
+ ratification: the handshake proven across **at least two independently-developed wallets**, and the
143
+ provider interface exercised by covenant-grade `signPskt` on-chain. Wallet and dApp authors who adopt it
144
+ (or who want fields changed *before* it freezes into a KIP) are invited to open an issue.
@@ -0,0 +1,88 @@
1
+ /** Canonical network ids used by `getNetwork()` / `switchNetwork()`. String literals are the contract;
2
+ * this object is a convenience so integrators don't hand-type them. */
3
+ declare const KASPA_NETWORKS: {
4
+ readonly MAINNET: "kaspa_mainnet";
5
+ readonly TESTNET_10: "kaspa_testnet_10";
6
+ readonly TESTNET_11: "kaspa_testnet_11";
7
+ readonly DEVNET: "kaspa_devnet";
8
+ };
9
+ type KaspaNetworkId = (typeof KASPA_NETWORKS)[keyof typeof KASPA_NETWORKS];
10
+ /** Identity a wallet announces about itself. `name`/`icon` are DISPLAY hints — never trust signals. */
11
+ type KaspaProviderInfo = {
12
+ /** UUIDv4, freshly generated per page load — instance identity, used only for dedupe. */
13
+ uuid: string;
14
+ /** Human-readable wallet name shown in pickers, e.g. "Kastle". */
15
+ name: string;
16
+ /** Wallet icon as a `data:` URI (SVG/PNG). dApps should refuse to render remote URLs. */
17
+ icon: string;
18
+ /** Reverse-DNS identifier, e.g. "com.kasware" — STABLE across page loads and versions. Strongly
19
+ * recommended: it is what lets a dApp silently restore a session with your wallet after a reload. */
20
+ rdns?: string;
21
+ };
22
+ /** One input a wallet is asked to sign, by position. `sighashType` 1 = SIGHASH_ALL (the only value
23
+ * KRON uses today); a wallet MUST refuse a type it does not implement rather than guess. */
24
+ type KaspaSignInput = {
25
+ index: number;
26
+ sighashType: number;
27
+ };
28
+ /**
29
+ * The raw provider surface a wallet exposes. Deliberately shaped like the widely-deployed injected APIs
30
+ * (KasWare's `window.kasware`) so an existing wallet can usually announce its injected object as-is.
31
+ *
32
+ * Only `requestAccounts` is MANDATORY. Everything else is OPTIONAL and MUST be capability-checked by the
33
+ * dApp (`typeof provider.signPskt === 'function'`) — a dApp degrades gracefully (e.g. disables trading)
34
+ * when a method is absent, rather than refusing to list the wallet.
35
+ *
36
+ * FUND-SAFETY RULE (wallet side): `signPskt` MUST sign ONLY the inputs listed in `options.signInputs`,
37
+ * and MUST leave every other input untouched. Kaspa covenant transactions carry pre-authorized inputs
38
+ * that must NOT be re-signed; a signature over an unlisted input, or over the wrong sighash, is a
39
+ * fund-safety bug, not a cosmetic mismatch. See SPEC.md §"Security considerations".
40
+ */
41
+ interface KaspaProvider {
42
+ /** Connect: prompt the user if needed; resolve to the authorized address list (active address first). */
43
+ requestAccounts(): Promise<string[]>;
44
+ /** Already-authorized accounts WITHOUT prompting (empty array if none) — enables silent session restore. */
45
+ getAccounts?(): Promise<string[]>;
46
+ /** Current network id (see {@link KaspaNetworkId}). */
47
+ getNetwork?(): Promise<string>;
48
+ switchNetwork?(networkId: string): Promise<void>;
49
+ /** The active account's public key hex (compressed 33-byte or x-only 32-byte — both accepted). */
50
+ getPublicKey?(): Promise<string>;
51
+ /** KIP-5 message signing; resolves to the Schnorr signature hex. */
52
+ signMessage?(message: string): Promise<string>;
53
+ /** Sign ONLY the listed inputs of a Kaspa Safe-JSON transaction and return the signed Safe JSON. */
54
+ signPskt?(arg: {
55
+ txJsonString: string;
56
+ options: {
57
+ signInputs: KaspaSignInput[];
58
+ };
59
+ }): Promise<string>;
60
+ disconnect?(origin?: string): Promise<void>;
61
+ on?(event: 'accountsChanged' | 'networkChanged', handler: (...args: any[]) => void): void;
62
+ removeListener?(event: string, handler: (...args: any[]) => void): void;
63
+ }
64
+ /** Dispatched on `window` by a dApp to ask all present wallets to (re-)announce themselves. */
65
+ declare const KASPA_REQUEST_PROVIDER_EVENT = "kaspa:requestProvider";
66
+ /** Dispatched on `window` by a wallet; `detail` is a frozen {@link KaspaProviderDetail}. */
67
+ declare const KASPA_ANNOUNCE_PROVIDER_EVENT = "kaspa:announceProvider";
68
+ /** The `detail` of a `kaspa:announceProvider` CustomEvent. */
69
+ type KaspaProviderDetail = {
70
+ info: KaspaProviderInfo;
71
+ provider: KaspaProvider;
72
+ };
73
+ /**
74
+ * WALLET SIDE — call once when your content script loads. Announces immediately AND replays on every
75
+ * `kaspa:requestProvider` (so a dApp that loads after you still finds you). Returns an unsubscribe
76
+ * (rarely needed; e.g. extension teardown). No-op outside a window context.
77
+ */
78
+ declare function announceKaspaWallet(info: KaspaProviderInfo, provider: KaspaProvider): () => void;
79
+ /**
80
+ * dApp SIDE — register `onAnnounce` (fires once per announce event, including replays; dedupe by
81
+ * `info.rdns ?? info.uuid` yourself), then request announcements from wallets already present. Keep the
82
+ * subscription alive for the page lifetime to catch late-injecting wallets. Returns an unsubscribe.
83
+ * Malformed announces (missing identity or `requestAccounts`) are dropped, never delivered. No-op
84
+ * outside a window context.
85
+ */
86
+ declare function requestKaspaWallets(onAnnounce: (detail: KaspaProviderDetail) => void): () => void;
87
+
88
+ export { KASPA_ANNOUNCE_PROVIDER_EVENT, KASPA_NETWORKS, KASPA_REQUEST_PROVIDER_EVENT, type KaspaNetworkId, type KaspaProvider, type KaspaProviderDetail, type KaspaProviderInfo, type KaspaSignInput, announceKaspaWallet, requestKaspaWallets };
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ // src/index.ts
2
+ var KASPA_NETWORKS = {
3
+ MAINNET: "kaspa_mainnet",
4
+ TESTNET_10: "kaspa_testnet_10",
5
+ TESTNET_11: "kaspa_testnet_11",
6
+ DEVNET: "kaspa_devnet"
7
+ };
8
+ var KASPA_REQUEST_PROVIDER_EVENT = "kaspa:requestProvider";
9
+ var KASPA_ANNOUNCE_PROVIDER_EVENT = "kaspa:announceProvider";
10
+ function announceKaspaWallet(info, provider) {
11
+ if (typeof window === "undefined") return () => {
12
+ };
13
+ const detail = Object.freeze({ info: Object.freeze({ ...info }), provider });
14
+ const announce = () => window.dispatchEvent(new CustomEvent(KASPA_ANNOUNCE_PROVIDER_EVENT, { detail }));
15
+ window.addEventListener(KASPA_REQUEST_PROVIDER_EVENT, announce);
16
+ announce();
17
+ return () => window.removeEventListener(KASPA_REQUEST_PROVIDER_EVENT, announce);
18
+ }
19
+ function requestKaspaWallets(onAnnounce) {
20
+ if (typeof window === "undefined") return () => {
21
+ };
22
+ const listener = (e) => {
23
+ const detail = e.detail;
24
+ if (!detail?.info?.uuid || !detail?.info?.name) return;
25
+ if (typeof detail.provider?.requestAccounts !== "function") return;
26
+ onAnnounce(detail);
27
+ };
28
+ window.addEventListener(KASPA_ANNOUNCE_PROVIDER_EVENT, listener);
29
+ window.dispatchEvent(new Event(KASPA_REQUEST_PROVIDER_EVENT));
30
+ return () => window.removeEventListener(KASPA_ANNOUNCE_PROVIDER_EVENT, listener);
31
+ }
32
+ export {
33
+ KASPA_ANNOUNCE_PROVIDER_EVENT,
34
+ KASPA_NETWORKS,
35
+ KASPA_REQUEST_PROVIDER_EVENT,
36
+ announceKaspaWallet,
37
+ requestKaspaWallets
38
+ };
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Kaspa Wallet Standard — reference implementation.\n//\n// A PROPOSED standard (see SPEC.md) for how a Kaspa dApp and a Kaspa wallet find and talk to each other,\n// with two parts:\n// • Part 1 — the provider interface a wallet exposes (accounts, network, signing).\n// • Part 2 — an EIP-6963-style discovery handshake so a dApp finds every present wallet with zero\n// per-wallet code, and any future wallet self-registers by dispatching one event.\n//\n// This package has ZERO runtime dependencies and is safe to import in Node (all browser access is guarded).\n// The wire contract — the two event names and the existing fields below — is FROZEN: it never changes.\n// Evolution happens only by adding new OPTIONAL fields. A wallet written against this file keeps working\n// against every future version.\n\n// ============================================================================================\n// Part 1 — Provider interface\n// ============================================================================================\n\n/** Canonical network ids used by `getNetwork()` / `switchNetwork()`. String literals are the contract;\n * this object is a convenience so integrators don't hand-type them. */\nexport const KASPA_NETWORKS = {\n MAINNET: 'kaspa_mainnet',\n TESTNET_10: 'kaspa_testnet_10',\n TESTNET_11: 'kaspa_testnet_11',\n DEVNET: 'kaspa_devnet',\n} as const;\n\nexport type KaspaNetworkId = (typeof KASPA_NETWORKS)[keyof typeof KASPA_NETWORKS];\n\n/** Identity a wallet announces about itself. `name`/`icon` are DISPLAY hints — never trust signals. */\nexport type KaspaProviderInfo = {\n /** UUIDv4, freshly generated per page load — instance identity, used only for dedupe. */\n uuid: string;\n /** Human-readable wallet name shown in pickers, e.g. \"Kastle\". */\n name: string;\n /** Wallet icon as a `data:` URI (SVG/PNG). dApps should refuse to render remote URLs. */\n icon: string;\n /** Reverse-DNS identifier, e.g. \"com.kasware\" — STABLE across page loads and versions. Strongly\n * recommended: it is what lets a dApp silently restore a session with your wallet after a reload. */\n rdns?: string;\n};\n\n/** One input a wallet is asked to sign, by position. `sighashType` 1 = SIGHASH_ALL (the only value\n * KRON uses today); a wallet MUST refuse a type it does not implement rather than guess. */\nexport type KaspaSignInput = { index: number; sighashType: number };\n\n/**\n * The raw provider surface a wallet exposes. Deliberately shaped like the widely-deployed injected APIs\n * (KasWare's `window.kasware`) so an existing wallet can usually announce its injected object as-is.\n *\n * Only `requestAccounts` is MANDATORY. Everything else is OPTIONAL and MUST be capability-checked by the\n * dApp (`typeof provider.signPskt === 'function'`) — a dApp degrades gracefully (e.g. disables trading)\n * when a method is absent, rather than refusing to list the wallet.\n *\n * FUND-SAFETY RULE (wallet side): `signPskt` MUST sign ONLY the inputs listed in `options.signInputs`,\n * and MUST leave every other input untouched. Kaspa covenant transactions carry pre-authorized inputs\n * that must NOT be re-signed; a signature over an unlisted input, or over the wrong sighash, is a\n * fund-safety bug, not a cosmetic mismatch. See SPEC.md §\"Security considerations\".\n */\nexport interface KaspaProvider {\n /** Connect: prompt the user if needed; resolve to the authorized address list (active address first). */\n requestAccounts(): Promise<string[]>;\n /** Already-authorized accounts WITHOUT prompting (empty array if none) — enables silent session restore. */\n getAccounts?(): Promise<string[]>;\n /** Current network id (see {@link KaspaNetworkId}). */\n getNetwork?(): Promise<string>;\n switchNetwork?(networkId: string): Promise<void>;\n /** The active account's public key hex (compressed 33-byte or x-only 32-byte — both accepted). */\n getPublicKey?(): Promise<string>;\n /** KIP-5 message signing; resolves to the Schnorr signature hex. */\n signMessage?(message: string): Promise<string>;\n /** Sign ONLY the listed inputs of a Kaspa Safe-JSON transaction and return the signed Safe JSON. */\n signPskt?(arg: { txJsonString: string; options: { signInputs: KaspaSignInput[] } }): Promise<string>;\n disconnect?(origin?: string): Promise<void>;\n on?(event: 'accountsChanged' | 'networkChanged', handler: (...args: any[]) => void): void;\n removeListener?(event: string, handler: (...args: any[]) => void): void;\n}\n\n// ============================================================================================\n// Part 2 — Discovery handshake\n// ============================================================================================\n\n/** Dispatched on `window` by a dApp to ask all present wallets to (re-)announce themselves. */\nexport const KASPA_REQUEST_PROVIDER_EVENT = 'kaspa:requestProvider';\n/** Dispatched on `window` by a wallet; `detail` is a frozen {@link KaspaProviderDetail}. */\nexport const KASPA_ANNOUNCE_PROVIDER_EVENT = 'kaspa:announceProvider';\n\n/** The `detail` of a `kaspa:announceProvider` CustomEvent. */\nexport type KaspaProviderDetail = { info: KaspaProviderInfo; provider: KaspaProvider };\n\n/**\n * WALLET SIDE — call once when your content script loads. Announces immediately AND replays on every\n * `kaspa:requestProvider` (so a dApp that loads after you still finds you). Returns an unsubscribe\n * (rarely needed; e.g. extension teardown). No-op outside a window context.\n */\nexport function announceKaspaWallet(info: KaspaProviderInfo, provider: KaspaProvider): () => void {\n if (typeof window === 'undefined') return () => {};\n const detail: KaspaProviderDetail = Object.freeze({ info: Object.freeze({ ...info }), provider });\n const announce = () =>\n window.dispatchEvent(new CustomEvent(KASPA_ANNOUNCE_PROVIDER_EVENT, { detail }));\n window.addEventListener(KASPA_REQUEST_PROVIDER_EVENT, announce);\n announce();\n return () => window.removeEventListener(KASPA_REQUEST_PROVIDER_EVENT, announce);\n}\n\n/**\n * dApp SIDE — register `onAnnounce` (fires once per announce event, including replays; dedupe by\n * `info.rdns ?? info.uuid` yourself), then request announcements from wallets already present. Keep the\n * subscription alive for the page lifetime to catch late-injecting wallets. Returns an unsubscribe.\n * Malformed announces (missing identity or `requestAccounts`) are dropped, never delivered. No-op\n * outside a window context.\n */\nexport function requestKaspaWallets(onAnnounce: (detail: KaspaProviderDetail) => void): () => void {\n if (typeof window === 'undefined') return () => {};\n const listener = (e: Event) => {\n const detail = (e as CustomEvent<KaspaProviderDetail>).detail;\n if (!detail?.info?.uuid || !detail?.info?.name) return;\n if (typeof detail.provider?.requestAccounts !== 'function') return;\n onAnnounce(detail);\n };\n window.addEventListener(KASPA_ANNOUNCE_PROVIDER_EVENT, listener);\n window.dispatchEvent(new Event(KASPA_REQUEST_PROVIDER_EVENT));\n return () => window.removeEventListener(KASPA_ANNOUNCE_PROVIDER_EVENT, listener);\n}\n"],"mappings":";AAmBO,IAAM,iBAAiB;AAAA,EAC5B,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AACV;AA0DO,IAAM,+BAA+B;AAErC,IAAM,gCAAgC;AAUtC,SAAS,oBAAoB,MAAyB,UAAqC;AAChG,MAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,EAAC;AACjD,QAAM,SAA8B,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,SAAS,CAAC;AAChG,QAAM,WAAW,MACf,OAAO,cAAc,IAAI,YAAY,+BAA+B,EAAE,OAAO,CAAC,CAAC;AACjF,SAAO,iBAAiB,8BAA8B,QAAQ;AAC9D,WAAS;AACT,SAAO,MAAM,OAAO,oBAAoB,8BAA8B,QAAQ;AAChF;AASO,SAAS,oBAAoB,YAA+D;AACjG,MAAI,OAAO,WAAW,YAAa,QAAO,MAAM;AAAA,EAAC;AACjD,QAAM,WAAW,CAAC,MAAa;AAC7B,UAAM,SAAU,EAAuC;AACvD,QAAI,CAAC,QAAQ,MAAM,QAAQ,CAAC,QAAQ,MAAM,KAAM;AAChD,QAAI,OAAO,OAAO,UAAU,oBAAoB,WAAY;AAC5D,eAAW,MAAM;AAAA,EACnB;AACA,SAAO,iBAAiB,+BAA+B,QAAQ;AAC/D,SAAO,cAAc,IAAI,MAAM,4BAA4B,CAAC;AAC5D,SAAO,MAAM,OAAO,oBAAoB,+BAA+B,QAAQ;AACjF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "kaspa-wallet-standard",
3
+ "version": "0.1.0",
4
+ "description": "A proposed standard for dApp↔wallet interoperability on Kaspa: a provider interface plus an EIP-6963-style discovery handshake so any wallet can announce itself to any dApp. Zero dependencies.",
5
+ "license": "MIT",
6
+ "author": "Shawn Pearce",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "SPEC.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "prepublishOnly": "npm run build && npm test"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "devDependencies": {
32
+ "happy-dom": "^15.11.6",
33
+ "tsup": "^8.3.5",
34
+ "typescript": "^5.6.3",
35
+ "vitest": "^2.1.8"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/kaspa-wallet-standard/kaspa-wallet-standard"
43
+ },
44
+ "homepage": "https://github.com/kaspa-wallet-standard/kaspa-wallet-standard#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/kaspa-wallet-standard/kaspa-wallet-standard/issues"
47
+ },
48
+ "keywords": [
49
+ "kaspa",
50
+ "wallet",
51
+ "standard",
52
+ "dapp",
53
+ "provider",
54
+ "discovery",
55
+ "eip-6963",
56
+ "kip"
57
+ ]
58
+ }