From d784c3e61434b67f4e06c4cfcd50f83d53670525 Mon Sep 17 00:00:00 2001 From: Linda Ortega Date: Mon, 7 Oct 2024 14:59:35 -0400 Subject: [PATCH 1/2] Re-structure Snaps README; add Danillo RPC methods docs from previous snaps repo --- packages/libs/snap/README.md | 660 ++++++++++++++++++++++++++++++++++- 1 file changed, 656 insertions(+), 4 deletions(-) diff --git a/packages/libs/snap/README.md b/packages/libs/snap/README.md index d3cec3ed59..cae9ecfba6 100644 --- a/packages/libs/snap/README.md +++ b/packages/libs/snap/README.md @@ -1,9 +1,6 @@ # Kadena Snap -This is Kadena Snap, which is written in TypeScript. It leverages the state -storage of Snaps to store the status of the DApp. It provides various functions, -including `kda_signTransaction`, `kda_addAccount`, `kda_getActiveAccount`, and -many more. +This documentation covers the RPC methods provided by the Kadena Snap, allowing interaction with the Kadena blockchain via MetaMask. The Kadena Snap provides methods for checking connection status, managing accounts, interacting with networks, and signing transactions. @@ -12,6 +9,33 @@ many more. +--- + + +## Table of Contents + +1. [Testing](#testing) +2. [Type Definitions](#type-definitions) +3. [Available RPC Methods](#available-rpc-methods) + - [kda_checkConnection](#kda_checkconnection) + - [kda_addAccount](#kda_addaccount) + - [kda_addHardwareAccount](#kda_addhardwareaccount) + - [kda_deleteAccount](#kda_deleteaccount) + - [kda_deleteHardwareAccount](#kda_deletehardwareaccount) + - [kda_getAccounts](#kda_getaccounts) + - [kda_getHardwareAccounts](#kda_gethardwareaccounts) + - [kda_getNetworks](#kda_getnetworks) + - [kda_storeNetwork](#kda_storenetwork) + - [kda_deleteNetwork](#kda_deletenetwork) + - [kda_getActiveNetwork](#kda_getactivenetwork) + - [kda_setActiveNetwork](#kda_setactivenetwork) + - [kda_setAccountName](#kda_setaccountname) + - [kda_setHardwareAccountName](#kda_sethardwareaccountname) + - [kda_signTransaction](#kda_signtransaction) +4. [Example Usage in a DApp](#example-usage-in-a-dapp) + +--- + ## Testing The Kadena Snap project includes several tests that cover the majority of its @@ -19,3 +43,631 @@ functions. To test the snap, navigate to this directory and run yarn test. This command will use [`@metamask/snaps-jest`](https://github.com/MetaMask/snaps/tree/main/packages/snaps-jest) to execute the tests found in src/index.test.ts. + +--- +## Type Definitions + +Below are the type definitions used in the Kadena Snap RPC calls. These types help ensure type safety and clarity when working with the RPC methods. + +```typescript +type CheckConnectionResponse = boolean; + +type SnapAccount = { + id: string; + address: string; + publicKey: string; + index: number; + name: string; +}; + +type CreateAccountResponse = SnapAccount; + +type GetAccountsResponse = SnapAccount[]; + +type DeleteAccountParams = { + id: string; +}; + +type DeleteHardwareAccountParams = { + id: string; +}; + +type SnapNetwork = { + name: string; + networkId: string; + blockExplorerTransaction: string; + blockExplorerAddress: string; + blockExplorerAddressTransactions: string; + isTestnet: boolean; + nodeUrl: string; + transactionListUrl: string; + transactionListTtl: number; + buyPageUrl: string; +}; + +type GetNetworksResponse = SnapNetwork[]; + +type StoreNetworkParams = { + network: SnapNetwork; +}; + +type DeleteNetworkParams = { + networkId: string; +}; + +type GetActiveNetworkResponse = string; + +type SetActiveNetworkParams = { + networkId: string; +}; + +type SetAccountNameParams = { + id: string; + name: string; +}; + +type SetHardwareAccountNameParams = { + id: string; + name: string; +}; + +type SignTransactionParams = { + id: string; + transaction: string; +}; + +type SignTransactionResponse = string; +``` + +--- + +## Available RPC Methods + +### kda_checkConnection + +**Description**: Checks if the Kadena Snap is connected. + +**Request Example**: + +```javascript +const isConnected = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_checkConnection' }, + }, +}); +``` + +**Response**: `CheckConnectionResponse` + +--- + +### kda_addAccount + +**Description**: Derives a new account from the Kadena Snap. + +**Request Example**: + +```javascript +const account = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_addAccount' }, + }, +}); +``` + +**Response**: `SnapAccount` + +--- + +### kda_addHardwareAccount + +**Description**: Adds a new hardware account from the Kadena Ledger App. NOTE: The Snap doesn't currently support signing with these hardware accounts. + +**Request Example**: + +```javascript +const account = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_addHardwareAccount', + params: { + index: 0, + address: '
', + publicKey: '', + }, + }, + }, +}); +``` + +**Response**: `SnapAccount` + +--- + +### kda_deleteAccount + +**Description**: Deletes an account by its ID on Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_deleteAccount', + params: { id: '' }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_deleteHardwareAccount + +**Description**: Deletes a hardware account by its ID on Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_deleteHardwareAccount', + params: { id: '' }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_getAccounts + +**Description**: Retrieves all accounts from the Kadena Snap. + +**Request Example**: + +```javascript +const accounts = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getAccounts' }, + }, +}); +``` + +**Response**: `SnapAccount[]` + +--- + +### kda_getHardwareAccounts + +**Description**: Retrieves all hardware accounts from the Kadena Snap. NOTE: The Snap doesn't currently support signing with these hardware accounts. + +**Request Example**: + +```javascript +const hardwareAccounts = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getHardwareAccounts' }, + }, +}); +``` + +**Response**: `SnapAccount[]` + +--- + +### kda_getNetworks + +**Description**: Retrieves all networks from the Kadena Snap. + +**Request Example**: + +```javascript +const networks = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getNetworks' }, + }, +}); +``` + +**Response**: `SnapNetwork[]` + +--- + +### kda_storeNetwork + +**Description**: Adds a network to the Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_storeNetwork', + params: { + network: { + name: 'New Network', + chainId: 1, + networkId: 'new-network-id', + nodeUrl: 'https://new-network-node.url', + blockExplorerTransaction: 'https://explorer.url/tx/{txId}', + blockExplorerAddress: 'https://explorer.url/address/{address}', + isTestnet: true, + }, + }, + }, + }, +}); +``` + +**Response**: `SnapNetwork` + +--- + +### kda_deleteNetwork + +**Description**: Deletes a network from the Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_deleteNetwork', + params: { networkId: '' }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_getActiveNetwork + +**Description**: Retrieves the active network from the Kadena Snap. + +**Request Example**: + +```javascript +const activeNetwork = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getActiveNetwork' }, + }, +}); +``` + +**Response**: `GetActiveNetworkResponse` + +--- + +### kda_setActiveNetwork + +**Description**: Sets the active network in the Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_setActiveNetwork', + params: { networkId: '' }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_setAccountName + +**Description**: Updates the name (or alias) of an account in the Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_setAccountName', + params: { + id: '', + name: 'New Account Name', + }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_setHardwareAccountName + +**Description**: Updates the name (or alias) of a hardware account in the Kadena Snap. + +**Request Example**: + +```javascript +await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_setHardwareAccountName', + params: { + id: '', + name: 'New Hardware Account Name', + }, + }, + }, +}); +``` + +**Response**: None + +--- + +### kda_signTransaction + +**Description**: Signs a transaction using the Kadena Snap. + +**Request Example**: + +```javascript +const signature = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_signTransaction', + params: { + id: '', + transaction: '', + }, + }, + }, +}); +``` + +**Response**: `SignTransactionResponse` + +--- + +## Example Usage in a DApp + +To leverage these RPC methods in a React application, you can implement a custom hook, such as `useKadenaSnap`. This hook provides an interface for using the RPC methods within the React component lifecycle. + +```typescript +import { useState } from 'react'; +import { SnapAccount, SnapNetwork } from '../types'; +import { defaultSnapOrigin } from '../config/snap'; + +export default function useKadenaSnap() { + const [connected, setConnected] = useState(false); + + const checkConnection = async (): Promise => { + try { + const isConnected = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_checkConnection' }, + }, + }); + setConnected(isConnected !== undefined && isConnected !== null); + return isConnected ?? false; + } catch (error) { + console.error('Error checking connection:', error); + return false; + } + }; + + const addAccount = async (): Promise => { + try { + const response = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_addAccount' }, + }, + }); + + if ( + !response || + !response.address || + !response.publicKey || + typeof response.index !== 'number' || + !response.name + ) { + throw new Error('Account creation failed: Missing essential data'); + } + + return response as SnapAccount; + } catch (error) { + console.error('Error creating account:', error); + throw error; + } + }; + + const getAccounts = async (): Promise => { + try { + const accounts = await window.ethereum.request< + (SnapAccount | undefined)[] + >({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getAccounts' }, + }, + }); + + if (!accounts) { + throw new Error('No accounts returned from Kadena snap'); + } + + return accounts.filter( + (account): account is SnapAccount => account !== undefined, + ); + } catch (error) { + console.error('Error getting accounts:', error); + return []; + } + }; + + const getNetworks = async (): Promise => { + try { + const networks = await window.ethereum.request< + (SnapNetwork | undefined)[] + >({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { method: 'kda_getNetworks' }, + }, + }); + + return (networks ?? []).filter( + (network): network is SnapNetwork => !!network, + ); + } catch (error) { + console.error('Error getting networks:', error); + return []; + } + }; + + const addNetwork = async ( + network: Partial, + ): Promise> => { + try { + const newNetwork = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_storeNetwork', + params: { + network: { + name: network.name!, + chainId: network.chainId!, + networkId: network.networkId!, + nodeUrl: network.nodeUrl!, + blockExplorerTransaction: network.blockExplorerTransaction!, + blockExplorerAddress: network.blockExplorerAddress!, + blockExplorerAddressTransactions: + network.blockExplorerAddressTransactions!, + isTestnet: network.isTestnet!, + transactionListUrl: network.transactionListUrl!, + transactionListTtl: network.transactionListTtl!, + buyPageUrl: network.buyPageUrl!, + }, + }, + }, + }, + }); + + if (!newNetwork) { + throw new Error('Failed to add network: No network returned'); + } + + return newNetwork; + } catch (error) { + console.error('Error adding network:', error); + throw error; + } + }; + + const deleteNetwork = async (networkId: string): Promise => { + try { + await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_deleteNetwork', + params: { networkId }, + }, + }, + }); + } catch (error) { + console.error('Error deleting network:', error); + throw error; + } + }; + + const signMessage = async ( + id: string, + transaction: string, + ): Promise => { + try { + const signature = await window.ethereum.request({ + method: 'wallet_invokeSnap', + params: { + snapId: defaultSnapOrigin, + request: { + method: 'kda_signTransaction', + params: { id, transaction }, + }, + }, + }); + if (!signature) { + throw new Error('Signing failed'); + } + return signature.toString().replace('0x', ''); + } catch (error) { + console.error('Error signing transaction:', error); + throw error; + } + }; + + return { + connected, + checkConnection, + addAccount, + getAccounts, + getNetworks, + addNetwork, + deleteNetwork, + signMessage, + }; +} +``` From 156fc16191af30d0db2cd4d25a9d0503c9e6ae5e Mon Sep 17 00:00:00 2001 From: Linda Ortega Date: Mon, 7 Oct 2024 16:24:10 -0400 Subject: [PATCH 2/2] Some rewording to provide context for req and resp types --- packages/libs/snap/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/libs/snap/README.md b/packages/libs/snap/README.md index cae9ecfba6..9f41c197a1 100644 --- a/packages/libs/snap/README.md +++ b/packages/libs/snap/README.md @@ -139,7 +139,7 @@ const isConnected = await window.ethereum.request({ }); ``` -**Response**: `CheckConnectionResponse` +**Response**: [`CheckConnectionResponse`](#type-definitions) --- @@ -159,13 +159,13 @@ const account = await window.ethereum.request({ }); ``` -**Response**: `SnapAccount` +**Response**: [`SnapAccount`](#type-definitions) --- ### kda_addHardwareAccount -**Description**: Adds a new hardware account from the Kadena Ledger App. NOTE: The Snap doesn't currently support signing with these hardware accounts. +**Description**: Adds a new hardware account from the Kadena Ledger App. Please note that the Snap doesn't currently support signing with these hardware accounts. **Request Example**: @@ -186,7 +186,7 @@ const account = await window.ethereum.request({ }); ``` -**Response**: `SnapAccount` +**Response**: [`SnapAccount`](#type-definitions) --- @@ -252,13 +252,13 @@ const accounts = await window.ethereum.request({ }); ``` -**Response**: `SnapAccount[]` +**Response**: [`SnapAccount[]`](#type-definitions) --- ### kda_getHardwareAccounts -**Description**: Retrieves all hardware accounts from the Kadena Snap. NOTE: The Snap doesn't currently support signing with these hardware accounts. +**Description**: Retrieves all hardware accounts from the Kadena Snap. Please note that the Snap doesn't currently support signing with these hardware accounts. **Request Example**: @@ -272,7 +272,7 @@ const hardwareAccounts = await window.ethereum.request({ }); ``` -**Response**: `SnapAccount[]` +**Response**: [`SnapAccount[]`](#type-definitions) --- @@ -292,7 +292,7 @@ const networks = await window.ethereum.request({ }); ``` -**Response**: `SnapNetwork[]` +**Response**: [`SnapNetwork[]`](#type-definitions) --- @@ -325,7 +325,7 @@ await window.ethereum.request({ }); ``` -**Response**: `SnapNetwork` +**Response**: [`SnapNetwork`](#type-definitions) --- @@ -368,7 +368,7 @@ const activeNetwork = await window.ethereum.request({ }); ``` -**Response**: `GetActiveNetworkResponse` +**Response**: [`GetActiveNetworkResponse`](#type-definitions) --- @@ -449,7 +449,7 @@ await window.ethereum.request({ ### kda_signTransaction -**Description**: Signs a transaction using the Kadena Snap. +**Description**: Signs a transaction using the Kadena Snap. The transaction is expected to be a stringified JSON of the type [`ICommandPayload`](https://github.com/kadena-community/kadena.js/blob/f88301538ee41b77b957edf58a8a5edb4fba44cb/packages/libs/types/src/PactCommand.ts#L176). **Request Example**: @@ -469,7 +469,7 @@ const signature = await window.ethereum.request({ }); ``` -**Response**: `SignTransactionResponse` +**Response**: [`SignTransactionResponse`](#type-definitions) ---