From 29e2071de77b5d895333408dafcd4a5673bc1ac7 Mon Sep 17 00:00:00 2001 From: Dhruv Kelawala Date: Wed, 31 Jan 2024 16:31:12 +0000 Subject: [PATCH] feat: enhance RpcMessage and EventListener types (#211) * feat: enhance RpcMessage and EventListener types * chore: export more types --- packages/core/src/StarknetWindowObject.ts | 302 +------------------- packages/core/src/main.ts | 35 ++- packages/core/src/rpcMessage.ts | 325 ++++++++++++++++++++++ packages/core/src/types.ts | 10 +- packages/core/src/wallet/filter.ts | 7 +- packages/core/src/walletEvents.ts | 17 ++ 6 files changed, 381 insertions(+), 315 deletions(-) create mode 100644 packages/core/src/rpcMessage.ts create mode 100644 packages/core/src/walletEvents.ts diff --git a/packages/core/src/StarknetWindowObject.ts b/packages/core/src/StarknetWindowObject.ts index e692f71..8a83961 100644 --- a/packages/core/src/StarknetWindowObject.ts +++ b/packages/core/src/StarknetWindowObject.ts @@ -1,304 +1,20 @@ -export enum StarknetChainId { - SN_MAIN = "0x534e5f4d41494e", - SN_GOERLI = "0x534e5f474f45524c49", -} - -export enum Permission { - Accounts = "accounts", -} - -type FELT = string - -type Call = { - contract_address: FELT - entrypoint: string - calldata?: FELT[] -} - -type SIERRA_ENTRY_POINT = { - selector: FELT - function_idx: number -} - -type StarknetMerkleType = { - name: string - type: "merkletree" - contains: string -} +import { RequestFn } from "./rpcMessage" +import { WalletEventHandlers } from "./walletEvents" -/** - * A single type, as part of a struct. The `type` field can be any of the EIP-712 supported types. - * - * Note that the `uint` and `int` aliases like in Solidity, and fixed point numbers are not supported by the EIP-712 - * standard. - */ -type StarknetType = - | { - name: string - type: string - } - | StarknetMerkleType - -/** - * The EIP712 domain struct. Any of these fields are optional, but it must contain at least one field. - */ -interface StarknetDomain extends Record { - name?: string - version?: string - chainId?: string | number -} - -/** - * The complete typed data, with all the structs, domain data, primary type of the message, and the message itself. - */ -export interface TypedData { - types: Record - primaryType: string - domain: StarknetDomain - message: Record -} - -export type AccountChangeEventHandler = (accounts?: string[]) => void - -export type NetworkChangeEventHandler = ( - chainId?: StarknetChainId, - accounts?: string[], +type WalletEventListener = ( + event: E, + handleEvent: WalletEventHandlers[E], ) => void -export type WalletEvents = - | { - type: "accountsChanged" - handler: AccountChangeEventHandler - } - | { - type: "networkChanged" - handler: NetworkChangeEventHandler - } - -/** - * INVOKE_TXN_V1 - * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json - */ -export interface AddInvokeTransactionParameters { - /** - * Calls to invoke by the account - */ - calls: Call[] -} -export interface AddInvokeTransactionResult { - /** - * The hash of the invoke transaction - */ - transaction_hash: FELT -} - -/** - * BROADCASTED_DECLARE_TXN_V2 - * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json - */ -export interface AddDeclareTransactionParameters { - /** - * The hash of the Cairo assembly resulting from the Sierra compilation - */ - compiled_class_hash: FELT - contract_class: { - /** - * The list of Sierra instructions of which the program consists - */ - sierra_program: FELT[] - /** - * The version of the contract class object. Currently, the Starknet OS supports version 0.1.0 - */ - contract_class_version: string - /** - * Entry points by type - */ - entry_points_by_type: { - CONSTRUCTOR: SIERRA_ENTRY_POINT[] - EXTERNAL: SIERRA_ENTRY_POINT[] - L1_HANDLER: SIERRA_ENTRY_POINT[] - } - /** - * The class ABI, as supplied by the user declaring the class - */ - abi?: string - } -} -export interface AddDeclareTransactionResult { - /** - * The hash of the declare transaction - */ - transaction_hash: FELT - /** - * The hash of the declared class - */ - class_hash: FELT -} - -/** - * DEPLOY_ACCOUNT_TXN_V1 - * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json - */ -export interface AddDeployAccountTransactionParameters { - /** - * The salt for the address of the deployed contract - */ - contract_address_salt: FELT - /** - * The parameters passed to the constructor - */ - constructor_calldata: FELT[] - /** - * The hash of the deployed contract's class - */ - class_hash: FELT -} -export interface AddDeployAccountTransactionResult { - /** - * The hash of the deploy transaction - */ - transaction_hash: FELT - /** - * The address of the new contract - */ - contract_address: FELT -} - -/** - * EIP-1102: - * @see https://eips.ethereum.org/EIPS/eip-1102 - */ -export interface RequestAccountsParameters { - /** - * If true, the wallet will not show the wallet-unlock UI in case of a locked wallet, - * nor the dApp-approve UI in case of a non-allowed dApp. - */ - silentMode?: boolean -} - -/** - * EIP-747: - * @see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md - */ -export interface WatchAssetParameters { - type: "ERC20" // The asset's interface, e.g. 'ERC20' - options: { - address: string // The hexadecimal Starknet address of the token contract - symbol?: string // A ticker symbol or shorthand, up to 5 alphanumerical characters - decimals?: number // The number of asset decimals - image?: string // A string url of the token logo - name?: string // The name of the token - not in spec - } -} - -/** - * EIP-3085: - * @see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3085.md - */ -export interface AddStarknetChainParameters { - id: string - chainId: string // A 0x-prefixed hexadecimal string - chainName: string - rpcUrls?: string[] - blockExplorerUrls?: string[] - - nativeCurrency?: { - address: string // Not part of the standard, but required by Starknet as it can work with any ERC20 token as the fee token - name: string - symbol: string // 2-6 characters long - decimals: number - } // Currently ignored. - iconUrls?: string[] // Currently ignored. -} - -export interface SwitchStarknetChainParameters { - chainId: string // A 0x-prefixed hexadecimal string -} - -// see https://community.starknet.io/t/snip-deployment-interface-between-dapps-and-wallets/101923 -export interface GetDeploymentDataResult { - address: FELT // the expected address, used to double-check the returned data - class_hash: FELT // The class hash of the contract to deploy - salt: FELT // The salt used for the computation of the account address - calldata: FELT[] // An array of felts - sigdata?: FELT[] // An optional array of felts to be added in the signature - version: 0 | 1 // Cairo version (an integer) -} - -export type RpcMessage = - | { - type: "wallet_getPermissions" - result: Permission[] - } - | { - type: "wallet_requestAccounts" - params?: RequestAccountsParameters - result: string[] - } - | { - type: "wallet_watchAsset" - params: WatchAssetParameters - result: boolean - } - | { - type: "wallet_addStarknetChain" - params: AddStarknetChainParameters - result: boolean - } - | { - type: "wallet_switchStarknetChain" - params: SwitchStarknetChainParameters - result: boolean - } - | { - type: "wallet_requestChainId" - result: StarknetChainId // returns the chain ID of the current network - } - | { - type: "wallet_deploymentData" - result: GetDeploymentDataResult - } - | { - type: "starknet_addInvokeTransaction" - params: AddInvokeTransactionParameters - result: AddInvokeTransactionResult - } - | { - type: "starknet_addDeclareTransaction" - params: AddDeclareTransactionParameters - result: AddDeclareTransactionResult - } - | { - type: "starknet_addDeployAccountTransaction" - params: AddDeployAccountTransactionParameters - result: AddDeployAccountTransactionResult - } - | { - type: "starknet_signTypedData" - params: TypedData - result: string[] - } - | { - type: "starknet_supportedSpecs" - result: string[] // supported starknet specs' tags (see https://github.com/starkware-libs/starknet-specs) - } - +// Implement the StarknetWindowObject interface with the improved request method export interface StarknetWindowObject { id: string name: string version: string icon: string | { dark: string; light: string } - request: ( - call: Omit, - ) => Promise - on: ( - event: E["type"], - handleEvent: E["handler"], - ) => void - off: ( - event: E["type"], - handleEvent: E["handler"], - ) => void + request: RequestFn + on: WalletEventListener + off: WalletEventListener } declare global { diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 6533ce0..f1b7a8e 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -1,6 +1,7 @@ -import { Permission, type StarknetWindowObject } from "./StarknetWindowObject" +import { type StarknetWindowObject } from "./StarknetWindowObject" import discovery, { type WalletProvider } from "./discovery" import { LocalStorageWrapper } from "./localStorageStore" +import { Permission } from "./rpcMessage" import type { GetStarknetOptions, GetStarknetResult } from "./types" import { pipe } from "./utils" import { filterBy, filterByAuthorized } from "./wallet/filter" @@ -8,8 +9,9 @@ import { isWalletObj } from "./wallet/isWalletObject" import { scanObjectForWallets } from "./wallet/scan" import { sortBy } from "./wallet/sort" +export type { StarknetWindowObject } from "./StarknetWindowObject" + export type { - AccountChangeEventHandler, AddDeclareTransactionParameters, AddDeclareTransactionResult, AddDeployAccountTransactionParameters, @@ -17,19 +19,26 @@ export type { AddInvokeTransactionParameters, AddInvokeTransactionResult, AddStarknetChainParameters, - NetworkChangeEventHandler, RequestAccountsParameters, - RpcMessage, StarknetChainId, - StarknetWindowObject, SwitchStarknetChainParameters, + GetDeploymentDataResult, + WatchAssetParameters, TypedData, + RequestFn, + RpcMessage, + IsParamsOptional, + RpcTypeToMessageMap, +} from "./rpcMessage" + +export type { WalletEvents, - WatchAssetParameters, - GetDeploymentDataResult, -} from "./StarknetWindowObject" + AccountChangeEventHandler, + NetworkChangeEventHandler, + WalletEventHandlers, +} from "./walletEvents" -export { Permission } from "./StarknetWindowObject" +export { Permission } from "./rpcMessage" export type { DisconnectOptions, @@ -104,13 +113,15 @@ export function getStarknet( enable: async (wallet, options) => { await wallet.request({ type: "wallet_requestAccounts", - params: options, + params: { + silentMode: options?.silentMode, + }, }) // check for permissions - const permissions = (await wallet.request({ + const permissions = await wallet.request({ type: "wallet_getPermissions", - })) as Permission[] + }) if (!permissions?.includes(Permission.Accounts)) { throw new Error("Failed to connect to wallet") } diff --git a/packages/core/src/rpcMessage.ts b/packages/core/src/rpcMessage.ts new file mode 100644 index 0000000..045d5f2 --- /dev/null +++ b/packages/core/src/rpcMessage.ts @@ -0,0 +1,325 @@ +export enum StarknetChainId { + SN_MAIN = "0x534e5f4d41494e", + SN_GOERLI = "0x534e5f474f45524c49", +} + +export enum Permission { + Accounts = "accounts", +} + +type FELT = string + +type Call = { + contract_address: FELT + entrypoint: string + calldata?: FELT[] +} + +type SIERRA_ENTRY_POINT = { + selector: FELT + function_idx: number +} + +type StarknetMerkleType = { + name: string + type: "merkletree" + contains: string +} + +/** + * A single type, as part of a struct. The `type` field can be any of the EIP-712 supported types. + * + * Note that the `uint` and `int` aliases like in Solidity, and fixed point numbers are not supported by the EIP-712 + * standard. + */ +type StarknetType = + | { + name: string + type: string + } + | StarknetMerkleType + +/** + * The EIP712 domain struct. Any of these fields are optional, but it must contain at least one field. + */ +interface StarknetDomain extends Record { + name?: string + version?: string + chainId?: string | number +} + +/** + * The complete typed data, with all the structs, domain data, primary type of the message, and the message itself. + */ +export interface TypedData { + types: Record + primaryType: string + domain: StarknetDomain + message: Record +} + +/** + * INVOKE_TXN_V1 + * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json + */ +export interface AddInvokeTransactionParameters { + /** + * Calls to invoke by the account + */ + calls: Call[] +} +export interface AddInvokeTransactionResult { + /** + * The hash of the invoke transaction + */ + transaction_hash: FELT +} + +/** + * BROADCASTED_DECLARE_TXN_V2 + * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json + */ +export interface AddDeclareTransactionParameters { + /** + * The hash of the Cairo assembly resulting from the Sierra compilation + */ + compiled_class_hash: FELT + contract_class: { + /** + * The list of Sierra instructions of which the program consists + */ + sierra_program: FELT[] + /** + * The version of the contract class object. Currently, the Starknet OS supports version 0.1.0 + */ + contract_class_version: string + /** + * Entry points by type + */ + entry_points_by_type: { + CONSTRUCTOR: SIERRA_ENTRY_POINT[] + EXTERNAL: SIERRA_ENTRY_POINT[] + L1_HANDLER: SIERRA_ENTRY_POINT[] + } + /** + * The class ABI, as supplied by the user declaring the class + */ + abi?: string + } +} +export interface AddDeclareTransactionResult { + /** + * The hash of the declare transaction + */ + transaction_hash: FELT + /** + * The hash of the declared class + */ + class_hash: FELT +} + +/** + * DEPLOY_ACCOUNT_TXN_V1 + * @see https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json + */ +export interface AddDeployAccountTransactionParameters { + /** + * The salt for the address of the deployed contract + */ + contract_address_salt: FELT + /** + * The parameters passed to the constructor + */ + constructor_calldata: FELT[] + /** + * The hash of the deployed contract's class + */ + class_hash: FELT +} +export interface AddDeployAccountTransactionResult { + /** + * The hash of the deploy transaction + */ + transaction_hash: FELT + /** + * The address of the new contract + */ + contract_address: FELT +} + +/** + * EIP-1102: + * @see https://eips.ethereum.org/EIPS/eip-1102 + */ +export interface RequestAccountsParameters { + /** + * If true, the wallet will not show the wallet-unlock UI in case of a locked wallet, + * nor the dApp-approve UI in case of a non-allowed dApp. + */ + silentMode?: boolean +} + +/** + * EIP-747: + * @see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md + */ +export interface WatchAssetParameters { + type: "ERC20" // The asset's interface, e.g. 'ERC20' + options: { + address: string // The hexadecimal Starknet address of the token contract + symbol?: string // A ticker symbol or shorthand, up to 5 alphanumerical characters + decimals?: number // The number of asset decimals + image?: string // A string url of the token logo + name?: string // The name of the token - not in spec + } +} + +/** + * EIP-3085: + * @see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3085.md + */ +export interface AddStarknetChainParameters { + id: string + chainId: string // A 0x-prefixed hexadecimal string + chainName: string + rpcUrls?: string[] + blockExplorerUrls?: string[] + + nativeCurrency?: { + address: string // Not part of the standard, but required by Starknet as it can work with any ERC20 token as the fee token + name: string + symbol: string // 2-6 characters long + decimals: number + } // Currently ignored. + iconUrls?: string[] // Currently ignored. +} + +export interface SwitchStarknetChainParameters { + chainId: string // A 0x-prefixed hexadecimal string +} + +// see https://community.starknet.io/t/snip-deployment-interface-between-dapps-and-wallets/101923 +export interface GetDeploymentDataResult { + address: FELT // the expected address, used to double-check the returned data + class_hash: FELT // The class hash of the contract to deploy + salt: FELT // The salt used for the computation of the account address + calldata: FELT[] // An array of felts + sigdata?: FELT[] // An optional array of felts to be added in the signature + version: 0 | 1 // Cairo version (an integer) +} + +/** + * Maps each RPC message type to its corresponding parameters and result type. + */ +export interface RpcTypeToMessageMap { + /** + * Get permissions from the wallet. + * @returns An array of permissions. + */ + wallet_getPermissions: { params?: never; result: Permission[] } + + /** + * Request accounts from the wallet. + * @param params Optional parameters for requesting accounts. + * @returns An array of account addresses as strings. + */ + wallet_requestAccounts: { + params?: RequestAccountsParameters + result: string[] + } + + /** + * Watch an asset in the wallet. + * @param params The parameters required to watch an asset. + * @returns A boolean indicating if the operation was successful. + */ + wallet_watchAsset: { params: WatchAssetParameters; result: boolean } + + /** + * Add a new Starknet chain to the wallet. + * @param params The parameters required to add a new chain. + * @returns A boolean indicating if the operation was successful. + */ + wallet_addStarknetChain: { + params: AddStarknetChainParameters + result: boolean + } + + /** + * Switch the current Starknet chain in the wallet. + * @param params The parameters required to switch chains. + * @returns A boolean indicating if the operation was successful. + */ + wallet_switchStarknetChain: { + params: SwitchStarknetChainParameters + result: boolean + } + + /** + * Request the current chain ID from the wallet. + * @returns The current Starknet chain ID. + */ + wallet_requestChainId: { params?: never; result: StarknetChainId } + + /** + * Get deployment data for a contract. + * @returns The deployment data result. + */ + wallet_deploymentData: { params?: never; result: GetDeploymentDataResult } + + /** + * Add an invoke transaction to the wallet. + * @param params The parameters required for the invoke transaction. + * @returns The result of adding the invoke transaction. + */ + starknet_addInvokeTransaction: { + params: AddInvokeTransactionParameters + result: AddInvokeTransactionResult + } + + /** + * Add a declare transaction to the wallet. + * @param params The parameters required for the declare transaction. + * @returns The result of adding the declare transaction. + */ + starknet_addDeclareTransaction: { + params: AddDeclareTransactionParameters + result: AddDeclareTransactionResult + } + + /** + * Add a deploy account transaction to the wallet. + * @param params The parameters required for the deploy account transaction. + * @returns The result of adding the deploy account transaction. + */ + starknet_addDeployAccountTransaction: { + params: AddDeployAccountTransactionParameters + result: AddDeployAccountTransactionResult + } + + /** + * Sign typed data using the wallet. + * @param params The typed data to sign. + * @returns An array of signatures as strings. + */ + starknet_signTypedData: { params: TypedData; result: string[] } + + /** + * Get the list of supported specifications. + * @returns An array of supported specification strings. + */ + starknet_supportedSpecs: { params?: never; result: string[] } +} + +export type RpcMessage = { + [K in keyof RpcTypeToMessageMap]: { type: K } & RpcTypeToMessageMap[K] +}[keyof RpcTypeToMessageMap] + +export type IsParamsOptional = + undefined extends RpcTypeToMessageMap[T]["params"] ? true : false + +export type RequestFn = ( + call: { type: T } & (IsParamsOptional extends true + ? { params?: RpcTypeToMessageMap[T]["params"] } + : { params: RpcTypeToMessageMap[T]["params"] }), +) => Promise diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7c4f935..775775e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,11 +1,9 @@ +import { StarknetWindowObject } from "./StarknetWindowObject" +import { WalletProvider } from "./discovery" import { IStorageWrapper } from "./localStorageStore" -import { Sort } from "./wallet/sort" +import { RequestAccountsParameters } from "./rpcMessage" import { FilterList } from "./wallet/filter" -import { - RequestAccountsParameters, - StarknetWindowObject, -} from "./StarknetWindowObject" -import { WalletProvider } from "./discovery" +import { Sort } from "./wallet/sort" export type { WalletProvider } from "./discovery" diff --git a/packages/core/src/wallet/filter.ts b/packages/core/src/wallet/filter.ts index d4460d4..c504827 100644 --- a/packages/core/src/wallet/filter.ts +++ b/packages/core/src/wallet/filter.ts @@ -1,5 +1,6 @@ -import { Permission, type StarknetWindowObject } from "../StarknetWindowObject" +import { type StarknetWindowObject } from "../StarknetWindowObject" import type { WalletProvider } from "../discovery" +import { Permission } from "../rpcMessage" export type FilterList = string[] interface FilterByOptions { @@ -35,9 +36,7 @@ export const filterByAuthorized = async ( wallets.map((w) => w .request({ type: "wallet_getPermissions" }) - .then((result) => - (result as Permission[])?.includes(Permission.Accounts), - ) + .then((result) => result.includes(Permission.Accounts)) .catch(() => false), ), ) diff --git a/packages/core/src/walletEvents.ts b/packages/core/src/walletEvents.ts new file mode 100644 index 0000000..4933fda --- /dev/null +++ b/packages/core/src/walletEvents.ts @@ -0,0 +1,17 @@ +import { StarknetChainId } from "./rpcMessage" + +export type AccountChangeEventHandler = (accounts?: string[]) => void + +export type NetworkChangeEventHandler = ( + chainId?: StarknetChainId, + accounts?: string[], +) => void + +export interface WalletEventHandlers { + accountsChanged: AccountChangeEventHandler + networkChanged: NetworkChangeEventHandler +} + +export type WalletEvents = { + [E in keyof WalletEventHandlers]: { type: E; handler: WalletEventHandlers[E] } +}[keyof WalletEventHandlers]