From 2ef0800251d157e3a6584c98472bca0863d6879c Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:32:15 +0000 Subject: [PATCH 01/10] Formatting, definitions Reformatted to fit to .md standard as well as clarified on definitions of web app and wallet --- kip12/kip12.md | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 kip12/kip12.md diff --git a/kip12/kip12.md b/kip12/kip12.md new file mode 100644 index 0000000..c2ca427 --- /dev/null +++ b/kip12/kip12.md @@ -0,0 +1,237 @@ +``` +KIP: 12 +Layer: WASM32 Wallet SDK, Browser Extension Wallet APIs +Title: Specification for Browser Extension Wallet APIs +Authors: @aspect, @starkbamse, @KaffinPX, @mattoo +Status: DRAFT / WIP +``` + +# Motivation + +There are several emerging Browser Extension Wallet standards for interfacing with Kaspa ecosystem web applications. This KIP looks to standardize the methodology, by which wallets expose themselves to and communicate with web applications. +In addition, this KIP aims to provide a common way for wallets to expose their capabilities related to the ability to perform different functions (for example, support PSKTs) and support different emerging protocols including but not limited to: +- KRC-20 protocol by Kasplex +- KSC protocol (future Sparkle Smart Contract assets) +This document is loosely based on [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) in the Ethereum ecosystem + +# 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). + +# Definitions + +## Wallet +An browser-compatible extension that has a non-exclusive functionality of storing funds in one way or the other on the Kaspa blockchain. Examples of these are: Chrome Extensions, Firefox Extensions. + +## Web App +An application accessible via a web browser that aims to somehow utilize the funds stored in the [wallet](#wallet), in order to provide some functionality to the end user of the web app. + +# Contents + +## 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 tag to ensure no untrusted Javascript execution can occur. + +## Default/Initial behavior +The wallet MUST NOT react to ANY requests except the [kaspa:connect](#kaspaconnect) event. Only after an explicit connection request MAY the wallet react to subsequent requests made by the [Web App](#web-app). + +## Window Events + +### General +In order to prevent race conditions and collisions between providers, Web Apps and wallets must interact via an event concurrency loop. This involves emitting and listening to events using `window.dispatchEvent` and `window.addEventListener`. + +**ALL** events should be instances of the [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) object. The type property of the object **MUST** be the identifier of the event, and detail **MUST** include the contents of the Kaspa event. + +```typescript +new CustomEvent(type, {"detail":eventData}) +``` + +## Provider Events + +### `kaspa:provider` +- Sent by the wallet, includes provider information. The purpose is to allow Web App to discover the wallet. (this event is emitted in response to `kaspa:requestProvider` application event) + +### `kaspa:connect` + +#### Sender: +[Web App](#web-app) + +#### Input parameters +Extension ID of the [wallet](#wallet) + +#### Purpose +The purpose is to establish a connection between the Web App and the wallet, allowing further communication. Not related to any protocol. + +#### Behavior + On success, an account event MUST be emitted by the provider. + +# TODO continue formatting event details. + +### `kaspa:event` +Sent by the wallet, includes the event data with an event id if it's an invoked event. It is used to send data or events from the wallet back to the Web App. +This event contains an Event object that the Web App needs to handle. The Event includes an id, event and data, with an optional error field. + +### `kaspa:disconnect` +Sent by the wallet or Web App, this event has no additional data (maybe reason, describing the reason for disconnect i.e. Rejected etc.). It indicates that the connection between the Web App and the wallet has been or should be terminated. This event is also emitted when a connection request is rejected. +Application Events (requests) + +### `kaspa:requestProvider` +Sent by the Web App, this event has no additional data. It is used to request wallets to announce themselves by emitting the kaspa:provider event. + +### `kaspa:invoke` +Sent by the Web App, contains a request that the wallet needs to handle. It allows the Web App to invoke specific actions or requests in the wallet. +This event contains a Request object that the wallet needs to handle. The Request includes an id, protocol, method, and params, which define the specific action or request the Web App is invoking in the wallet. + + +# KaspaProviderDetail +`KaspaProviderDetail` is an object that MUST include the following +```typescript +interface KaspaProviderDetail { +info: KaspaProviderInfo +provider: KaspaProvider +} +``` + + +# `KaspaProviderInfo` +`id` (unique extension identifier, typically a UUID or an ID accessible from the extension as `browser.runtime.id`) +`name : string` +`icon : string` +`capabilities : string[]` +```typescript + +/** + * Represents the assets needed to display a wallet + */ +interface KaspaProviderInfo { + id: string; + name: string; + icon: string; + capabilities: Object +} +``` +# `KaspaProvider` +```typescript +interface RequestArguments { + method: string; + params?: object; +} +interface SendResponse { + success: bool; + txid: string; +} + + +interface KaspaProvider { + request: (args:RequestArguments)=>Promise; + connect: ()=>Promise; + disconnect(): ()=>Promise; +} +``` + + +# The connect method + +function connect(){ + emit connect event + wait for response + resolve or reject promise +} +# The disconnect method + +function disconnect(){ + emit disconnect event + wait for response + resolve or reject promise +} + + + +# The request method +The request method should have extension id hardcoded +function request() { + emit invoke event + wait for response + resolve or reject promise +} + + +Wallet Capabilities Object +Wallet capabilities object is comprised of keys and values where the key is the protocol name and the value is the string array of the protocol-specific API methods. API method names MUST be specified in "kebab-case" format. +```typescript +capabilities : { + "KASPA" : ["get-account", "send", "sign-message"], + "KRC-20" : ["get-account", "send", "mint", "deploy"], + "KSC" : ["execute"] +} +``` +Note about messages transmitted between extensions and clients: ALL messages transmitted to an extension should be accompanied by an id property containing a unique ID, in the event of a response from an extension the response MUST reference the initial ID sent by the client. + +```typescript +request = { + protocol : "KASPA", + method : "send", + params : [ + "kaspa:...", + "123.45", + "1.0" + ] +} +``` + + + + + + + +```typescript +let info: KaspaProviderInfo; +let provider: KaspaProvider; +const announceEvent: KaspaAnnounceProviderEvent = new CustomEvent( + "kaspa:announceProvider", + { detail: Object.freeze({ info, provider }) } +); +// The Wallet dispatches an announce event which is heard by +// the Web App code that had run earlier +window.dispatchEvent(announceEvent); +// The Wallet listens to the request events which may be +// dispatched later and re-dispatches the `EIP6963AnnounceProviderEvent` +window.addEventListener("kaspa:requestProvider", () => { + window.dispatchEvent(announceEvent); +}); +``` +```typescript +// The Web App listens to announced providers +window.addEventListener( + "kaspa:announceProvider", + (event: KaspaAnnounceProviderEvent) => {} +); +// The Web App dispatches a request event which will be heard by +// Wallets' code that had run earlier +window.dispatchEvent(new Event("kaspa:requestProvider")); +``` +```typescript +function onPageLoad() { + + function announceProvider() { + const info: KaspaProviderInfo = { + uuid: "eccb1a1a-9e1b-43ea-bf3e-915f8f9de7c6", + name: "Example Wallet", + icon: "data:image/svg+xml,", + }; + window.dispatchEvent( + new CustomEvent("kaspa:provider", { + detail: Object.freeze({ info, provider }), + }) + ); + } + + window.addEventListener( + "kaspa:requestProvider", + (event: KaspaRequestProviderEvent) => { + announceProvider(); + } + ); + announceProvider(); +} + +``` From d88de34087b43fe00f750a75840495a5bb909402 Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:24:58 +0000 Subject: [PATCH 02/10] Reformat --- kip12/kip12.md | 126 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 42 deletions(-) diff --git a/kip12/kip12.md b/kip12/kip12.md index c2ca427..559961d 100644 --- a/kip12/kip12.md +++ b/kip12/kip12.md @@ -47,27 +47,72 @@ new CustomEvent(type, {"detail":eventData}) ## Provider Events ### `kaspa:provider` -- Sent by the wallet, includes provider information. The purpose is to allow Web App to discover the wallet. (this event is emitted in response to `kaspa:requestProvider` application event) -### `kaspa:connect` +``` +Sender: Wallet +Purpose: Allow Web App to discover the wallet. +``` -#### Sender: -[Web App](#web-app) +```typescript +interface KaspaProviderDetail { +info: KaspaProviderInfo +provider: KaspaProvider +} -#### Input parameters -Extension ID of the [wallet](#wallet) +/** + * Represents the assets needed to display a wallet + */ +interface KaspaProviderInfo { + id: string; + name: string; + icon: string; + capabilities: Object +} -#### Purpose -The purpose is to establish a connection between the Web App and the wallet, allowing further communication. Not related to any protocol. +interface KaspaProvider { + request: (args:RequestArguments)=>Promise; + connect: ()=>Promise; + disconnect(): ()=>Promise; +} -#### Behavior - On success, an account event MUST be emitted by the provider. +interface RequestArguments { + method: string; + params?: object; +} +``` -# TODO continue formatting event details. +### `kaspa:connect` + +``` +Sender: Web app +Purpose: Establish a connection between the Web App and the wallet, allowing further communication. +``` +```typescript +type extensionId=string; // Extension id of the wallet +``` ### `kaspa:event` -Sent by the wallet, includes the event data with an event id if it's an invoked event. It is used to send data or events from the wallet back to the Web App. -This event contains an Event object that the Web App needs to handle. The Event includes an id, event and data, with an optional error field. + +``` +Sender: Wallet +Purpose: Used to send data from the wallet to the web app. +``` + + +```typescript +interface Response { + protocol: string, + data: any +} + +interface Event { + eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + extensionId:string, + data:Response, + error:string | undefined +} +``` + ### `kaspa:disconnect` Sent by the wallet or Web App, this event has no additional data (maybe reason, describing the reason for disconnect i.e. Rejected etc.). It indicates that the connection between the Web App and the wallet has been or should be terminated. This event is also emitted when a connection request is rejected. @@ -80,9 +125,30 @@ Sent by the Web App, this event has no additional data. It is used to request wa Sent by the Web App, contains a request that the wallet needs to handle. It allows the Web App to invoke specific actions or requests in the wallet. This event contains a Request object that the wallet needs to handle. The Request includes an id, protocol, method, and params, which define the specific action or request the Web App is invoking in the wallet. +``` +Sender: Web App +Purpose: Request the wallet for a specific action +``` + +```typescript +interface Request { + protocol: string, + method: string, + params: Object +} + +interface Event { + eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + extensionId:string, + data:Request, + error:string | undefined +} +``` + # KaspaProviderDetail `KaspaProviderDetail` is an object that MUST include the following + ```typescript interface KaspaProviderDetail { info: KaspaProviderInfo @@ -91,42 +157,18 @@ provider: KaspaProvider ``` + +# `KaspaProvider` + + + # `KaspaProviderInfo` `id` (unique extension identifier, typically a UUID or an ID accessible from the extension as `browser.runtime.id`) `name : string` `icon : string` `capabilities : string[]` -```typescript - -/** - * Represents the assets needed to display a wallet - */ -interface KaspaProviderInfo { - id: string; - name: string; - icon: string; - capabilities: Object -} -``` -# `KaspaProvider` -```typescript -interface RequestArguments { - method: string; - params?: object; -} -interface SendResponse { - success: bool; - txid: string; -} -interface KaspaProvider { - request: (args:RequestArguments)=>Promise; - connect: ()=>Promise; - disconnect(): ()=>Promise; -} -``` - # The connect method From e467d293e75fa44052221997b68b0057d63ca1c8 Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:18:30 +0200 Subject: [PATCH 03/10] Condense information, create implementations * Condense information from docs document * Create function implementations for connect, disconnect, request. --- kip12/kip12.md | 133 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 26 deletions(-) diff --git a/kip12/kip12.md b/kip12/kip12.md index 559961d..706c49c 100644 --- a/kip12/kip12.md +++ b/kip12/kip12.md @@ -45,6 +45,7 @@ new CustomEvent(type, {"detail":eventData}) ``` ## Provider Events +This section contains all the events that are part of this KIP as well as their specified content, with description of the expected behavior. ### `kaspa:provider` @@ -55,29 +56,89 @@ Purpose: Allow Web App to discover the wallet. ```typescript interface KaspaProviderDetail { -info: KaspaProviderInfo -provider: KaspaProvider + info: KaspaProviderInfo + provider: KaspaProvider } -/** - * Represents the assets needed to display a wallet - */ interface KaspaProviderInfo { - id: string; - name: string; - icon: string; - capabilities: Object + id: string; + name: string; + icon: string; + capabilities: string[] } interface KaspaProvider { - request: (args:RequestArguments)=>Promise; - connect: ()=>Promise; - disconnect(): ()=>Promise; + request: (args:Request)=>Promise; + connect: ()=>Promise; + disconnect(): ()=>Promise; +} + +interface Request { + protocol: string, + method: string, + params: Object +} + +interface Request { + protocol: string, + method: string, + params: Object +} + +interface Event { + eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + extensionId:string, // `browser.runtime.id` + data:Request, + error:string | undefined } -interface RequestArguments { - method: string; - params?: object; +async function request(request:Request):Promise{ + const requestEvent = new CustomEvent("kaspa:invoke", { + detail: Object.freeze({ + eventId: uuidv4(), + extensionId: browser.runtime.id, + data: request, + error: undefined, + }) + } + ); + + window.dispatchEvent(requestEvent); + + return new Promise((resolve, reject) => { + window.addEventListener("kaspa:event", (event: Event) => { + if (event.eventId === request.eventId) { + if (event.error) { + reject(event.error); + } else { + resolve(event.data); + } + } + }); + }); +} + +async function connect():Promise{ + const connectEvent = new CustomEvent("kaspa:connect", { + detail: Object.freeze({ + eventId: uuidv4(), + extensionId: browser.runtime.id, + }), + }); + + window.dispatchEvent(connectEvent); + + return new Promise((resolve, reject) => { + window.addEventListener("kaspa:event", (event: Event) => { + if (event.eventId === connectEvent.eventId) { + if (event.error) { + reject(event.error); + } else { + resolve(); + } + } + }); + }); } ``` @@ -88,7 +149,10 @@ Sender: Web app Purpose: Establish a connection between the Web App and the wallet, allowing further communication. ``` ```typescript -type extensionId=string; // Extension id of the wallet +interface Connect { + eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12 + extensionId:string, // `browser.runtime.id` +} ``` ### `kaspa:event` @@ -107,19 +171,39 @@ interface Response { interface Event { eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} - extensionId:string, + extensionId:string, // `browser.runtime.id` data:Response, error:string | undefined } ``` - ### `kaspa:disconnect` -Sent by the wallet or Web App, this event has no additional data (maybe reason, describing the reason for disconnect i.e. Rejected etc.). It indicates that the connection between the Web App and the wallet has been or should be terminated. This event is also emitted when a connection request is rejected. -Application Events (requests) + +``` +Sender: Wallet or Web App +Purpose: To signal to the corresponding party that a connection has been terminated from the sending part, and that the sender party will no longer respond to requests nor react to any kind of data transmitted by the sender. +``` + +```typescript +interface Disconnect { + eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12 + extensionId:string, // `browser.runtime.id` + reason:string +} +``` + ### `kaspa:requestProvider` -Sent by the Web App, this event has no additional data. It is used to request wallets to announce themselves by emitting the kaspa:provider event. + +``` +Sender: Web App +Purpose: To request wallets to announce themselves by emitting the kaspa:provider event. +``` + +```typescript +// No additional data +``` + ### `kaspa:invoke` Sent by the Web App, contains a request that the wallet needs to handle. It allows the Web App to invoke specific actions or requests in the wallet. @@ -127,7 +211,7 @@ This event contains a Request object that the wallet needs to handle. The Reques ``` Sender: Web App -Purpose: Request the wallet for a specific action +Purpose: Used internally, by the provided request() method to request the wallet for a specific action. Not intended for direct usage. ``` ```typescript @@ -139,7 +223,7 @@ interface Request { interface Event { eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} - extensionId:string, + extensionId:string, // `browser.runtime.id` data:Request, error:string | undefined } @@ -222,9 +306,6 @@ request = { - - - ```typescript let info: KaspaProviderInfo; let provider: KaspaProvider; From 01b02cc8d2466dd2d28fed972f670243ae05d717 Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:19:07 +0200 Subject: [PATCH 04/10] Delete old docs content, introduce Capability * Delete old docs content that was used as inspiration for the new format * Introduce the capability interface --- kip12/kip12.md | 166 ++++++++++--------------------------------------- 1 file changed, 33 insertions(+), 133 deletions(-) diff --git a/kip12/kip12.md b/kip12/kip12.md index 706c49c..f0087da 100644 --- a/kip12/kip12.md +++ b/kip12/kip12.md @@ -64,9 +64,15 @@ interface KaspaProviderInfo { id: string; name: string; icon: string; - capabilities: string[] + capabilities: Capability[] } +interface Capability { + protocol: string; // Protocol name, e.g. "KASPA", "KRC-20", "KSC" + methods: string[]; // Array of method names in kebab-case : "get-account", "send", "sign-message +} + + interface KaspaProvider { request: (args:Request)=>Promise; connect: ()=>Promise; @@ -120,10 +126,10 @@ async function request(request:Request):Promise{ async function connect():Promise{ const connectEvent = new CustomEvent("kaspa:connect", { - detail: Object.freeze({ + detail: Object.freeze({ eventId: uuidv4(), extensionId: browser.runtime.id, - }), + }), }); window.dispatchEvent(connectEvent); @@ -140,6 +146,30 @@ async function connect():Promise{ }); }); } + +async function disconnect():Promise{ + const disconnectEvent = new CustomEvent("kaspa:disconnect", { + detail: Object.freeze({ + eventId: uuidv4(), + extensionId: browser.runtime.id, + reason: "User initiated", + }), + }); + + window.dispatchEvent(disconnectEvent); + + return new Promise((resolve, reject) => { + window.addEventListener("kaspa:event", (event: Event) => { + if (event.eventId === disconnectEvent.eventId) { + if (event.error) { + reject(event.error); + } else { + resolve(); + } + } + }); + }); +} ``` ### `kaspa:connect` @@ -228,133 +258,3 @@ interface Event { error:string | undefined } ``` - - -# KaspaProviderDetail -`KaspaProviderDetail` is an object that MUST include the following - -```typescript -interface KaspaProviderDetail { -info: KaspaProviderInfo -provider: KaspaProvider -} -``` - - - -# `KaspaProvider` - - - -# `KaspaProviderInfo` -`id` (unique extension identifier, typically a UUID or an ID accessible from the extension as `browser.runtime.id`) -`name : string` -`icon : string` -`capabilities : string[]` - - - -# The connect method - -function connect(){ - emit connect event - wait for response - resolve or reject promise -} -# The disconnect method - -function disconnect(){ - emit disconnect event - wait for response - resolve or reject promise -} - - - -# The request method -The request method should have extension id hardcoded -function request() { - emit invoke event - wait for response - resolve or reject promise -} - - -Wallet Capabilities Object -Wallet capabilities object is comprised of keys and values where the key is the protocol name and the value is the string array of the protocol-specific API methods. API method names MUST be specified in "kebab-case" format. -```typescript -capabilities : { - "KASPA" : ["get-account", "send", "sign-message"], - "KRC-20" : ["get-account", "send", "mint", "deploy"], - "KSC" : ["execute"] -} -``` -Note about messages transmitted between extensions and clients: ALL messages transmitted to an extension should be accompanied by an id property containing a unique ID, in the event of a response from an extension the response MUST reference the initial ID sent by the client. - -```typescript -request = { - protocol : "KASPA", - method : "send", - params : [ - "kaspa:...", - "123.45", - "1.0" - ] -} -``` - - - - -```typescript -let info: KaspaProviderInfo; -let provider: KaspaProvider; -const announceEvent: KaspaAnnounceProviderEvent = new CustomEvent( - "kaspa:announceProvider", - { detail: Object.freeze({ info, provider }) } -); -// The Wallet dispatches an announce event which is heard by -// the Web App code that had run earlier -window.dispatchEvent(announceEvent); -// The Wallet listens to the request events which may be -// dispatched later and re-dispatches the `EIP6963AnnounceProviderEvent` -window.addEventListener("kaspa:requestProvider", () => { - window.dispatchEvent(announceEvent); -}); -``` -```typescript -// The Web App listens to announced providers -window.addEventListener( - "kaspa:announceProvider", - (event: KaspaAnnounceProviderEvent) => {} -); -// The Web App dispatches a request event which will be heard by -// Wallets' code that had run earlier -window.dispatchEvent(new Event("kaspa:requestProvider")); -``` -```typescript -function onPageLoad() { - - function announceProvider() { - const info: KaspaProviderInfo = { - uuid: "eccb1a1a-9e1b-43ea-bf3e-915f8f9de7c6", - name: "Example Wallet", - icon: "data:image/svg+xml,", - }; - window.dispatchEvent( - new CustomEvent("kaspa:provider", { - detail: Object.freeze({ info, provider }), - }) - ); - } - - window.addEventListener( - "kaspa:requestProvider", - (event: KaspaRequestProviderEvent) => { - announceProvider(); - } - ); - announceProvider(); -} - -``` From ac357e1da373955223e99e8557f08c5d0c313cdb Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:08:43 +0200 Subject: [PATCH 05/10] Align format as rest of kips Modify naming and format to be compatible with the standardized KIP format. --- kip12/kip12.md => kip-0012.md | 0 kip-0012/KRC-20.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename kip12/kip12.md => kip-0012.md (100%) create mode 100644 kip-0012/KRC-20.md diff --git a/kip12/kip12.md b/kip-0012.md similarity index 100% rename from kip12/kip12.md rename to kip-0012.md diff --git a/kip-0012/KRC-20.md b/kip-0012/KRC-20.md new file mode 100644 index 0000000..e69de29 From 398784f5780b11150925d93f170cd98b15331cb5 Mon Sep 17 00:00:00 2001 From: starkbamse Date: Sun, 13 Oct 2024 18:04:14 +0200 Subject: [PATCH 06/10] Update KIP-12 as per suggestions by @aspect Condense further into table, simplify interface use. Co-Authored-By: aspect <7544020+aspect@users.noreply.github.com> --- kip-0012.md | 361 +++++++++++++++++++++++++--------------------------- 1 file changed, 170 insertions(+), 191 deletions(-) diff --git a/kip-0012.md b/kip-0012.md index f0087da..5c6eaa2 100644 --- a/kip-0012.md +++ b/kip-0012.md @@ -1,63 +1,114 @@ -``` -KIP: 12 -Layer: WASM32 Wallet SDK, Browser Extension Wallet APIs -Title: Specification for Browser Extension Wallet APIs -Authors: @aspect, @starkbamse, @KaffinPX, @mattoo -Status: DRAFT / WIP -``` +# KIP-0012: Specification for Browser Extension Wallet APIs -# Motivation -There are several emerging Browser Extension Wallet standards for interfacing with Kaspa ecosystem web applications. This KIP looks to standardize the methodology, by which wallets expose themselves to and communicate with web applications. -In addition, this KIP aims to provide a common way for wallets to expose their capabilities related to the ability to perform different functions (for example, support PSKTs) and support different emerging protocols including but not limited to: -- KRC-20 protocol by Kasplex -- KSC protocol (future Sparkle Smart Contract assets) -This document is loosely based on [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) in the Ethereum ecosystem -# 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). -# Definitions + + + + + +
LayerWASM32 Wallet SDK, Browser Extension Wallet APIs
TitleSpecification for Browser Extension Wallet APIs
Authors@aspect, @starkbamse, @KaffinPX, @mattoo
StatusDRAFT / WIP
-## Wallet -An browser-compatible extension that has a non-exclusive functionality of storing funds in one way or the other on the Kaspa blockchain. Examples of these are: Chrome Extensions, Firefox Extensions. -## Web App -An application accessible via a web browser that aims to somehow utilize the funds stored in the [wallet](#wallet), in order to provide some functionality to the end user of the web app. +
-# Contents +## **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. -## 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 tag to ensure no untrusted Javascript execution can occur. +
-## Default/Initial behavior -The wallet MUST NOT react to ANY requests except the [kaspa:connect](#kaspaconnect) event. Only after an explicit connection request MAY the wallet react to subsequent requests made by the [Web App](#web-app). +## **Motivation** -## Window Events +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. -### General -In order to prevent race conditions and collisions between providers, Web Apps and wallets must interact via an event concurrency loop. This involves emitting and listening to events using `window.dispatchEvent` and `window.addEventListener`. +Additionally, this KIP seeks to provide a common framework for wallets to showcase their capabilities, particularly in performing various functions (e.g., supporting PSKTs) and accommodating different emerging protocols. These protocols include, but are not limited to: -**ALL** events should be instances of the [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) object. The type property of the object **MUST** be the identifier of the event, and detail **MUST** include the contents of the Kaspa event. +- **KRC-20 protocol** by Kasplex +- **KSC protocol** (future Sparkle Smart Contract assets) -```typescript -new CustomEvent(type, {"detail":eventData}) -``` +This document is loosely based on Ethereum's [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). -## Provider Events -This section contains all the events that are part of this KIP as well as their specified content, with description of the expected behavior. +The specification is inspired by Ethereum's [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963), providing a robust foundation for wallet and web app interaction. -### `kaspa:provider` +
+ +## **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:invoke`**: Invokes specific wallet functionalities ( Used internally ). + - **`kaspa:event`**: Used to send data from the wallet to the web app. + - **`kaspa:disconnect`**: Terminates the connection between wallet and web app. + +
+ + +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**. + + +|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` +| **Web App** | → | **Invoke Action** (`kaspa:invoke`) | → | **Wallet** | `Request` +| **Wallet** | → | **Send Response** (`kaspa:event`) | → | **Web App** | `Response` +| **Web App / Wallet** | → | **Disconnect** (`kaspa:disconnect`) | → | **Wallet / Web App** | `Notification` + + +
+ + +### Wallets + + Wallets **MUST** expose key methods (KaspaProvider): + + - **`request`**: Makes a specific request (e.g., transferring assets). + - **`connect`**: Initiates a connection between the web app and the wallet. + - **`disconnect`**: Ends the connection and prevents further communication. + +
+ + +Wallets **SHOULD** support multiple protocols such as: +- **KRC-20**: Token standard for fungible tokens. +- **KSC**: Kaspa's future smart contract protocol for asset management. + + +
-``` -Sender: Wallet -Purpose: Allow Web App to discover the wallet. -``` +### 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 tag to ensure no untrusted Javascript execution can occur. + +
+ +### ```typescript -interface KaspaProviderDetail { - info: KaspaProviderInfo - provider: KaspaProvider +// kaspa:provider + +interface Capability { + protocol: string; // Protocol name, e.g. "KASPA", "KRC-20", "KSC" + methods: string[]; // Array of method names in kebab-case : "get-account", "send", "sign-message } interface KaspaProviderInfo { @@ -67,194 +118,122 @@ interface KaspaProviderInfo { capabilities: Capability[] } -interface Capability { - protocol: string; // Protocol name, e.g. "KASPA", "KRC-20", "KSC" - methods: string[]; // Array of method names in kebab-case : "get-account", "send", "sign-message +interface KaspaProvider { + request: (args:Request) => Promise; + connect: () => Promise; + disconnect: () => Promise; } - -interface KaspaProvider { - request: (args:Request)=>Promise; - connect: ()=>Promise; - disconnect(): ()=>Promise; +interface KaspaProviderDetail { + info: KaspaProviderInfo + provider: KaspaProvider } interface Request { - protocol: string, - method: string, - params: Object + extensionId: string; // `browser.runtime.id` + data: { + protocol: string; + method: string; + params: Object; + }, + error: string | Object | undefined; } -interface Request { - protocol: string, - method: string, - params: Object +interface Response { + extensionId: string; // `browser.runtime.id` + data: { + protocol: string | undefined; + data: any; + }, + error: string | Object | undefined; } -interface Event { - eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} - extensionId:string, // `browser.runtime.id` - data:Request, - error:string | undefined +interface Notification { + data: { + protocol: string | undefined; + data: any; + }, + error: string | Object | undefined; } +``` +
+ +### Reference -async function request(request:Request):Promise{ - const requestEvent = new CustomEvent("kaspa:invoke", { - detail: Object.freeze({ - eventId: uuidv4(), - extensionId: browser.runtime.id, - data: request, - error: undefined, - }) - } - ); +```typescript +async function request(protocol:string,method:string,params:Object): Promise { + const requestEvent = new CustomEvent("kaspa:invoke", { + detail: Object.freeze({ + eventId: uuidv4(), + extensionId: browser.runtime.id, + data: { + protocol, + method, + params + }, + error: undefined, + } as Request ) + }); window.dispatchEvent(requestEvent); return new Promise((resolve, reject) => { - window.addEventListener("kaspa:event", (event: Event) => { - if (event.eventId === request.eventId) { - if (event.error) { - reject(event.error); + const handleEvent = (event: CustomEvent) => { + if (event.detail.eventId === requestEvent.detail.eventId) { + window.removeEventListener("kaspa:event", handleEvent); + if (event.detail.error) { + reject(event.detail.error); } else { - resolve(event.data); + resolve(event.detail.data); } } - }); + }; + window.addEventListener("kaspa:event", handleEvent as EventListener); }); } -async function connect():Promise{ + +async function connect(): Promise { const connectEvent = new CustomEvent("kaspa:connect", { detail: Object.freeze({ eventId: uuidv4(), extensionId: browser.runtime.id, - }), + data:undefined, + error:undefined + } as Request ), }); - window.dispatchEvent(connectEvent); return new Promise((resolve, reject) => { - window.addEventListener("kaspa:event", (event: Event) => { - if (event.eventId === connectEvent.eventId) { - if (event.error) { - reject(event.error); + const handleEvent = (event: CustomEvent) => { + if (event.detail.eventId === connectEvent.detail.eventId) { + window.removeEventListener("kaspa:event", handleEvent); + if (event.detail.error) { + reject(event.detail.error); } else { resolve(); } } - }); + }; + window.addEventListener("kaspa:event", handleEvent as EventListener); }); } -async function disconnect():Promise{ + +async function disconnect() { + + let data:Data = { + protocol:undefined, + data:"User initiated" + } + const disconnectEvent = new CustomEvent("kaspa:disconnect", { detail: Object.freeze({ - eventId: uuidv4(), extensionId: browser.runtime.id, - reason: "User initiated", - }), + data, + error:undefined + } as Notification ), }); - window.dispatchEvent(disconnectEvent); - - return new Promise((resolve, reject) => { - window.addEventListener("kaspa:event", (event: Event) => { - if (event.eventId === disconnectEvent.eventId) { - if (event.error) { - reject(event.error); - } else { - resolve(); - } - } - }); - }); -} -``` - -### `kaspa:connect` - -``` -Sender: Web app -Purpose: Establish a connection between the Web App and the wallet, allowing further communication. -``` -```typescript -interface Connect { - eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12 - extensionId:string, // `browser.runtime.id` -} -``` - -### `kaspa:event` - -``` -Sender: Wallet -Purpose: Used to send data from the wallet to the web app. -``` - - -```typescript -interface Response { - protocol: string, - data: any -} - -interface Event { - eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} - extensionId:string, // `browser.runtime.id` - data:Response, - error:string | undefined -} -``` - -### `kaspa:disconnect` - -``` -Sender: Wallet or Web App -Purpose: To signal to the corresponding party that a connection has been terminated from the sending part, and that the sender party will no longer respond to requests nor react to any kind of data transmitted by the sender. -``` - -```typescript -interface Disconnect { - eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12 - extensionId:string, // `browser.runtime.id` - reason:string -} -``` - - -### `kaspa:requestProvider` - -``` -Sender: Web App -Purpose: To request wallets to announce themselves by emitting the kaspa:provider event. -``` - -```typescript -// No additional data -``` - - -### `kaspa:invoke` -Sent by the Web App, contains a request that the wallet needs to handle. It allows the Web App to invoke specific actions or requests in the wallet. -This event contains a Request object that the wallet needs to handle. The Request includes an id, protocol, method, and params, which define the specific action or request the Web App is invoking in the wallet. - -``` -Sender: Web App -Purpose: Used internally, by the provided request() method to request the wallet for a specific action. Not intended for direct usage. -``` - -```typescript -interface Request { - protocol: string, - method: string, - params: Object } - -interface Event { - eventId:string, // UUID as [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} - extensionId:string, // `browser.runtime.id` - data:Request, - error:string | undefined -} -``` +``` \ No newline at end of file From a61102a36b8718da2237c50de5c4cfb919b0ad61 Mon Sep 17 00:00:00 2001 From: starkbamse Date: Sun, 13 Oct 2024 18:08:49 +0200 Subject: [PATCH 07/10] Make interface consistent Fix inconsistencies with interface and reference examples --- kip-0012.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/kip-0012.md b/kip-0012.md index 5c6eaa2..0de798a 100644 --- a/kip-0012.md +++ b/kip-0012.md @@ -119,7 +119,7 @@ interface KaspaProviderInfo { } interface KaspaProvider { - request: (args:Request) => Promise; + request: (protocol:string,method:string,params:Object) => Promise; connect: () => Promise; disconnect: () => Promise; } @@ -130,6 +130,7 @@ interface KaspaProviderDetail { } interface Request { + eventId: string, // A unique id that this request associates with. E.g. uuidv4 extensionId: string; // `browser.runtime.id` data: { protocol: string; @@ -140,6 +141,7 @@ interface Request { } interface Response { + eventId: string, // A unique id that this response associates with. E.g. uuidv4 extensionId: string; // `browser.runtime.id` data: { protocol: string | undefined; @@ -148,7 +150,7 @@ interface Response { error: string | Object | undefined; } -interface Notification { +interface Notification { // Do not require a response, and therefore no id. data: { protocol: string | undefined; data: any; @@ -158,7 +160,7 @@ interface Notification { ```
-### Reference +### Implementation examples ```typescript @@ -166,7 +168,7 @@ async function request(protocol:string,method:string,params:Object): Promise { const connectEvent = new CustomEvent("kaspa:connect", { detail: Object.freeze({ eventId: uuidv4(), - extensionId: browser.runtime.id, + extensionId: "HardcodedExtensionIdEmittedByExtension", data:undefined, error:undefined } as Request ), @@ -229,7 +231,7 @@ async function disconnect() { const disconnectEvent = new CustomEvent("kaspa:disconnect", { detail: Object.freeze({ - extensionId: browser.runtime.id, + extensionId: "HardcodedExtensionIdEmittedByExtension", data, error:undefined } as Notification ), From 3f2fd0fad455691f69a74d5ebb82c265ed3eb609 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Wed, 22 Jan 2025 21:43:01 +0100 Subject: [PATCH 08/10] Update kip-0012.md --- kip-0012.md | 316 +++++++++++++++++++++++----------------------------- 1 file changed, 137 insertions(+), 179 deletions(-) diff --git a/kip-0012.md b/kip-0012.md index 0de798a..9bfe96f 100644 --- a/kip-0012.md +++ b/kip-0012.md @@ -1,8 +1,5 @@ # KIP-0012: Specification for Browser Extension Wallet APIs - - - @@ -10,232 +7,193 @@
LayerWASM32 Wallet SDK, Browser Extension Wallet APIs
TitleSpecification for Browser Extension Wallet APIs
StatusDRAFT / WIP
- -
- -## **Goals** +## 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** +## 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 (e.g., supporting PSKTs) and accommodating different emerging protocols. These protocols include, but are not limited to: +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). -The specification is inspired by Ethereum's [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963), providing a robust foundation for wallet and web app interaction. - -
- -## **Key Definitions** +## 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 -
- -## **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. +**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).** - - **`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:invoke`**: Invokes specific wallet functionalities ( Used internally ). - - **`kaspa:event`**: Used to send data from the wallet to the web app. - - **`kaspa:disconnect`**: Terminates the connection between wallet and web app. - -
+### 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:invoke`**: Invokes specific wallet functionalities (Used internally). +- **`kaspa:event`**: Used to send data from the wallet to the web app. +- **`kaspa:disconnect`**: Terminates the connection between wallet and web app. 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**. +### Event Flow |Sender||Event||Receiver|Interface| |---|--|---|---|---|---| | **Web App** | → | **Request Provider** (`kaspa:requestProvider`) | → | **Wallet** | `Notification` -| **Wallet** | → | **Provide Information** (`kaspa:provider`) | → | **Web App** | `Notification` +| **Wallet** | → | **Provide Information** (`kaspa:provider`) | → | **Web App** | `Notification` | **Web App** | → | **Connect** (`kaspa:connect`) | → | **Wallet** | `Request` | **Web App** | → | **Invoke Action** (`kaspa:invoke`) | → | **Wallet** | `Request` -| **Wallet** | → | **Send Response** (`kaspa:event`) | → | **Web App** | `Response` +| **Wallet** | → | **Send Response** (`kaspa:event`) | → | **Web App** | `Response` | **Web App / Wallet** | → | **Disconnect** (`kaspa:disconnect`) | → | **Wallet / Web App** | `Notification` +### Interface Definitions -
- - -### Wallets - - Wallets **MUST** expose key methods (KaspaProvider): - - - **`request`**: Makes a specific request (e.g., transferring assets). - - **`connect`**: Initiates a connection between the web app and the wallet. - - **`disconnect`**: Ends the connection and prevents further communication. - -
- - -Wallets **SHOULD** support multiple protocols such as: -- **KRC-20**: Token standard for fungible tokens. -- **KSC**: Kaspa's future smart contract protocol for asset management. - - -
- - -### 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 tag to ensure no untrusted Javascript execution can occur. - -
- -### ```typescript -// kaspa:provider - -interface Capability { - protocol: string; // Protocol name, e.g. "KASPA", "KRC-20", "KSC" - methods: string[]; // Array of method names in kebab-case : "get-account", "send", "sign-message +export interface KaspaProvider { + request: (protocol: string, method: string, params: any) => Promise; + connect: () => Promise; + disconnect: () => Promise; } -interface KaspaProviderInfo { - id: string; - name: string; - icon: string; - capabilities: Capability[] -} - -interface KaspaProvider { - request: (protocol:string,method:string,params:Object) => Promise; - connect: () => Promise; - disconnect: () => Promise; +export interface ProviderCapability { + protocol: string; + methods: string[]; } -interface KaspaProviderDetail { - info: KaspaProviderInfo - provider: KaspaProvider +export interface ProviderInfo { + id: string; + name: string; + icon: string; + capabilities: ProviderCapability[]; } -interface Request { - eventId: string, // A unique id that this request associates with. E.g. uuidv4 - extensionId: string; // `browser.runtime.id` - data: { - protocol: string; +export interface Message { + eventId: string; + extensionId: string; method: string; - params: Object; - }, - error: string | Object | undefined; -} - -interface Response { - eventId: string, // A unique id that this response associates with. E.g. uuidv4 - extensionId: string; // `browser.runtime.id` - data: { - protocol: string | undefined; - data: any; - }, - error: string | Object | undefined; + data?: { + method?: string; + protocol?: string; + params?: any; + data?: any; + }; + error?: any; } -interface Notification { // Do not require a response, and therefore no id. - data: { - protocol: string | undefined; - data: any; - }, - error: string | Object | undefined; +declare global { + interface Window { + kaspaProvider: KaspaProvider; + } } ``` -
-### Implementation examples +### 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 tag to ensure no untrusted Javascript execution can occur. +### Implementation Example ```typescript -async function request(protocol:string,method:string,params:Object): Promise { - const requestEvent = new CustomEvent("kaspa:invoke", { - detail: Object.freeze({ - eventId: uuidv4(), - extensionId: "HardcodedExtensionIdEmittedByExtension", - data: { - protocol, - method, - params - }, - error: undefined, - } as Request ) - }); - window.dispatchEvent(requestEvent); - - return new Promise((resolve, reject) => { - const handleEvent = (event: CustomEvent) => { - if (event.detail.eventId === requestEvent.detail.eventId) { - window.removeEventListener("kaspa:event", handleEvent); - if (event.detail.error) { - reject(event.detail.error); - } else { - resolve(event.detail.data); +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 (protocol: string, method: string, params: any): Promise => { + return new Promise((resolve, reject) => { + const eventId = generateUUID(); + + const message: Message = { + eventId, + extensionId, + method: "kaspa:invoke", + data: { + protocol, + params, + method + } + }; + + 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 => { + 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 => { + const message: Message = { + eventId: generateUUID(), + extensionId, + method: "kaspa:disconnect" + }; + window.dispatchEvent(new CustomEvent(uuid, { detail: message })); } - } }; - window.addEventListener("kaspa:event", handleEvent as EventListener); - }); -} - - -async function connect(): Promise { - const connectEvent = new CustomEvent("kaspa:connect", { - detail: Object.freeze({ - eventId: uuidv4(), - extensionId: "HardcodedExtensionIdEmittedByExtension", - data:undefined, - error:undefined - } as Request ), - }); - window.dispatchEvent(connectEvent); - - return new Promise((resolve, reject) => { - const handleEvent = (event: CustomEvent) => { - if (event.detail.eventId === connectEvent.detail.eventId) { - window.removeEventListener("kaspa:event", handleEvent); - if (event.detail.error) { - reject(event.detail.error); - } else { - resolve(); - } - } - }; - window.addEventListener("kaspa:event", handleEvent as EventListener); - }); -} - -async function disconnect() { - - let data:Data = { - protocol:undefined, - data:"User initiated" - } - - const disconnectEvent = new CustomEvent("kaspa:disconnect", { - detail: Object.freeze({ - extensionId: "HardcodedExtensionIdEmittedByExtension", - data, - error:undefined - } as Notification ), - }); - window.dispatchEvent(disconnectEvent); + // 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 + }); } ``` \ No newline at end of file From 8b9d129fc8a40154ed0dabd9baf9279b468f75e5 Mon Sep 17 00:00:00 2001 From: Alexander Saefstroem <139136798+saefstroem@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:05:16 +0100 Subject: [PATCH 09/10] Update kip-0012.md --- kip-0012.md | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/kip-0012.md b/kip-0012.md index 9bfe96f..7eb2eb2 100644 --- a/kip-0012.md +++ b/kip-0012.md @@ -38,10 +38,14 @@ Wallets and web apps communicate through the browser's `window` object using `Cu - **`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:invoke`**: Invokes specific wallet functionalities (Used internally). - **`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 +kaspa:sign +kaspa:broadcast + + 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**. ### Event Flow @@ -59,33 +63,25 @@ The wallet **MUST NOT** react to any requests except the **`kaspa:connect`** eve ```typescript export interface KaspaProvider { - request: (protocol: string, method: string, params: any) => Promise; + request: (method:string,args:any[]) => Promise; connect: () => Promise; disconnect: () => Promise; } -export interface ProviderCapability { - protocol: string; - methods: string[]; -} + export interface ProviderInfo { id: string; name: string; icon: string; - capabilities: ProviderCapability[]; + methods: string[]; } export interface Message { eventId: string; extensionId: string; method: string; - data?: { - method?: string; - protocol?: string; - params?: any; - data?: any; - }; + args?: any[]; error?: any; } @@ -112,19 +108,15 @@ export function script(providerInfo: ProviderInfo, extensionId: string, uuid: st } const kaspaProvider: KaspaProvider = { - request: async (protocol: string, method: string, params: any): Promise => { + request: async (method:string,args:any[]): Promise => { return new Promise((resolve, reject) => { const eventId = generateUUID(); const message: Message = { eventId, extensionId, - method: "kaspa:invoke", - data: { - protocol, - params, - method - } + method, + args }; const handleResponse = (event: CustomEvent) => { From 7a03504f15c7fc900e6caaede244ef21859b2170 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Thu, 23 Jan 2025 16:29:50 +0100 Subject: [PATCH 10/10] Update kip-0012.md --- kip-0012.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/kip-0012.md b/kip-0012.md index 7eb2eb2..ac064fa 100644 --- a/kip-0012.md +++ b/kip-0012.md @@ -40,25 +40,39 @@ Wallets and web apps communicate through the browser's `window` object using `Cu - **`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 -kaspa:sign -kaspa:broadcast - +- **`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**. -### Event Flow +### 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` -| **Web App** | → | **Invoke Action** (`kaspa:invoke`) | → | **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 @@ -69,7 +83,6 @@ export interface KaspaProvider { } - export interface ProviderInfo { id: string; name: string;