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 +21 -0
- package/README.md +115 -0
- package/SPEC.md +144 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|