From 6a308024b754c48b79c9f43227561faaf96e8863 Mon Sep 17 00:00:00 2001 From: Tomek Marciniak Date: Sun, 22 Sep 2024 17:54:25 +0200 Subject: [PATCH] feat(connect): add wallet client docs --- apps/docs/src/pages/connect/wallet-client.mdx | 229 ++++++++++++++++++ apps/docs/vocs.config.ts | 3 + packages/connect/src/client.spec.ts | 3 +- packages/connect/src/client.ts | 45 ++-- packages/connect/src/index.ts | 1 + 5 files changed, 264 insertions(+), 17 deletions(-) create mode 100644 apps/docs/src/pages/connect/wallet-client.mdx diff --git a/apps/docs/src/pages/connect/wallet-client.mdx b/apps/docs/src/pages/connect/wallet-client.mdx new file mode 100644 index 0000000..d126985 --- /dev/null +++ b/apps/docs/src/pages/connect/wallet-client.mdx @@ -0,0 +1,229 @@ +# Wallet Client [One API to rull them all.] + +If you missed a Wallet Client from Viem, you can use MinaJS's Wallet Client to interact with Mina wallets. It's a one API that wraps over various wallet types and connectivities. + +## Client properties + +- `network`: Network type. `devnet` or `mainnet`. +- `account`: Account object compatible with `toAccount` format. `string` or `Account` object. If not provided, the client will use the last injected wallet. +- `providerSource`: Provider source. `klesia` (JSON-RPC account), `window.mina` (last injected wallet), `string` (slug of specific browser wallet). Default is `window.mina`. + +## Initialize client + +```ts twoslash +import { createWalletClient } from '@mina-js/connect' + +const client = createWalletClient({ network: "devnet" }); +``` + +## Querying the data + +### From injected wallet + +Query from the last injected wallet (using `window.mina`): + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const balance = await client.getBalance(); +``` + +Query from a specific browser wallet (using slug of a specific injected wallet): + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ + account, + network: "devnet", + providerSource: 'pallad' +}); + +const balance = await client.getBalance(); +``` + +### From Klesia JSON-RPC + +Query data from our [Klesia JSON-RPC](/klesia): + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ + account, + network: "devnet", + providerSource: 'klesia' +}); + +const balance = await client.getBalance(); +``` + +## Wallet queries + +### Get balance + +Query current wallet's balance. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const balance = await client.getBalance(); +``` + +### Get accounts + +Query wallet addresses of an injected wallet. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const balance = await client.getAccounts(); +``` + +### Get transaction count + +Query the number of transactions of a wallet (used as nonce). + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const transactionCount = await client.getTransactionCount(); +``` + +### Get chain ID + +Query the chain ID of the network. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const chainId = await client.getChainId(); +``` + +### Get estimated fees + +Query the estimated fees of a transaction. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect' + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const fees = await client.estimateFees(); +``` + +## Wallet commands + +If you provide a local wallet, Wallet Client will sign with it, otherwise, it will use the injected wallet. + +### Sign a message + +Sign a message with either the injected wallet or a local wallet. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect'; + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ network: "devnet" }); + +const signature = await client.signMessage({ message: 'Hello World' }) +``` + +### Sign a transaction + +Sign a transaction with either the injected wallet or a local wallet. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect'; + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const signature = await client.signTransaction({ + transaction: { + nonce: 1n, + from: "B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5", + to: "B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5", + amount: 3000000000n, + fee: 100000000n, + } +}) +``` + +### Sign fields + +Sign fields of a transaction with either the injected wallet or a local wallet. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect'; + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const signature = await client.signFields({ + fields: [1n, 2n, 3n] +}) +``` + +### Create a nullifier + +Create a nullifier with either the injected wallet or a local wallet. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect'; + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const nullifier = await client.createNullifier({ + message: [1n, 2n, 3n] +}); +``` + +### Prepare a transaction request + +There is a helper method to prepare a transaction request that lacks either nonce or fee. This will fetch the correct nonce for you and estimate a `medium` fee that ensures the transaction is included in the next block. + +```ts twoslash +import { toAccount } from "@mina-js/accounts"; +import { createWalletClient } from '@mina-js/connect'; + +const account = toAccount('B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5'); +const client = createWalletClient({ account, network: "devnet" }); + +const transactionRequest = await client.prepareTransactionRequest({ + from: "B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5", + to: "B62qmWKtvNQTtUqo1LxfEEDLyWMg59cp6U7c4uDC7aqgaCEijSc3Hx5", + amount: 3000000000n, +}) +``` diff --git a/apps/docs/vocs.config.ts b/apps/docs/vocs.config.ts index 5893592..dd9dbbb 100644 --- a/apps/docs/vocs.config.ts +++ b/apps/docs/vocs.config.ts @@ -2,6 +2,8 @@ import { remarkMermaid } from "@theguild/remark-mermaid"; import { defineConfig } from "vocs"; export default defineConfig({ + ogImageUrl: + "https://vocs.dev/api/og?logo=%logo&title=%title&description=%description", title: "MinaJS", description: "The TypeScript interface for Mina Protocol.", rootDir: "src", @@ -87,6 +89,7 @@ export default defineConfig({ { text: "Getting Started", link: "/connect/getting-started" }, { text: "Provider Discovery", link: "/connect/provider-discovery" }, { text: "Wallet Interface", link: "/connect/wallet-interface" }, + { text: "Wallet Client", link: "/connect/wallet-client" }, ], }, { diff --git a/packages/connect/src/client.spec.ts b/packages/connect/src/client.spec.ts index d6b9e82..aa6adf1 100644 --- a/packages/connect/src/client.spec.ts +++ b/packages/connect/src/client.spec.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { privateKeyToAccount } from "@mina-js/accounts"; -import { toAccount } from "@mina-js/accounts"; +import { privateKeyToAccount, toAccount } from "@mina-js/accounts"; import { Test } from "@mina-js/shared"; import { createWalletClient } from "./client"; diff --git a/packages/connect/src/client.ts b/packages/connect/src/client.ts index c6c5f8a..74a2269 100644 --- a/packages/connect/src/client.ts +++ b/packages/connect/src/client.ts @@ -15,7 +15,7 @@ export type WalletProviderSlug = "pallad"; export type ProviderSource = "window.mina" | "klesia" | WalletProviderSlug; export type CreateWalletClientProps = { - account: Account; + account?: Account; network: "mainnet" | "devnet"; providerSource?: ProviderSource; }; @@ -40,7 +40,7 @@ export const createWalletClient = ({ const klesiaClient = createClient({ network }); const getAccounts = async () => { return match(providerSource) - .with("klesia", () => [account.publicKey]) + .with("klesia", () => (account ? [account.publicKey] : [])) .otherwise(async () => { const provider = getWalletProvider(providerSource); const { result } = await provider.request<"mina_accounts">({ @@ -50,23 +50,30 @@ export const createWalletClient = ({ }); }; const getBalance = async () => { + const getBalanceFromInjectedWallet = async () => { + const provider = getWalletProvider(providerSource); + const { result } = await provider.request<"mina_getBalance">({ + method: "mina_getBalance", + }); + return result; + }; + const getBalanceFromKlesia = async (account: Account) => { + const { result } = await klesiaClient.request<"mina_getBalance">({ + method: "mina_getBalance", + params: [account.publicKey], + }); + return BigInt(result); + }; return match(providerSource) .with("klesia", async () => { - const { result } = await klesiaClient.request<"mina_getBalance">({ - method: "mina_getBalance", - params: [account.publicKey], - }); - return BigInt(result); + if (account) return getBalanceFromKlesia(account); + return getBalanceFromInjectedWallet(); }) - .otherwise(async () => { - const provider = getWalletProvider(providerSource); - const { result } = await provider.request<"mina_getBalance">({ - method: "mina_getBalance", - }); - return result; - }); + .otherwise(getBalanceFromInjectedWallet); }; const getTransactionCount = async () => { + if (!account) + throw new Error("Account is required to get transaction count"); const { result } = await klesiaClient.request<"mina_getTransactionCount">({ method: "mina_getTransactionCount", params: [account.publicKey], @@ -90,18 +97,22 @@ export const createWalletClient = ({ }); }; const signTransaction: SignTransaction = async (params) => { + if (!account) throw new Error("Account is required to sign transaction"); if (account.type !== "local") throw new Error("Account type not supported"); return account.signTransaction(params); }; const signMessage: SignMessage = async (params) => { + if (!account) throw new Error("Account is required to sign message"); if (account.type !== "local") throw new Error("Account type not supported"); return account.signMessage(params); }; const signFields: SignFields = async (params) => { + if (!account) throw new Error("Account is required to sign fields"); if (account.type !== "local") throw new Error("Account type not supported"); return account.signFields(params); }; const createNullifier: CreateNullifier = async (params) => { + if (!account) throw new Error("Account is required to create nullifier"); if (account.type !== "local") throw new Error("Account type not supported"); return account.createNullifier(params); }; @@ -109,7 +120,11 @@ export const createWalletClient = ({ const { result } = await klesiaClient.request<"mina_estimateFees">({ method: "mina_estimateFees", }); - return result; + return { + low: BigInt(result.low), + medium: BigInt(result.medium), + high: BigInt(result.high), + }; }; const prepareTransactionRequest = async ( transaction: PartiallyFormedTransactionProperties, diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 25a72a1..02c7f2a 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,3 +1,4 @@ export * from "./store"; export * from "./events"; +export * from "./client"; export type { MinaProviderDetail } from "@mina-js/providers";