Skip to content
204 changes: 204 additions & 0 deletions kip-0012.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# KIP-0012: Specification for Browser Extension Wallet APIs

<table>
<tr><td>Layer</td><td>WASM32 Wallet SDK, Browser Extension Wallet APIs</td></tr>
<tr><td>Title</td><td>Specification for Browser Extension Wallet APIs</td></tr>
<tr><td>Authors</td><td>@aspect, @starkbamse, @KaffinPX, @mattoo</td></tr>
<tr><td>Status</td><td>DRAFT / WIP</td></tr>
</table>

## Goals
- **Standardize Wallet APIs**: Define how wallets expose their functionalities to web apps.
- **Support Multiple Protocols**: Ensure compatibility with current and future Kaspa protocols.
- **Interoperability**: Allow seamless interaction between wallets and web apps through a uniform API structure.

## Motivation

The Kaspa ecosystem is witnessing the emergence of several Browser Extension Wallet standards for interfacing with web applications. This KIP aims to standardize the methodology by which wallets expose themselves to and communicate with these web applications.

Additionally, this KIP seeks to provide a common framework for wallets to showcase their capabilities, particularly in performing various functions and accommodating different emerging protocols. These protocols include, but are not limited to:

- **KRC-20 protocol** by Kasplex
- **KSC protocol** (future Sparkle Smart Contract assets)

This document is loosely based on Ethereum's [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963).

## Key Definitions

- **Wallet**: A browser extension capable of storing Kaspa-based assets.
- **Web App**: A browser-accessible application interacting with the wallet to access stored funds and execute operations.

## Specification

**The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC-2119](https://datatracker.ietf.org/doc/html/rfc2119).**

### Communication Events
Wallets and web apps communicate through the browser's `window` object using `CustomEvent` objects.

- **`kaspa:requestProvider`**: Request wallets to announce themselves.
- **`kaspa:provider`**: Announces the wallet's presence to the web app.
- **`kaspa:connect`**: Establishes connection between the wallet and the web app.
- **`kaspa:event`**: Used to send data from the wallet to the web app.
- **`kaspa:disconnect`**: Terminates the connection between wallet and web app.
- **`kaspa:send`**: Request to sign and broadcast a PSKB over the Kaspa network.
- **`kaspa:sign`**: Request to sign a PSKB.
- **`kaspa:broadcast`**: Request to broadcast a signed PSKB over the Kaspa network.

The wallet **MUST NOT** react to any requests except the **`kaspa:connect`** event. Only after an explicit connection request **MAY** the wallet react to subsequent requests made by the **Web App**.

### Example Event Flows

The following is an example event flow of sending a transaction.

|Sender||Event||Receiver|Interface|
|---|--|---|---|---|---|
| **Web App** | → | **Request Provider** (`kaspa:requestProvider`) | → | **Wallet** | `Notification`
| **Wallet** | → | **Provide Information** (`kaspa:provider`) | → | **Web App** | `Notification`
| **Web App** | → | **Connect** (`kaspa:connect`) | → | **Wallet** | `Request`
| **Wallet** | → | **Connect Confirmation** (`kaspa:event`) | → | **Web App** | `Response`
| **Web App** | → | **Send & Sign PSKB** (`kaspa:send`) | → | **Wallet** | `Request`
| **Wallet** | → | **Send Response** (`kaspa:event`) | → | **Web App** | `Response`
| **Web App / Wallet** | → | **Disconnect** (`kaspa:disconnect`) | → | **Wallet / Web App** | `Notification`

|Sender||Event||Receiver|Interface|
|---|--|---|---|---|---|
| **Web App** | → | **Request Provider** (`kaspa:requestProvider`) | → | **Wallet** | `Notification`
| **Wallet** | → | **Provide Information** (`kaspa:provider`) | → | **Web App** | `Notification`
| **Web App** | → | **Connect** (`kaspa:connect`) | → | **Wallet** | `Request`
| **Wallet** | → | **Connect Confirmation** (`kaspa:event`) | → | **Web App** | `Response`
| **Web App** | → | **Sign PSKB** (`kaspa:sign`) | → | **Wallet** | `Request`
| **Wallet** | → | **Send back signed PSKB** (`kaspa:event`) | → | **Web App** | `Response`
| **Web App** | → | **Broadcast signed PSKB** (`kaspa:broadcast`) | → | **Wallet** | `Request`
| **Web App / Wallet** | → | **Disconnect** (`kaspa:disconnect`) | → | **Wallet / Web App** | `Notification`



### Interface Definitions

```typescript
export interface KaspaProvider {
request: (method:string,args:any[]) => Promise<any>;
connect: () => Promise<void>;
disconnect: () => Promise<void>;
}


export interface ProviderInfo {
id: string;
name: string;
icon: string;
methods: string[];
}

export interface Message {
eventId: string;
extensionId: string;
method: string;
args?: any[];
error?: any;
}

declare global {
interface Window {
kaspaProvider: KaspaProvider;
}
}
```

### Icons/Images
The icon string **MUST** be a data URI as defined in [RFC-2397](https://datatracker.ietf.org/doc/html/rfc2397). The image **SHOULD** be a square with 96x96px minimum resolution. The image format is **RECOMMENDED** to be either lossless or vector-based such as PNG, WebP, or SVG to make the image easy to render on the Web App. Since SVG images can execute Javascript, applications and libraries **MUST** render SVG images using the <img> tag to ensure no untrusted Javascript execution can occur.

### Implementation Example

```typescript
export function script(providerInfo: ProviderInfo, extensionId: string, uuid: string) {
function generateUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = crypto.getRandomValues(new Uint8Array(1))[0] & 15;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

const kaspaProvider: KaspaProvider = {
request: async (method:string,args:any[]): Promise<any> => {
return new Promise((resolve, reject) => {
const eventId = generateUUID();

const message: Message = {
eventId,
extensionId,
method,
args
};

const handleResponse = (event: CustomEvent) => {
const response = event.detail;
if (response.method === "kaspa:event" &&
response.eventId === eventId) {
window.removeEventListener(uuid, handleResponse);
if (response.error) {
reject(response.error);
} else {
resolve(response.data?.data);
}
}
};
window.addEventListener(uuid, handleResponse);
window.dispatchEvent(new CustomEvent(uuid, { detail: message }));
});
},

connect: async (): Promise<void> => {
return new Promise((resolve, reject) => {
const eventId = generateUUID();

const message: Message = {
eventId,
extensionId,
method: "kaspa:connect",
};

const handleResponse = (event: CustomEvent) => {
const response = event.detail;
if (response.method === "kaspa:event" &&
response.eventId === eventId) {
window.removeEventListener(uuid, handleResponse);
if (response.error) {
reject(response.error);
} else {
resolve();
}
}
};
window.addEventListener(uuid, handleResponse);
window.dispatchEvent(new CustomEvent(uuid, { detail: message }));
});
},

disconnect: async (): Promise<void> => {
const message: Message = {
eventId: generateUUID(),
extensionId,
method: "kaspa:disconnect"
};
window.dispatchEvent(new CustomEvent(uuid, { detail: message }));
}
};

// Listen for provider requests from web apps
window.addEventListener("kaspa:requestProvider", () => {
window.dispatchEvent(new CustomEvent("kaspa:provider", {
detail: Object.freeze({
info: providerInfo,
provider: kaspaProvider
})
}));
});

// Listen for responses from the extension
window.addEventListener("message", (event: MessageEvent) => {
// Handle events from the extension such as subscription events
});
}
```
Empty file added kip-0012/KRC-20.md
Empty file.