diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 195d4f4..d1985c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,4 +12,4 @@ jobs: - run: bun i --no-save - run: bun run build - run: bun run test - - run: bunx pkg-pr-new publish './packages/klesia-sdk' './packages/accounts' + - run: bunx pkg-pr-new publish './packages/klesia-sdk' './packages/accounts' './packages/connect' diff --git a/apps/docs/package.json b/apps/docs/package.json index aa1f161..75816d2 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@mina-js/klesia-sdk": "workspace:*", + "@mina-js/connect": "workspace:*", "daisyui": "^4.12.10" } } diff --git a/apps/docs/src/pages/accounts/getting-started.mdx b/apps/docs/src/pages/accounts/getting-started.mdx index 3acac4f..7449a4d 100644 --- a/apps/docs/src/pages/accounts/getting-started.mdx +++ b/apps/docs/src/pages/accounts/getting-started.mdx @@ -12,6 +12,16 @@ MinaJS Accounts shares an API similar to [Viem](https://viem.sh/). $ npm install @mina-js/accounts ``` +### Nightly builds + +```sh +$ npm install https://pkg.pr.new/palladians/mina-js/@mina-js/accounts@main +``` + +:::warning +For now there are only [nightly builds](/getting-started#nightly-builds) available. The stable version will be released soon™️. +::: + ## Utilities ### generateMnemonic diff --git a/apps/docs/src/pages/connect/getting-started.mdx b/apps/docs/src/pages/connect/getting-started.mdx index 6193338..21311e9 100644 --- a/apps/docs/src/pages/connect/getting-started.mdx +++ b/apps/docs/src/pages/connect/getting-started.mdx @@ -1 +1,31 @@ # Getting Started [Start with MinaJS Connect.] + +## Installation + +```sh +$ npm install @mina-js/connect +``` + +### Nightly builds + +```sh +$ npm install https://pkg.pr.new/palladians/mina-js/@mina-js/connect@main +``` + +:::warning +For now there are only [nightly builds](/getting-started#nightly-builds) available. The stable version will be released soon™️. +::: + +## Find Mina wallets + +To discover injected wallet providers, we've developed a Provider Discovery reactive store utility. [Check out the usage](/connect/provider-discovery). + +## Window Polyfill + +For your convenient TypeScript development, we've created a window polyfill. This will make sure you have an autocompletion for MinaJS compliant providers. + +```ts +import '@mina-js/connect/window' + +const account = await window.mina.request({ method: 'mina_accounts' }) +``` diff --git a/apps/docs/src/pages/connect/provider-discovery.mdx b/apps/docs/src/pages/connect/provider-discovery.mdx new file mode 100644 index 0000000..a1a9935 --- /dev/null +++ b/apps/docs/src/pages/connect/provider-discovery.mdx @@ -0,0 +1,56 @@ +# Provider Discovery [Discover injected wallet providers with ease.] + +For your convenience, MinaJS Connect provides a simple way to discover all Mina's injected wallet providers. This is done by an API similar to [Wevm's MIPD](https://github.com/wevm/mipd). + +## Vanilla TypeScript + +```ts twoslash +import { createStore, type MinaProviderDetail } from '@mina-js/connect' + +const store = createStore() + +// Reactively, you can use MinaProviderDetail to type the array, but it fails in docs ffs. +const providersReactively = [] +const unsubscribe = store.subscribe((provider) => providersReactively.push(provider)) + +// Imperatively +const providersImperatively = store.getProviders() +``` + +## React + +```tsx twoslash +import { useSyncExternalStore } from 'react' +import { createStore } from '@mina-js/connect' + +const store = createStore() + +function Example() { + const providers = useSyncExternalStore(store.subscribe, store.getProviders) +} +``` + +## Svelte + +```svelte twoslash + +``` + +## Vue + +```vue twoslash + +``` diff --git a/apps/docs/src/pages/getting-started.mdx b/apps/docs/src/pages/getting-started.mdx index 845072a..3bb58d9 100644 --- a/apps/docs/src/pages/getting-started.mdx +++ b/apps/docs/src/pages/getting-started.mdx @@ -75,3 +75,9 @@ The libraries are available on npm. You can install them using npm, yarn, pnpm, - [MinaJS Connect](/connect) - [MinaJS Accounts](/accounts) - [Klesia SDK](/klesia/sdk) + +## Nightly builds + +We have nightly builds available for the SDK. You can find the latest nightly versions of our packages on: + +https://nightly.akryum.dev/palladians/mina-js diff --git a/apps/docs/src/pages/klesia/sdk.mdx b/apps/docs/src/pages/klesia/sdk.mdx index a9e0ba0..878946f 100644 --- a/apps/docs/src/pages/klesia/sdk.mdx +++ b/apps/docs/src/pages/klesia/sdk.mdx @@ -8,8 +8,14 @@ Klesia has a TypeScript SDK to interact with the JSON-RPC API. The SDK provides $ npm install @mina-js/klesia-sdk ``` +### Nightly builds + +```sh +$ npm install https://pkg.pr.new/palladians/mina-js/@mina-js/klesia-sdk@main +``` + :::warning -For now there are only [nightly builds](/klesia/sdk#nightly-builds) available. The stable version will be released soon™️. +For now there are only [nightly builds](/getting-started#nightly-builds) available. The stable version will be released soon™️. ::: ## Client Options @@ -34,9 +40,3 @@ const { result } = await client.request<'mina_getTransactionCount'>({ ## Methods Refer to the [RPC Methods](/klesia/rpc#rpc-methods) page for a complete list of methods available on Klesia. - -## Nightly Builds - -We have nightly builds available for the SDK. You can find the latest nightly versions of our packages on: - -https://nightly.akryum.dev/palladians/mina-js diff --git a/apps/docs/vocs.config.ts b/apps/docs/vocs.config.ts index d430cc6..6c55f37 100644 --- a/apps/docs/vocs.config.ts +++ b/apps/docs/vocs.config.ts @@ -85,6 +85,7 @@ export default defineConfig({ items: [ { text: "Introduction", link: "/connect" }, { text: "Getting Started", link: "/connect/getting-started" }, + { text: "Provider Discovery", link: "/connect/provider-discovery" }, ], }, { diff --git a/bun.lockb b/bun.lockb index 05992da..e9a3dbf 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/connect/package.json b/packages/connect/package.json index 644cedf..fac1203 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -4,10 +4,15 @@ "module": "dist/index.js", "types": "dist/index.d.ts", "exports": { + "./window": { + "types": "./dist/window.d.ts", + "default": "./dist/window.js", + "import": "./dist/window.mjs" + }, ".": { "types": "./dist/index.d.ts", - "default": "./dist/index.cjs", - "import": "./dist/index.js" + "default": "./dist/index.js", + "import": "./dist/index.mjs" } }, "scripts": { @@ -17,6 +22,7 @@ }, "dependencies": { "@mina-js/providers": "workspace:*", + "ts-pattern": "^5.3.1", "zod": "3.23.8" }, "peerDependencies": { diff --git a/packages/connect/src/__mocks__/provider.ts b/packages/connect/src/__mocks__/provider.ts index ac54a6a..ec6cbb3 100644 --- a/packages/connect/src/__mocks__/provider.ts +++ b/packages/connect/src/__mocks__/provider.ts @@ -10,7 +10,7 @@ export const mockedProvider: MinaProviderDetail = { }, provider: { request: mock(), - addListener: mock(), + on: mock(), removeListener: mock(), }, }; diff --git a/packages/connect/src/__snapshots__/store.spec.ts.snap b/packages/connect/src/__snapshots__/store.spec.ts.snap new file mode 100644 index 0000000..0d7e124 --- /dev/null +++ b/packages/connect/src/__snapshots__/store.spec.ts.snap @@ -0,0 +1,13 @@ +// Bun Snapshot v1, https://goo.gl/fbAQLP + +exports[`should initialize the store 1`] = ` +{ + "_listeners": [Function: _listeners], + "clear": [Function: clear], + "destroy": [Function: destroy], + "findProvider": [Function: findProvider], + "getProviders": [Function: getProviders], + "reset": [Function: reset], + "subscribe": [Function: subscribe], +} +`; diff --git a/packages/connect/src/events.spec.ts b/packages/connect/src/events.spec.ts index 485f555..2569313 100644 --- a/packages/connect/src/events.spec.ts +++ b/packages/connect/src/events.spec.ts @@ -5,7 +5,7 @@ import { announceProvider, requestProviders } from "./events"; type Resolver = (value: unknown) => void; -it("announcec Mina Provider with window event", async () => { +it("announces Mina Provider with window event", async () => { const listener = (resolve: Resolver) => ({ detail }: MinaAnnounceProviderEvent) => { diff --git a/packages/connect/src/events.ts b/packages/connect/src/events.ts index 4de04ad..60cfc7f 100644 --- a/packages/connect/src/events.ts +++ b/packages/connect/src/events.ts @@ -43,9 +43,10 @@ export function requestProviders( if (typeof window === "undefined") return; const handler = (event: MinaAnnounceProviderEvent) => listener(event.detail); - window.addEventListener("mina:announceProvider", handler); + window.addEventListener("mina:announceProvider", handler as never); window.dispatchEvent(new CustomEvent("mina:requestProvider")); - return () => window.removeEventListener("mina:announceProvider", handler); + return () => + window.removeEventListener("mina:announceProvider", handler as never); } diff --git a/packages/connect/src/global-polyfill.ts b/packages/connect/src/global-polyfill.ts new file mode 100644 index 0000000..7d6c183 --- /dev/null +++ b/packages/connect/src/global-polyfill.ts @@ -0,0 +1,15 @@ +import type { + MinaAnnounceProviderEvent, + MinaProviderClient, + MinaRequestProviderEvent, +} from "@mina-js/providers"; + +declare global { + interface WindowEventMap { + "mina:announceProvider": MinaAnnounceProviderEvent; + "mina:requestProvider": MinaRequestProviderEvent; + } + interface Window { + mina?: MinaProviderClient | undefined; + } +} diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index 96c500c..25a72a1 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,18 +1,3 @@ -import type { - MinaAnnounceProviderEvent, - MinaProviderClient, - MinaRequestProviderEvent, -} from "@mina-js/providers"; - -declare global { - interface WindowEventMap { - "mina:announceProvider": MinaAnnounceProviderEvent; - "mina:requestProvider": MinaRequestProviderEvent; - } - interface Window { - mina?: MinaProviderClient | undefined; - } -} - export * from "./store"; export * from "./events"; +export type { MinaProviderDetail } from "@mina-js/providers"; diff --git a/packages/connect/src/store.spec.ts b/packages/connect/src/store.spec.ts new file mode 100644 index 0000000..5453340 --- /dev/null +++ b/packages/connect/src/store.spec.ts @@ -0,0 +1,38 @@ +import { expect, it, mock } from "bun:test"; +import { mockedProvider } from "./__mocks__/provider"; +import { announceProvider } from "./events"; +import { createStore } from "./store"; + +it("should initialize the store", () => { + const store = createStore(); + expect(store).toMatchSnapshot(); +}); + +it("should update providers", () => { + const store = createStore(); + const listener = mock(); + store.subscribe(listener); + announceProvider(mockedProvider); + const provider = store.getProviders()[0]; + expect(listener).toHaveBeenCalled(); + expect(provider).toEqual(mockedProvider); +}); + +it("should unsubscribe from store", () => { + const store = createStore(); + const listener = mock(); + const unsub = store.subscribe(listener); + unsub(); + announceProvider(mockedProvider); + expect(listener).not.toHaveBeenCalled(); +}); + +it("should clear providers list", () => { + const store = createStore(); + const listener = mock(); + store.subscribe(listener); + announceProvider(mockedProvider); + store.clear(); + const providers = store.getProviders(); + expect(providers.length).toEqual(0); +}); diff --git a/packages/connect/src/unito.ts b/packages/connect/src/unito.ts new file mode 100644 index 0000000..c0395fc --- /dev/null +++ b/packages/connect/src/unito.ts @@ -0,0 +1,16 @@ +import type { MinaProviderDetail } from "@mina-js/providers"; +import { match } from "ts-pattern"; + +export const unito = (provider: MinaProviderDetail) => { + return match(provider) + .with({ info: { slug: "auro" } }, (provider) => { + return { + ...provider, + provider: { + ...provider.provider, + request: null, + }, + }; + }) + .otherwise((provider) => provider); +}; diff --git a/packages/connect/src/window.ts b/packages/connect/src/window.ts new file mode 100644 index 0000000..e44d983 --- /dev/null +++ b/packages/connect/src/window.ts @@ -0,0 +1 @@ +import "./global-polyfill"; diff --git a/packages/connect/tsup.config.ts b/packages/connect/tsup.config.ts index 6e9381b..39966ef 100644 --- a/packages/connect/tsup.config.ts +++ b/packages/connect/tsup.config.ts @@ -1,3 +1,7 @@ +import { defineConfig } from "tsup"; import sharedConfig from "../shared/tsup.config"; -export default sharedConfig; +export default defineConfig({ + ...sharedConfig, + entry: ["./src/index.ts", "./src/window.ts"], +}); diff --git a/packages/providers/src/types.ts b/packages/providers/src/types.ts index bf5a170..36cb12c 100644 --- a/packages/providers/src/types.ts +++ b/packages/providers/src/types.ts @@ -1,22 +1,17 @@ import type { + CreateNullifierParams, Nullifier, PublicKey, + SendTransactionParams, + SignFieldsParams, + SignMessageParams, + SignTransactionParams, SignedFields, SignedMessage, + SignedTransaction, TransactionReceipt, } from "@mina-js/shared"; -import type { - AddChainData, - CreateNullifierData, - SendTransactionData, - SignFieldsData, - SignMessageData, - SignTransactionData, - SwitchChainData, -} from "./validation"; - -// biome-ignore lint/suspicious/noExplicitAny: Deal with it. -type TODO = any; +import type { AddChainData, SwitchChainData } from "./validation"; export type MinaProviderDetail = { info: MinaProviderInfo; @@ -74,38 +69,38 @@ export type GetBalanceRequest = (args: { export type SignRequest = (args: { method: "mina_sign"; - params: SignMessageData; + params: SignMessageParams; }) => Promise; export type SignFieldsRequest = (args: { method: "mina_signFields"; - params: SignFieldsData; + params: SignFieldsParams; }) => Promise; export type SignTransactionRequest = (args: { method: "mina_signTransaction"; - params: SignTransactionData; -}) => Promise; + params: SignTransactionParams; +}) => Promise; export type SendTransactionRequest = (args: { method: "mina_sendTransaction"; - params: SendTransactionData; + params: SendTransactionParams; }) => Promise; export type CreateNullifierRequest = (args: { method: "mina_createNullifier"; - params: CreateNullifierData; + params: CreateNullifierParams; }) => Promise; export type SwitchChainRequest = (args: { method: "mina_switchChain"; params: SwitchChainData; -}) => Promise; +}) => Promise; export type AddChainRequest = (args: { method: "mina_addChain"; params: AddChainData; -}) => Promise; +}) => Promise; export type ProviderRequest = | AccountsRequest diff --git a/packages/providers/src/validation.ts b/packages/providers/src/validation.ts index 024a985..4664b47 100644 --- a/packages/providers/src/validation.ts +++ b/packages/providers/src/validation.ts @@ -1,63 +1,5 @@ import { z } from "zod"; -export const signFieldsRequestSchema = z.object({ - fields: z.array(z.coerce.number()), -}); - -export type SignFieldsData = z.infer; - -export const signMessageRequestSchema = z.object({ - message: z.string(), -}); - -export type SignMessageData = z.infer; - -export const createNullifierRequestSchema = z.object({ - message: z.array(z.coerce.number()), -}); - -export type CreateNullifierData = z.infer; - -export const publicKeySchema = z.string().length(55); - -export const transactionSchema = z - .object({ - to: publicKeySchema, - from: publicKeySchema, - fee: z.coerce.number(), - nonce: z.coerce.number(), - memo: z.string().optional(), - validUntil: z.coerce.number().optional(), - amount: z.coerce.number().optional(), - }) - .strict(); - -export const signatureSchema = z - .object({ - field: z.string(), - scalar: z.string(), - }) - .strict(); - -export const signedTransactionSchema = z.object({ - data: transactionSchema, - publicKey: publicKeySchema, - signature: signatureSchema, -}); - -export const signTransactionRequestSchema = z.object({ - transaction: transactionSchema.strict(), -}); - -export type SignTransactionData = z.infer; - -export const sendTransactionRequestSchema = z.object({ - signedTransaction: signedTransactionSchema.strict(), - transactionType: z.enum(["payment", "delegation", "zkapp"]), -}); - -export type SendTransactionData = z.infer; - export const switchChainRequestSchema = z.object({ chainId: z.string(), }); diff --git a/packages/shared/src/validation.ts b/packages/shared/src/validation.ts index 40cb096..4233045 100644 --- a/packages/shared/src/validation.ts +++ b/packages/shared/src/validation.ts @@ -16,48 +16,62 @@ const JsonSchema: z.ZodType = z.lazy(() => export const FieldSchema = z.coerce.bigint(); -export const GroupSchema = z.object({ - x: FieldSchema, - y: FieldSchema, -}); +export const GroupSchema = z + .object({ + x: FieldSchema, + y: FieldSchema, + }) + .strict(); export const PublicKeySchema = z.string().length(55).startsWith("B62"); export const PrivateKeySchema = z.string().length(52); -export const TransactionPayload = z.object({ - from: PublicKeySchema, - to: PublicKeySchema, - memo: z.string().optional(), - fee: z.coerce.bigint(), - amount: z.coerce.bigint(), - nonce: z.coerce.bigint(), - validUntil: z.coerce.bigint().optional(), -}); +export const TransactionPayload = z + .object({ + from: PublicKeySchema, + to: PublicKeySchema, + memo: z.string().optional(), + fee: z.coerce.bigint(), + amount: z.coerce.bigint(), + nonce: z.coerce.bigint(), + validUntil: z.coerce.bigint().optional(), + }) + .strict(); /** * Parameter schemas */ -export const SignFieldsParamsSchema = z.object({ - fields: z.array(z.coerce.bigint()), -}); +export const SignFieldsParamsSchema = z + .object({ + fields: z.array(FieldSchema), + }) + .strict(); -export const SignMessageParamsSchema = z.object({ - message: z.string(), -}); +export const SignMessageParamsSchema = z + .object({ + message: z.string(), + }) + .strict(); -export const CreateNullifierParamsSchema = z.object({ - message: z.array(z.coerce.bigint()), -}); +export const CreateNullifierParamsSchema = z + .object({ + message: z.array(FieldSchema), + }) + .strict(); -export const SignTransactionParamsSchema = z.object({ - transaction: TransactionPayload, -}); +export const SignTransactionParamsSchema = z + .object({ + transaction: TransactionPayload, + }) + .strict(); -export const SendTransactionParamsSchema = z.object({ - signedTransaction: TransactionPayload.strict(), - transactionType: z.enum(["payment", "delegation", "zkapp"]), -}); +export const SendTransactionParamsSchema = z + .object({ + signedTransaction: TransactionPayload.strict(), + transactionType: z.enum(["payment", "delegation", "zkapp"]), + }) + .strict(); /** * Return type schemas @@ -77,31 +91,39 @@ export const SignedMessageSchema = z }) .strict(); -export const SignedFieldsSchema = z.object({ - data: z.array(FieldSchema), - publicKey: PublicKeySchema, - signature: z.string(), -}); - -export const NullifierSchema = z.object({ - publicKey: GroupSchema, - public: z.object({ - nullifier: GroupSchema, - s: FieldSchema, - }), - private: z.object({ - c: FieldSchema, - g_r: GroupSchema, - h_m_pk_r: GroupSchema, - }), -}); - -export const SignedTransactionSchema = z.object({ - signature: SignatureSchema, - publicKey: PublicKeySchema, - data: TransactionPayload, -}); - -export const TransactionReceiptSchema = z.object({ - hash: z.string(), -}); +export const SignedFieldsSchema = z + .object({ + data: z.array(FieldSchema), + publicKey: PublicKeySchema, + signature: z.string(), + }) + .strict(); + +export const NullifierSchema = z + .object({ + publicKey: GroupSchema, + public: z.object({ + nullifier: GroupSchema, + s: FieldSchema, + }), + private: z.object({ + c: FieldSchema, + g_r: GroupSchema, + h_m_pk_r: GroupSchema, + }), + }) + .strict(); + +export const SignedTransactionSchema = z + .object({ + signature: SignatureSchema, + publicKey: PublicKeySchema, + data: TransactionPayload, + }) + .strict(); + +export const TransactionReceiptSchema = z + .object({ + hash: z.string(), + }) + .strict();