From b1793f80f8fbe523337b30023f6981a9a83e801e Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 2 Sep 2026 14:20:37 +0200 Subject: [PATCH 1/4] feat: restore wallet helpers as a first-class operation type Reintroduce the Wallet operation type dropped in the Evo SDK rewrite (4ce19f5), now backed by the package-level `wallet` namespace. Phase 1 adds ten key-generation/utility operations: generate/validate mnemonic, mnemonic to seed, generate key pair(s), key pair from WIF/hex, pubkey to address, validate address, and sign message. Wallet operations run locally in wasm and never touch the platform: executeSelected() skips ensureClient() for the wallet type, and network-dependent operations read the new getSelectedNetwork() helper (unifying the previously inconsistent radio fallbacks on mainnet). The type extractor learns namespace-function declaration modules and package-level sdk_examples; docs render wallet as `wallet.method` with a local-execution note. A new preserveWhitespace flag keeps the sign-message text and BIP39 passphrase untrimmed, since whitespace there is significant. Tests: wallet dispatch unit suite, getSelectedNetwork() coverage, namespace-function extractor fixtures, and an e2e suite with deterministic fixtures plus an offline guarantee asserting zero external requests. Co-Authored-By: Claude Fable 5 --- playwright.config.ts | 2 +- public/AI_REFERENCE.md | 212 +++++ public/TYPE_REFERENCE.md | 80 +- public/api-definitions.json | 296 +++++++ public/documentation-check-report.txt | 2 +- public/sdk-operation-catalog.json | 965 +++++++++++++++++++-- public/src/definitions-data.js | 108 +++ public/src/definitions.js | 7 + public/src/execute.js | 4 +- public/src/form/dynamic-handlers.js | 2 +- public/src/form/parse-input.js | 4 +- public/src/form/render.js | 14 - public/src/operations.js | 50 ++ public/src/sdk-client.js | 11 +- scripts/extract_sdk_types.mjs | 21 +- scripts/generate_docs.py | 89 +- tests/e2e/fixtures/test-data.js | 31 + tests/e2e/wallet/wallet-operations.spec.js | 202 +++++ tests/type-extraction.test.mjs | 62 ++ tests/unit/definitions-data.test.js | 6 +- tests/unit/parse-input.test.js | 12 + tests/unit/sdk-client-network.test.js | 45 + tests/unit/wallet-dispatch.test.js | 202 +++++ 23 files changed, 2273 insertions(+), 154 deletions(-) create mode 100644 tests/e2e/wallet/wallet-operations.spec.js create mode 100644 tests/unit/sdk-client-network.test.js create mode 100644 tests/unit/wallet-dispatch.test.js diff --git a/playwright.config.ts b/playwright.config.ts index 972c1cf..c52efee 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -65,7 +65,7 @@ export default defineConfig({ }, { name: 'parallel-e2e-tests', - testMatch: ['tests/e2e/queries/*.spec.js'], + testMatch: ['tests/e2e/queries/*.spec.js', 'tests/e2e/wallet/*.spec.js'], fullyParallel: true, // workers: process.env.CI ? 1 : undefined, use: { diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 1a753cf..b76d403 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -41,6 +41,8 @@ All queries follow this pattern: const result = await sdk..(params); ``` +Exception: the `wallet` namespace is a package-level export (`import { wallet } from '@dashevo/evo-sdk'`), called as `wallet.(…)` without an SDK instance or platform connection. + ### Available Queries #### Identity Queries @@ -1906,6 +1908,216 @@ const nullifier = new Uint8Array(32); return await sdk.shielded.nullifiers([nullifier]); ``` +#### Wallet Utilities + +*Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site’s network selector.* + +**Generate Mnemonic** - `wallet.generateMnemonic` +*Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateMnemonic(params?: wasm.GenerateMnemonicParams): Promise` + +Parameters: +- `params`: `wasm.GenerateMnemonicParams` (optional) + - Type declarations: [`wasm.GenerateMnemonicParams`](TYPE_REFERENCE.md#type-generatemnemonicparams) + - `wordCount`: `number` (optional) + - `languageCode`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const mnemonic = await wallet.generateMnemonic({ wordCount: 12 }); +``` + +**Validate Mnemonic** - `wallet.validateMnemonic` +*Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.* + +Signature: `validateMnemonic(mnemonic: string, languageCode?: string): Promise` + +Parameters: +- `mnemonic`: `string` (required) + +- `languageCode`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const isValid = await wallet.validateMnemonic('your seed phrase here …'); +``` + +**Mnemonic to Seed** - `wallet.mnemonicToSeed` +*Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.* + +Signature: `mnemonicToSeed(mnemonic: string, passphrase?: string): Promise` + +Parameters: +- `mnemonic`: `string` (required) + +- `passphrase`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const seedBytes = await wallet.mnemonicToSeed('your seed phrase here …'); +``` + +**Generate Key Pair** - `wallet.generateKeyPair` +*Generate a random private key with its public key and address. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateKeyPair(network: NetworkLike): Promise` + +Parameters: +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.generateKeyPair('testnet'); +``` + +**Generate Key Pairs** - `wallet.generateKeyPairs` +*Generate multiple random key pairs at once. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateKeyPairs(network: NetworkLike, count: number): Promise` + +Parameters: +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +- `count`: `number` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPairs = await wallet.generateKeyPairs('testnet', 3); +``` + +**Key Pair from WIF** - `wallet.keyPairFromWif` +*Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site’s network selector does not affect this operation.* + +Signature: `keyPairFromWif(privateKeyWif: string): Promise` + +Parameters: +- `privateKeyWif`: `string` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.keyPairFromWif('cYourTestnetPrivateKeyWif…'); +``` + +**Key Pair from Hex** - `wallet.keyPairFromHex` +*Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site’s network selector.* + +Signature: `keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise` + +Parameters: +- `privateKeyHex`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.keyPairFromHex('c4bb… (64 hex chars)', 'testnet'); +``` + +**Public Key to Address** - `wallet.pubkeyToAddress` +*Convert a compressed public key (hex) to a Dash address. The network comes from the site’s network selector.* + +Signature: `pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise` + +Parameters: +- `pubkeyHex`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const address = await wallet.pubkeyToAddress('0378d4…', 'testnet'); +``` + +**Validate Address** - `wallet.validateAddress` +*Check whether a Dash address is valid for a network. The network comes from the site’s network selector.* + +Signature: `validateAddress(address: string, network: NetworkLike): Promise` + +Parameters: +- `address`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const isValid = await wallet.validateAddress('yXSSUfQ8AFeWVionamYzedYSf3PANMazbw', 'testnet'); +``` + +**Sign Message** - `wallet.signMessage` +*Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered.* + +Signature: `signMessage(message: string, privateKeyWif: string): Promise` + +Parameters: +- `message`: `string` (required) + +- `privateKeyWif`: `string` (required) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const signature = await wallet.signMessage('Hello Dash', 'cYourPrivateKeyWif…'); +``` + ## State Transition Operations ### Pattern diff --git a/public/TYPE_REFERENCE.md b/public/TYPE_REFERENCE.md index c959b0b..96df949 100644 --- a/public/TYPE_REFERENCE.md +++ b/public/TYPE_REFERENCE.md @@ -1299,6 +1299,18 @@ export interface FinalizedEpochsQuery { } ``` + +## `GenerateMnemonicParams` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export interface GenerateMnemonicParams { + wordCount?: number; + languageCode?: string; +} +``` + ## `Group` @@ -2410,6 +2422,28 @@ export interface IdentityUpdateOptions { } ``` + +## `KeyPair` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export class KeyPair { + private constructor(); + free(): void; + [Symbol.dispose](): void; + static fromJSON(js: object): KeyPair; + static fromObject(obj: object): KeyPair; + toJSON(): any; + toObject(): any; + address: string; + network: string; + privateKeyHex: string; + privateKeyWif: string; + publicKey: string; +} +``` + ## `MasternodeVoteOptions` @@ -2454,6 +2488,15 @@ export interface MasternodeVoteOptions { } ``` + +## `NetworkLike` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export type NetworkLike = Network | "mainnet" | "testnet" | "devnet" | "regtest" | 0 | 1 | 2 | 3; +``` + ## `PathElement` @@ -4352,15 +4395,6 @@ export class ContestedResourceVoteWinner { } ``` - -## `NetworkLike` - -Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` - -```typescript -export type NetworkLike = Network | "mainnet" | "testnet" | "devnet" | "regtest" | 0 | 1 | 2 | 3; -``` - ## `DataContractConfig` @@ -5071,6 +5105,20 @@ export interface IdentityPublicKeyInCreationOptions { } ``` + +## `Network` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export enum Network { + Mainnet = 0, + Testnet = 1, + Devnet = 2, + Regtest = 3, +} +``` + ## `GroveElementType` @@ -5614,20 +5662,6 @@ export class ContestedDocumentVotePollWinnerInfo { } ``` - -## `Network` - -Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` - -```typescript -export enum Network { - Mainnet = 0, - Testnet = 1, - Devnet = 2, - Regtest = 3, -} -``` - ## `PlatformVersion` diff --git a/public/api-definitions.json b/public/api-definitions.json index 0b4044f..b70e4a5 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -1783,6 +1783,302 @@ "sdk_method": "shielded.nullifiers" } } + }, + "wallet": { + "label": "Wallet Utilities", + "description": "Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site’s network selector.", + "queries": { + "walletGenerateMnemonic": { + "label": "Generate Mnemonic", + "description": "Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only — never use it to store real funds.", + "inputs": [ + { + "name": "wordCount", + "type": "select", + "label": "Word Count", + "options": [ + { + "value": "12", + "label": "12 words" + }, + { + "value": "15", + "label": "15 words" + }, + { + "value": "18", + "label": "18 words" + }, + { + "value": "21", + "label": "21 words" + }, + { + "value": "24", + "label": "24 words" + } + ], + "value": "12", + "sdk_property": "params.wordCount" + }, + { + "name": "languageCode", + "type": "select", + "label": "Language", + "options": [ + { + "value": "", + "label": "Default (English)" + }, + { + "value": "en", + "label": "English (en)" + }, + { + "value": "zh-cn", + "label": "Chinese, Simplified (zh-cn)" + }, + { + "value": "zh-tw", + "label": "Chinese, Traditional (zh-tw)" + }, + { + "value": "cs", + "label": "Czech (cs)" + }, + { + "value": "fr", + "label": "French (fr)" + }, + { + "value": "it", + "label": "Italian (it)" + }, + { + "value": "ja", + "label": "Japanese (ja)" + }, + { + "value": "ko", + "label": "Korean (ko)" + }, + { + "value": "pt", + "label": "Portuguese (pt)" + }, + { + "value": "es", + "label": "Spanish (es)" + } + ], + "help": "Optional BIP39 wordlist language. Supported codes: en, zh-cn, zh-tw, cs, fr, it, ja, ko, pt, es.", + "sdk_property": "params.languageCode" + } + ], + "sdk_method": "wallet.generateMnemonic", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst mnemonic = await wallet.generateMnemonic({ wordCount: 12 });" + }, + "walletValidateMnemonic": { + "label": "Validate Mnemonic", + "description": "Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.", + "inputs": [ + { + "name": "mnemonic", + "type": "password", + "label": "Mnemonic Seed Phrase", + "required": true, + "sdk_parameter": "mnemonic" + }, + { + "name": "languageCode", + "type": "select", + "label": "Language", + "options": [ + { + "value": "", + "label": "Default (English)" + }, + { + "value": "en", + "label": "English (en)" + }, + { + "value": "zh-cn", + "label": "Chinese, Simplified (zh-cn)" + }, + { + "value": "zh-tw", + "label": "Chinese, Traditional (zh-tw)" + }, + { + "value": "cs", + "label": "Czech (cs)" + }, + { + "value": "fr", + "label": "French (fr)" + }, + { + "value": "it", + "label": "Italian (it)" + }, + { + "value": "ja", + "label": "Japanese (ja)" + }, + { + "value": "ko", + "label": "Korean (ko)" + }, + { + "value": "pt", + "label": "Portuguese (pt)" + }, + { + "value": "es", + "label": "Spanish (es)" + } + ], + "help": "Optional BIP39 wordlist language. Supported codes: en, zh-cn, zh-tw, cs, fr, it, ja, ko, pt, es.", + "sdk_parameter": "languageCode" + } + ], + "sdk_method": "wallet.validateMnemonic", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst isValid = await wallet.validateMnemonic('your seed phrase here …');" + }, + "walletMnemonicToSeed": { + "label": "Mnemonic to Seed", + "description": "Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.", + "inputs": [ + { + "name": "mnemonic", + "type": "password", + "label": "Mnemonic Seed Phrase", + "required": true, + "sdk_parameter": "mnemonic" + }, + { + "name": "passphrase", + "type": "password", + "label": "Passphrase (optional)", + "sdk_parameter": "passphrase", + "preserveWhitespace": true, + "help": "Whitespace is significant and changes the derived seed; the passphrase is used exactly as entered." + } + ], + "sdk_method": "wallet.mnemonicToSeed", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst seedBytes = await wallet.mnemonicToSeed('your seed phrase here …');" + }, + "walletGenerateKeyPair": { + "label": "Generate Key Pair", + "description": "Generate a random private key with its public key and address. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.", + "inputs": [], + "sdk_method": "wallet.generateKeyPair", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst keyPair = await wallet.generateKeyPair('testnet');" + }, + "walletGenerateKeyPairs": { + "label": "Generate Key Pairs", + "description": "Generate multiple random key pairs at once. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.", + "inputs": [ + { + "name": "count", + "type": "number", + "label": "Count", + "required": true, + "min": 1, + "max": 100, + "value": 1, + "sdk_parameter": "count" + } + ], + "sdk_method": "wallet.generateKeyPairs", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst keyPairs = await wallet.generateKeyPairs('testnet', 3);" + }, + "walletKeyPairFromWif": { + "label": "Key Pair from WIF", + "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site’s network selector does not affect this operation.", + "inputs": [ + { + "name": "privateKeyWif", + "type": "password", + "label": "Private Key (WIF)", + "required": true, + "sdk_parameter": "privateKeyWif" + } + ], + "sdk_method": "wallet.keyPairFromWif", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst keyPair = await wallet.keyPairFromWif('cYourTestnetPrivateKeyWif…');" + }, + "walletKeyPairFromHex": { + "label": "Key Pair from Hex", + "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site’s network selector.", + "inputs": [ + { + "name": "privateKeyHex", + "type": "password", + "label": "Private Key (hex)", + "required": true, + "sdk_parameter": "privateKeyHex" + } + ], + "sdk_method": "wallet.keyPairFromHex", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst keyPair = await wallet.keyPairFromHex('c4bb… (64 hex chars)', 'testnet');" + }, + "walletPubkeyToAddress": { + "label": "Public Key to Address", + "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site’s network selector.", + "inputs": [ + { + "name": "pubkeyHex", + "type": "text", + "label": "Public Key (hex)", + "required": true, + "placeholder": "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71", + "sdk_parameter": "pubkeyHex" + } + ], + "sdk_method": "wallet.pubkeyToAddress", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst address = await wallet.pubkeyToAddress('0378d4…', 'testnet');" + }, + "walletValidateAddress": { + "label": "Validate Address", + "description": "Check whether a Dash address is valid for a network. The network comes from the site’s network selector.", + "inputs": [ + { + "name": "address", + "type": "text", + "label": "Address", + "required": true, + "placeholder": "yXSSUfQ8AFeWVionamYzedYSf3PANMazbw", + "sdk_parameter": "address" + } + ], + "sdk_method": "wallet.validateAddress", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst isValid = await wallet.validateAddress('yXSSUfQ8AFeWVionamYzedYSf3PANMazbw', 'testnet');" + }, + "walletSignMessage": { + "label": "Sign Message", + "description": "Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered.", + "inputs": [ + { + "name": "message", + "type": "textarea", + "label": "Message", + "required": true, + "sdk_parameter": "message" + }, + { + "name": "privateKeyWif", + "type": "password", + "label": "Private Key (WIF)", + "required": true, + "sdk_parameter": "privateKeyWif" + } + ], + "sdk_method": "wallet.signMessage", + "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst signature = await wallet.signMessage('Hello Dash', 'cYourPrivateKeyWif…');" + } + } } }, "transitions": { diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 717fb07..6b6db51 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-08-27T14:10:15.480441 +Timestamp: 2026-09-02T14:17:54.606479 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/public/sdk-operation-catalog.json b/public/sdk-operation-catalog.json index 7e38458..ed31546 100644 --- a/public/sdk-operation-catalog.json +++ b/public/sdk-operation-catalog.json @@ -2932,6 +2932,334 @@ "wasm.ShieldedNullifierStatus" ] }, + { + "key": "walletGenerateMnemonic", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.generateMnemonic", + "label": "Generate Mnemonic", + "description": "Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only \u2014 never use it to store real funds.", + "disabled": null, + "signature": "generateMnemonic(params?: wasm.GenerateMnemonicParams): Promise", + "parameters": [ + { + "name": "params", + "type": "wasm.GenerateMnemonicParams", + "optional": true, + "rest": false, + "description": "", + "references": [ + "wasm.GenerateMnemonicParams" + ], + "properties": [ + { + "name": "wordCount", + "type": "number", + "optional": true, + "description": "", + "references": [] + }, + { + "name": "languageCode", + "type": "string", + "optional": true, + "description": "", + "references": [] + } + ] + } + ], + "returnType": "Promise", + "references": [] + }, + { + "key": "walletValidateMnemonic", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.validateMnemonic", + "label": "Validate Mnemonic", + "description": "Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.", + "disabled": null, + "signature": "validateMnemonic(mnemonic: string, languageCode?: string): Promise", + "parameters": [ + { + "name": "mnemonic", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "languageCode", + "type": "string", + "optional": true, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [] + }, + { + "key": "walletMnemonicToSeed", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.mnemonicToSeed", + "label": "Mnemonic to Seed", + "description": "Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.", + "disabled": null, + "signature": "mnemonicToSeed(mnemonic: string, passphrase?: string): Promise", + "parameters": [ + { + "name": "mnemonic", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "passphrase", + "type": "string", + "optional": true, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [] + }, + { + "key": "walletGenerateKeyPair", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.generateKeyPair", + "label": "Generate Key Pair", + "description": "Generate a random private key with its public key and address. The network comes from the site\u2019s network selector. Generated key material is for testing only \u2014 never use it to store real funds.", + "disabled": null, + "signature": "generateKeyPair(network: NetworkLike): Promise", + "parameters": [ + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "references": [ + "wasm.KeyPair" + ] + }, + { + "key": "walletGenerateKeyPairs", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.generateKeyPairs", + "label": "Generate Key Pairs", + "description": "Generate multiple random key pairs at once. The network comes from the site\u2019s network selector. Generated key material is for testing only \u2014 never use it to store real funds.", + "disabled": null, + "signature": "generateKeyPairs(network: NetworkLike, count: number): Promise", + "parameters": [ + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + }, + { + "name": "count", + "type": "number", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [ + "wasm.KeyPair" + ] + }, + { + "key": "walletKeyPairFromWif", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.keyPairFromWif", + "label": "Key Pair from WIF", + "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site\u2019s network selector does not affect this operation.", + "disabled": null, + "signature": "keyPairFromWif(privateKeyWif: string): Promise", + "parameters": [ + { + "name": "privateKeyWif", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [ + "wasm.KeyPair" + ] + }, + { + "key": "walletKeyPairFromHex", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.keyPairFromHex", + "label": "Key Pair from Hex", + "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site\u2019s network selector.", + "disabled": null, + "signature": "keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise", + "parameters": [ + { + "name": "privateKeyHex", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "references": [ + "wasm.KeyPair" + ] + }, + { + "key": "walletPubkeyToAddress", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.pubkeyToAddress", + "label": "Public Key to Address", + "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site\u2019s network selector.", + "disabled": null, + "signature": "pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise", + "parameters": [ + { + "name": "pubkeyHex", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "references": [] + }, + { + "key": "walletValidateAddress", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.validateAddress", + "label": "Validate Address", + "description": "Check whether a Dash address is valid for a network. The network comes from the site\u2019s network selector.", + "disabled": null, + "signature": "validateAddress(address: string, network: NetworkLike): Promise", + "parameters": [ + { + "name": "address", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "references": [] + }, + { + "key": "walletSignMessage", + "group": "queries", + "category": "wallet", + "sdkMethod": "wallet.signMessage", + "label": "Sign Message", + "description": "Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered.", + "disabled": null, + "signature": "signMessage(message: string, privateKeyWif: string): Promise", + "parameters": [ + { + "name": "message", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "privateKeyWif", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [] + }, { "key": "identityCreate", "group": "transitions", @@ -8745,87 +9073,414 @@ "type": "wasm.PlatformAddressLike", "optional": false, "rest": false, - "description": "- The platform address to query (PlatformAddress, Uint8Array, or bech32m string)", + "description": "- The platform address to query (PlatformAddress, Uint8Array, or bech32m string)", + "references": [ + "wasm.PlatformAddressLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.PlatformAddressInfo" + ], + "references": [ + "wasm.PlatformAddressInfo" + ], + "variants": [ + { + "key": "getPlatformAddress", + "group": "queries", + "category": "address" + } + ], + "source": "dist/addresses/facade.d.ts" + }, + "addresses.getMany": { + "sdkMethod": "addresses.getMany", + "signature": "getMany(addresses: wasm.PlatformAddressLikeArray): Promise>", + "description": "Fetches information about multiple Platform addresses.", + "parameters": [ + { + "name": "addresses", + "type": "wasm.PlatformAddressLikeArray", + "optional": false, + "rest": false, + "description": "- Array of platform addresses to query", + "references": [ + "wasm.PlatformAddressLikeArray" + ], + "properties": [] + } + ], + "returnType": "Promise>", + "returnReferences": [ + "wasm.PlatformAddressInfo" + ], + "references": [ + "wasm.PlatformAddressInfo" + ], + "variants": [ + { + "key": "getPlatformAddresses", + "group": "queries", + "category": "address" + } + ], + "source": "dist/addresses/facade.d.ts" + }, + "shielded.poolState": { + "sdkMethod": "shielded.poolState", + "signature": "poolState(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedPoolState", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.encryptedNotes": { + "sdkMethod": "shielded.encryptedNotes", + "signature": "encryptedNotes(startIndex: bigint, count: number): Promise", + "description": "", + "parameters": [ + { + "name": "startIndex", + "type": "bigint", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "count", + "type": "number", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.ShieldedEncryptedNote" + ], + "references": [ + "wasm.ShieldedEncryptedNote" + ], + "variants": [ + { + "key": "getShieldedEncryptedNotes", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.anchors": { + "sdkMethod": "shielded.anchors", + "signature": "anchors(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedAnchors", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.mostRecentAnchor": { + "sdkMethod": "shielded.mostRecentAnchor", + "signature": "mostRecentAnchor(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedMostRecentAnchor", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.nullifiers": { + "sdkMethod": "shielded.nullifiers", + "signature": "nullifiers(nullifiers: Uint8Array[]): Promise", + "description": "Each nullifier must be a `Uint8Array` of exactly 32 bytes; otherwise\nthe underlying call rejects with `InvalidArgument`.", + "parameters": [ + { + "name": "nullifiers", + "type": "Uint8Array[]", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.ShieldedNullifierStatus" + ], + "references": [ + "wasm.ShieldedNullifierStatus" + ], + "variants": [ + { + "key": "getShieldedNullifiers", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "wallet.generateMnemonic": { + "sdkMethod": "wallet.generateMnemonic", + "signature": "generateMnemonic(params?: wasm.GenerateMnemonicParams): Promise", + "description": "", + "parameters": [ + { + "name": "params", + "type": "wasm.GenerateMnemonicParams", + "optional": true, + "rest": false, + "description": "", + "references": [ + "wasm.GenerateMnemonicParams" + ], + "properties": [ + { + "name": "wordCount", + "type": "number", + "optional": true, + "description": "", + "references": [] + }, + { + "name": "languageCode", + "type": "string", + "optional": true, + "description": "", + "references": [] + } + ] + } + ], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "walletGenerateMnemonic", + "group": "queries", + "category": "wallet" + } + ], + "source": "dist/wallet/functions.d.ts" + }, + "wallet.validateMnemonic": { + "sdkMethod": "wallet.validateMnemonic", + "signature": "validateMnemonic(mnemonic: string, languageCode?: string): Promise", + "description": "", + "parameters": [ + { + "name": "mnemonic", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "languageCode", + "type": "string", + "optional": true, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "walletValidateMnemonic", + "group": "queries", + "category": "wallet" + } + ], + "source": "dist/wallet/functions.d.ts" + }, + "wallet.mnemonicToSeed": { + "sdkMethod": "wallet.mnemonicToSeed", + "signature": "mnemonicToSeed(mnemonic: string, passphrase?: string): Promise", + "description": "", + "parameters": [ + { + "name": "mnemonic", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "passphrase", + "type": "string", + "optional": true, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "walletMnemonicToSeed", + "group": "queries", + "category": "wallet" + } + ], + "source": "dist/wallet/functions.d.ts" + }, + "wallet.generateKeyPair": { + "sdkMethod": "wallet.generateKeyPair", + "signature": "generateKeyPair(network: NetworkLike): Promise", + "description": "", + "parameters": [ + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", "references": [ - "wasm.PlatformAddressLike" + "NetworkLike" ], "properties": [] } ], - "returnType": "Promise", + "returnType": "Promise", "returnReferences": [ - "wasm.PlatformAddressInfo" + "wasm.KeyPair" ], "references": [ - "wasm.PlatformAddressInfo" + "wasm.KeyPair" ], "variants": [ { - "key": "getPlatformAddress", + "key": "walletGenerateKeyPair", "group": "queries", - "category": "address" + "category": "wallet" } ], - "source": "dist/addresses/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "addresses.getMany": { - "sdkMethod": "addresses.getMany", - "signature": "getMany(addresses: wasm.PlatformAddressLikeArray): Promise>", - "description": "Fetches information about multiple Platform addresses.", + "wallet.generateKeyPairs": { + "sdkMethod": "wallet.generateKeyPairs", + "signature": "generateKeyPairs(network: NetworkLike, count: number): Promise", + "description": "", "parameters": [ { - "name": "addresses", - "type": "wasm.PlatformAddressLikeArray", + "name": "network", + "type": "NetworkLike", "optional": false, "rest": false, - "description": "- Array of platform addresses to query", + "description": "", "references": [ - "wasm.PlatformAddressLikeArray" + "NetworkLike" ], "properties": [] + }, + { + "name": "count", + "type": "number", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] } ], - "returnType": "Promise>", + "returnType": "Promise", "returnReferences": [ - "wasm.PlatformAddressInfo" + "wasm.KeyPair" ], "references": [ - "wasm.PlatformAddressInfo" + "wasm.KeyPair" ], "variants": [ { - "key": "getPlatformAddresses", + "key": "walletGenerateKeyPairs", "group": "queries", - "category": "address" + "category": "wallet" } ], - "source": "dist/addresses/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "shielded.poolState": { - "sdkMethod": "shielded.poolState", - "signature": "poolState(): Promise", + "wallet.keyPairFromWif": { + "sdkMethod": "wallet.keyPairFromWif", + "signature": "keyPairFromWif(privateKeyWif: string): Promise", "description": "", - "parameters": [], - "returnType": "Promise", - "returnReferences": [], - "references": [], + "parameters": [ + { + "name": "privateKeyWif", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.KeyPair" + ], + "references": [ + "wasm.KeyPair" + ], "variants": [ { - "key": "getShieldedPoolState", + "key": "walletKeyPairFromWif", "group": "queries", - "category": "shielded" + "category": "wallet" } ], - "source": "dist/shielded/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "shielded.encryptedNotes": { - "sdkMethod": "shielded.encryptedNotes", - "signature": "encryptedNotes(startIndex: bigint, count: number): Promise", + "wallet.keyPairFromHex": { + "sdkMethod": "wallet.keyPairFromHex", + "signature": "keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise", "description": "", "parameters": [ { - "name": "startIndex", - "type": "bigint", + "name": "privateKeyHex", + "type": "string", "optional": false, "rest": false, "description": "", @@ -8833,73 +9488,126 @@ "properties": [] }, { - "name": "count", - "type": "number", + "name": "network", + "type": "NetworkLike", "optional": false, "rest": false, "description": "", - "references": [], + "references": [ + "NetworkLike" + ], "properties": [] } ], - "returnType": "Promise", + "returnType": "Promise", "returnReferences": [ - "wasm.ShieldedEncryptedNote" + "wasm.KeyPair" ], "references": [ - "wasm.ShieldedEncryptedNote" + "wasm.KeyPair" ], "variants": [ { - "key": "getShieldedEncryptedNotes", + "key": "walletKeyPairFromHex", "group": "queries", - "category": "shielded" + "category": "wallet" } ], - "source": "dist/shielded/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "shielded.anchors": { - "sdkMethod": "shielded.anchors", - "signature": "anchors(): Promise", + "wallet.pubkeyToAddress": { + "sdkMethod": "wallet.pubkeyToAddress", + "signature": "pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise", "description": "", - "parameters": [], - "returnType": "Promise", + "parameters": [ + { + "name": "pubkeyHex", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", "returnReferences": [], "references": [], "variants": [ { - "key": "getShieldedAnchors", + "key": "walletPubkeyToAddress", "group": "queries", - "category": "shielded" + "category": "wallet" } ], - "source": "dist/shielded/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "shielded.mostRecentAnchor": { - "sdkMethod": "shielded.mostRecentAnchor", - "signature": "mostRecentAnchor(): Promise", + "wallet.validateAddress": { + "sdkMethod": "wallet.validateAddress", + "signature": "validateAddress(address: string, network: NetworkLike): Promise", "description": "", - "parameters": [], - "returnType": "Promise", + "parameters": [ + { + "name": "address", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "network", + "type": "NetworkLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "NetworkLike" + ], + "properties": [] + } + ], + "returnType": "Promise", "returnReferences": [], "references": [], "variants": [ { - "key": "getShieldedMostRecentAnchor", + "key": "walletValidateAddress", "group": "queries", - "category": "shielded" + "category": "wallet" } ], - "source": "dist/shielded/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, - "shielded.nullifiers": { - "sdkMethod": "shielded.nullifiers", - "signature": "nullifiers(nullifiers: Uint8Array[]): Promise", - "description": "Each nullifier must be a `Uint8Array` of exactly 32 bytes; otherwise\nthe underlying call rejects with `InvalidArgument`.", + "wallet.signMessage": { + "sdkMethod": "wallet.signMessage", + "signature": "signMessage(message: string, privateKeyWif: string): Promise", + "description": "", "parameters": [ { - "name": "nullifiers", - "type": "Uint8Array[]", + "name": "message", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "privateKeyWif", + "type": "string", "optional": false, "rest": false, "description": "", @@ -8907,21 +9615,17 @@ "properties": [] } ], - "returnType": "Promise", - "returnReferences": [ - "wasm.ShieldedNullifierStatus" - ], - "references": [ - "wasm.ShieldedNullifierStatus" - ], + "returnType": "Promise", + "returnReferences": [], + "references": [], "variants": [ { - "key": "getShieldedNullifiers", + "key": "walletSignMessage", "group": "queries", - "category": "shielded" + "category": "wallet" } ], - "source": "dist/shielded/facade.d.ts" + "source": "dist/wallet/functions.d.ts" }, "identities.create": { "sdkMethod": "identities.create", @@ -13563,6 +14267,29 @@ "references": [], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "GenerateMnemonicParams": { + "kind": "InterfaceDeclaration", + "anchor": "type-generatemnemonicparams", + "declaration": "export interface GenerateMnemonicParams {\n wordCount?: number;\n languageCode?: string;\n}", + "properties": [ + { + "name": "wordCount", + "type": "number", + "optional": true, + "description": "", + "references": [] + }, + { + "name": "languageCode", + "type": "string", + "optional": true, + "description": "", + "references": [] + } + ], + "references": [], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "Group": { "kind": "ClassDeclaration", "anchor": "type-group", @@ -15096,6 +15823,52 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "KeyPair": { + "kind": "ClassDeclaration", + "anchor": "type-keypair", + "declaration": "export class KeyPair {\n private constructor();\n free(): void;\n [Symbol.dispose](): void;\n static fromJSON(js: object): KeyPair;\n static fromObject(obj: object): KeyPair;\n toJSON(): any;\n toObject(): any;\n address: string;\n network: string;\n privateKeyHex: string;\n privateKeyWif: string;\n publicKey: string;\n}", + "properties": [ + { + "name": "address", + "type": "string", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "network", + "type": "string", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "privateKeyHex", + "type": "string", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "privateKeyWif", + "type": "string", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "publicKey", + "type": "string", + "optional": false, + "description": "", + "references": [] + } + ], + "references": [ + "KeyPair" + ], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "MasternodeVoteOptions": { "kind": "InterfaceDeclaration", "anchor": "type-masternodevoteoptions", @@ -15166,6 +15939,16 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "NetworkLike": { + "kind": "TypeAliasDeclaration", + "anchor": "type-networklike", + "declaration": "export type NetworkLike = Network | \"mainnet\" | \"testnet\" | \"devnet\" | \"regtest\" | 0 | 1 | 2 | 3;", + "properties": [], + "references": [ + "Network" + ], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "PathElement": { "kind": "ClassDeclaration", "anchor": "type-pathelement", @@ -17578,16 +18361,6 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, - "NetworkLike": { - "kind": "TypeAliasDeclaration", - "anchor": "type-networklike", - "declaration": "export type NetworkLike = Network | \"mainnet\" | \"testnet\" | \"devnet\" | \"regtest\" | 0 | 1 | 2 | 3;", - "properties": [], - "references": [ - "Network" - ], - "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" - }, "DataContractConfig": { "kind": "InterfaceDeclaration", "anchor": "type-datacontractconfig", @@ -19709,6 +20482,14 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "Network": { + "kind": "EnumDeclaration", + "anchor": "type-network", + "declaration": "export enum Network {\n Mainnet = 0,\n Testnet = 1,\n Devnet = 2,\n Regtest = 3,\n}", + "properties": [], + "references": [], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "GroveElementType": { "kind": "TypeAliasDeclaration", "anchor": "type-groveelementtype", @@ -20623,14 +21404,6 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, - "Network": { - "kind": "EnumDeclaration", - "anchor": "type-network", - "declaration": "export enum Network {\n Mainnet = 0,\n Testnet = 1,\n Devnet = 2,\n Regtest = 3,\n}", - "properties": [], - "references": [], - "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" - }, "PlatformVersion": { "kind": "ClassDeclaration", "anchor": "type-platformversion", diff --git a/public/src/definitions-data.js b/public/src/definitions-data.js index 9f53035..3005e15 100644 --- a/public/src/definitions-data.js +++ b/public/src/definitions-data.js @@ -130,6 +130,113 @@ export const DPNS_CATEGORY_DEFINITIONS = { */ }; +const WALLET_LANGUAGE_OPTIONS = [ + { value: '', label: 'Default (English)' }, + { value: 'en', label: 'English (en)' }, + { value: 'zh-cn', label: 'Chinese, Simplified (zh-cn)' }, + { value: 'zh-tw', label: 'Chinese, Traditional (zh-tw)' }, + { value: 'cs', label: 'Czech (cs)' }, + { value: 'fr', label: 'French (fr)' }, + { value: 'it', label: 'Italian (it)' }, + { value: 'ja', label: 'Japanese (ja)' }, + { value: 'ko', label: 'Korean (ko)' }, + { value: 'pt', label: 'Portuguese (pt)' }, + { value: 'es', label: 'Spanish (es)' }, +]; + +const WALLET_SAFETY_HELP = 'Generated key material is for testing only — never use it to store real funds.'; + +// Wallet helpers run locally in the wasm runtime and never contact the +// platform; network-dependent operations read the site's network selector. +// Keep these in sync with the queries.wallet entries in api-definitions.json +// (the docs/catalog source), mirroring the DPNS two-source arrangement. +export const WALLET_CATEGORY_DEFINITIONS = { + keyGeneration: { + label: 'Key Generation & Utilities', + operations: { + walletGenerateMnemonic: { + label: 'Generate Mnemonic', + description: `Generate a random BIP39 mnemonic seed phrase. ${WALLET_SAFETY_HELP}`, + inputs: [ + { name: 'wordCount', label: 'Word Count', type: 'select', value: '12', options: [ + { value: '12', label: '12 words' }, + { value: '15', label: '15 words' }, + { value: '18', label: '18 words' }, + { value: '21', label: '21 words' }, + { value: '24', label: '24 words' }, + ] }, + { name: 'languageCode', label: 'Language', type: 'select', options: WALLET_LANGUAGE_OPTIONS }, + ], + }, + walletValidateMnemonic: { + label: 'Validate Mnemonic', + description: 'Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.', + inputs: [ + { name: 'mnemonic', label: 'Mnemonic Seed Phrase', type: 'password', required: true }, + { name: 'languageCode', label: 'Language', type: 'select', options: WALLET_LANGUAGE_OPTIONS }, + ], + }, + walletMnemonicToSeed: { + label: 'Mnemonic to Seed', + description: 'Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.', + inputs: [ + { name: 'mnemonic', label: 'Mnemonic Seed Phrase', type: 'password', required: true }, + // BIP39 passphrase whitespace is significant — it changes the seed. + { name: 'passphrase', label: 'Passphrase (optional)', type: 'password', preserveWhitespace: true, help: 'Whitespace is significant and changes the derived seed; the passphrase is used exactly as entered.' }, + ], + }, + walletGenerateKeyPair: { + label: 'Generate Key Pair', + description: `Generate a random private key with its public key and address for the selected network. ${WALLET_SAFETY_HELP}`, + inputs: [], + }, + walletGenerateKeyPairs: { + label: 'Generate Key Pairs', + description: `Generate multiple random key pairs at once for the selected network. ${WALLET_SAFETY_HELP}`, + inputs: [ + { name: 'count', label: 'Count', type: 'number', required: true, min: 1, max: 100, value: 1 }, + ], + }, + walletKeyPairFromWif: { + label: 'Key Pair from WIF', + description: 'Import a private key from WIF and show its public key and address.', + inputs: [ + { name: 'privateKeyWif', label: 'Private Key (WIF)', type: 'password', required: true, help: 'The WIF version prefix determines the network; the network selector does not affect this operation.' }, + ], + }, + walletKeyPairFromHex: { + label: 'Key Pair from Hex', + description: 'Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address for the selected network.', + inputs: [ + { name: 'privateKeyHex', label: 'Private Key (hex)', type: 'password', required: true }, + ], + }, + walletPubkeyToAddress: { + label: 'Public Key to Address', + description: 'Convert a compressed public key (hex) to a Dash address for the selected network.', + inputs: [ + { name: 'pubkeyHex', label: 'Public Key (hex)', type: 'text', required: true, placeholder: '0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' }, + ], + }, + walletValidateAddress: { + label: 'Validate Address', + description: 'Check whether a Dash address is valid for the selected network.', + inputs: [ + { name: 'address', label: 'Address', type: 'text', required: true, placeholder: 'yXSSUfQ8AFeWVionamYzedYSf3PANMazbw' }, + ], + }, + walletSignMessage: { + label: 'Sign Message', + description: 'Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered, including whitespace.', + inputs: [ + { name: 'message', label: 'Message', type: 'textarea', required: true, preserveWhitespace: true }, + { name: 'privateKeyWif', label: 'Private Key (WIF)', type: 'password', required: true }, + ], + }, + }, + }, +}; + export const PROOF_CAPABLE = new Set([ // Identity 'getIdentity', 'getIdentityKeys', 'getIdentitiesContractKeys', 'getIdentityNonce', 'getIdentityContractNonce', @@ -305,6 +412,7 @@ export const TYPE_CONFIG = { queries: { definitionKey: 'queries', itemsKey: 'queries', allowProof: true }, transitions: { definitionKey: 'transitions', itemsKey: 'transitions', allowProof: false }, dpns: { definitionKey: 'dpns', itemsKey: 'operations', allowProof: true }, + wallet: { definitionKey: 'wallet', itemsKey: 'operations', allowProof: false }, }; export function filterDefinitions(source, entriesKey, allowSet) { diff --git a/public/src/definitions.js b/public/src/definitions.js index 3967d94..b3c74f5 100644 --- a/public/src/definitions.js +++ b/public/src/definitions.js @@ -2,6 +2,7 @@ import { DPNS_CATEGORY_DEFINITIONS, SUPPORTED_QUERIES, SUPPORTED_TRANSITIONS, + WALLET_CATEGORY_DEFINITIONS, filterDefinitions, } from './definitions-data.js'; import { EvoSDK } from './sdk-client.js'; @@ -16,6 +17,7 @@ export { SUPPORTED_QUERIES, SUPPORTED_TRANSITIONS, DPNS_CATEGORY_DEFINITIONS, + WALLET_CATEGORY_DEFINITIONS, PROOF_CAPABLE, TRANSITION_AUTH_REQUIREMENTS, DPNS_AUTH_REQUIREMENTS, @@ -37,15 +39,20 @@ export async function loadDefinitions() { queries: data.queries || {}, transitions: data.transitions || {}, dpns: DPNS_CATEGORY_DEFINITIONS, + wallet: WALLET_CATEGORY_DEFINITIONS, }; state.definitions = { queries: filterDefinitions(state.rawDefinitions.queries, 'queries', SUPPORTED_QUERIES), transitions: filterDefinitions(state.rawDefinitions.transitions, 'transitions', SUPPORTED_TRANSITIONS), dpns: JSON.parse(JSON.stringify(DPNS_CATEGORY_DEFINITIONS)), + wallet: JSON.parse(JSON.stringify(WALLET_CATEGORY_DEFINITIONS)), }; if (state.definitions.queries?.dpns) { delete state.definitions.queries.dpns; } + if (state.definitions.queries?.wallet) { + delete state.definitions.queries.wallet; + } if (elements.latestVersionInfo) { let latestVersion = await EvoSDK.getLatestVersionNumber(); elements.latestVersionInfo.textContent = `Latest version: ${latestVersion}`; diff --git a/public/src/execute.js b/public/src/execute.js index 2cca93a..e305291 100644 --- a/public/src/execute.js +++ b/public/src/execute.js @@ -16,7 +16,9 @@ export async function executeSelected() { const { definition, auth } = state.selected; const args = collectArgs(definition); const authArgs = collectAuthArgs(auth); - const client = await ensureClient(); + // Wallet helpers run locally in the wasm runtime — never create or + // connect a platform client for them. + const client = state.selected.type === 'wallet' ? null : await ensureClient(); const typeConfig = getTypeConfig(state.selected.type); const useProof = Boolean(typeConfig?.allowProof && elements.proofToggleContainer.style.display !== 'none' diff --git a/public/src/form/dynamic-handlers.js b/public/src/form/dynamic-handlers.js index e6482b8..1d250a0 100644 --- a/public/src/form/dynamic-handlers.js +++ b/public/src/form/dynamic-handlers.js @@ -628,7 +628,7 @@ export async function generateTestSeed() { } try { await ensureClient(); - const mnemonic = await wallet.generateMnemonic(12); + const mnemonic = await wallet.generateMnemonic({ wordCount: 12 }); seedInput.value = mnemonic; seedInput.dispatchEvent(new Event('input', { bubbles: true })); const parent = seedInput.parentElement; diff --git a/public/src/form/parse-input.js b/public/src/form/parse-input.js index 8c86f59..5c58a96 100644 --- a/public/src/form/parse-input.js +++ b/public/src/form/parse-input.js @@ -57,7 +57,9 @@ export function parseInputValue(type, def, control) { case 'password': case 'text': default: { - const raw = control.value.trim(); + // preserveWhitespace inputs (e.g. a message to sign) pass through + // exactly as entered; only a fully empty value counts as missing. + const raw = def.preserveWhitespace ? control.value : control.value.trim(); if (!raw && def.required) { throw new Error(`${def.label || def.name} is required`); } diff --git a/public/src/form/render.js b/public/src/form/render.js index 9e1ebeb..bafec9b 100644 --- a/public/src/form/render.js +++ b/public/src/form/render.js @@ -9,20 +9,6 @@ import { getTransitionOperation, renderTransitionCode } from '../transitions/reg export function populateCategories() { const type = elements.operationType.value; - if (type === 'wallet') { - elements.queryCategory.innerHTML = ''; - elements.queryType.innerHTML = ''; - elements.queryType.style.display = 'none'; - if (elements.queryTypeLabel) { - elements.queryTypeLabel.style.display = 'none'; - } - hideOperationDetails(); - setNoProofInfoVisibility(false); - setStatus('Wallet helpers are not available with the Evo SDK demo.', 'loading'); - updateAuthInputsVisibility(null); - return; - } - const config = getTypeConfig(type) || TYPE_CONFIG.queries; if (!config.allowProof) { setNoProofInfoVisibility(false); diff --git a/public/src/operations.js b/public/src/operations.js index 66c1535..93fc1e9 100644 --- a/public/src/operations.js +++ b/public/src/operations.js @@ -1,4 +1,5 @@ import { namedArgs } from './form/collect.js'; +import { getSelectedNetwork, wallet } from './sdk-client.js'; import { executeTransitionOperation } from './transitions/registry.js'; export async function callEvo(client, groupKey, itemKey, defs, args, useProof, extraArgs = {}) { @@ -53,6 +54,12 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e const bytesToHex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); + const requireTrimmed = (value, label) => { + const trimmed = String(value ?? '').trim(); + if (!trimmed) throw new Error(`${label} is required`); + return trimmed; + }; + // Anchors arrive as raw 32-byte values (a single one for mostRecentAnchor, // an array for anchors); hex-encode them for display while keeping proof // metadata wrappers intact. @@ -227,6 +234,49 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e return c.dpns.isValidUsername(n.label); case 'dpnsIsContestedUsername': return c.dpns.isContestedUsername(n.label); + + // Wallet helpers: local wasm operations that must never touch the + // platform client (executeSelected passes client = null for them). + case 'walletGenerateMnemonic': { + const wordCount = toNumber(n.wordCount, 12); + if (![12, 15, 18, 21, 24].includes(wordCount)) { + throw new Error('Word count must be 12, 15, 18, 21, or 24'); + } + const params = { wordCount }; + if (n.languageCode) params.languageCode = n.languageCode; + return wallet.generateMnemonic(params); + } + case 'walletValidateMnemonic': + return wallet.validateMnemonic(requireTrimmed(n.mnemonic, 'Mnemonic'), n.languageCode || undefined); + case 'walletMnemonicToSeed': { + const seed = await wallet.mnemonicToSeed(requireTrimmed(n.mnemonic, 'Mnemonic'), n.passphrase || undefined); + return bytesToHex(seed); + } + case 'walletGenerateKeyPair': + return wallet.generateKeyPair(getSelectedNetwork()); + case 'walletGenerateKeyPairs': { + const count = toNumber(n.count); + if (!Number.isInteger(count) || count < 1 || count > 100) { + throw new Error('Count must be an integer between 1 and 100'); + } + return wallet.generateKeyPairs(getSelectedNetwork(), count); + } + case 'walletKeyPairFromWif': + return wallet.keyPairFromWif(requireTrimmed(n.privateKeyWif, 'Private key WIF')); + case 'walletKeyPairFromHex': + return wallet.keyPairFromHex(requireTrimmed(n.privateKeyHex, 'Private key hex'), getSelectedNetwork()); + case 'walletPubkeyToAddress': + return wallet.pubkeyToAddress(requireTrimmed(n.pubkeyHex, 'Public key hex'), getSelectedNetwork()); + case 'walletValidateAddress': + return wallet.validateAddress(requireTrimmed(n.address, 'Address'), getSelectedNetwork()); + case 'walletSignMessage': { + // Whitespace is legitimate signed data: require a non-empty message but + // pass it through exactly as entered, without trimming. + if (typeof n.message !== 'string' || n.message.length === 0) { + throw new Error('Message is required'); + } + return wallet.signMessage(n.message, requireTrimmed(n.privateKeyWif, 'Private key WIF')); + } case 'dpnsRegisterName': { if (!n.label) { throw new Error('Label is required for DPNS registration.'); diff --git a/public/src/sdk-client.js b/public/src/sdk-client.js index c9fe7e4..fc25368 100644 --- a/public/src/sdk-client.js +++ b/public/src/sdk-client.js @@ -4,8 +4,14 @@ import { elements, state } from './state.js'; import { setStatus } from './ui.js'; import { buildVersionDisplayModel } from './version-display.js'; +// Mainnet is the checked default at init (main.js), so the fallback only +// applies before initialization or if no radio exists. +export function getSelectedNetwork() { + return elements.networkRadios.find(r => r.checked)?.value || 'mainnet'; +} + export function updateNetworkIndicator() { - const selected = elements.networkRadios.find(r => r.checked)?.value || 'testnet'; + const selected = getSelectedNetwork(); if (!elements.networkIndicator) return; elements.networkIndicator.textContent = selected.toUpperCase(); elements.networkIndicator.classList.toggle('mainnet', selected === 'mainnet'); @@ -13,9 +19,8 @@ export function updateNetworkIndicator() { } export function buildClientOptions() { - const selectedNetwork = elements.networkRadios.find(r => r.checked)?.value || 'mainnet'; const trusted = !!(elements.trustedMode && elements.trustedMode.checked); - return assembleClientOptions(selectedNetwork, trusted, state.advancedOptions); + return assembleClientOptions(getSelectedNetwork(), trusted, state.advancedOptions); } export async function ensureClient(force = false) { diff --git a/scripts/extract_sdk_types.mjs b/scripts/extract_sdk_types.mjs index 9f90882..0327c5e 100644 --- a/scripts/extract_sdk_types.mjs +++ b/scripts/extract_sdk_types.mjs @@ -12,6 +12,10 @@ const BUILT_INS = new Set([ 'Pick', 'Omit', 'Exclude', 'Extract', 'NonNullable', 'Uint8Array', 'Date', ]); const NAMESPACE_DIRS = { stateTransitions: 'state-transitions' }; +// Package-level namespaces whose declarations are namespace functions in the +// named file rather than class methods in facade.d.ts. Their sdk_examples call +// the namespace directly (`wallet.method()`) instead of through an instance. +const NAMESPACE_FILES = { wallet: 'functions.d.ts' }; // Upstream 4.0.0 declarations refer to the public pooling enum by its Rust // name in one high-level options interface, while exporting it as PoolingWasm. const DECLARATION_ALIASES = { Pooling: 'PoolingWasm' }; @@ -45,7 +49,7 @@ function parseFile(file) { function methodDeclarations(source, methodName) { const matches = []; function visit(node) { - if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) && node.name?.getText(source) === methodName) matches.push(node); + if ((ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isFunctionDeclaration(node)) && node.name?.getText(source) === methodName) matches.push(node); ts.forEachChild(node, visit); } visit(source); @@ -182,11 +186,18 @@ function validateSdkExample(entry, method) { const source = ts.createSourceFile('example.ts', entry.sdkExample, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); const [expectedNamespace, expectedMethod] = entry.sdkMethod.split('.'); let matchingCalls = 0; + // Instance-method examples must use the three-part `sdk.namespace.method()` + // shape; only package-level namespaces (NAMESPACE_FILES) may call the + // namespace identifier directly (`wallet.method()`), so a stray two-part + // call on any other namespace still fails validation. + const packageLevel = Object.prototype.hasOwnProperty.call(NAMESPACE_FILES, expectedNamespace); function visit(node) { if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === expectedMethod - && ts.isPropertyAccessExpression(node.expression.expression) - && node.expression.expression.name.text === expectedNamespace) { + && ((ts.isPropertyAccessExpression(node.expression.expression) + && node.expression.expression.name.text === expectedNamespace) + || (packageLevel && ts.isIdentifier(node.expression.expression) + && node.expression.expression.text === expectedNamespace))) { matchingCalls += 1; const argument = node.arguments[0]; const options = method.parameters[0]; @@ -219,8 +230,8 @@ export function extractTypes({ apiFile, packageRoot }) { for (const entry of documentedMethods(api)) { const [namespace, methodName, ...extra] = entry.sdkMethod.split('.'); if (!namespace || !methodName || extra.length) fail(`Invalid sdk_method: ${entry.sdkMethod}`); - const facadeFile = path.join(dist, NAMESPACE_DIRS[namespace] || namespace, 'facade.d.ts'); - if (!fs.existsSync(facadeFile)) fail(`No facade declaration for ${entry.sdkMethod}: ${facadeFile}`); + const facadeFile = path.join(dist, NAMESPACE_DIRS[namespace] || namespace, NAMESPACE_FILES[namespace] || 'facade.d.ts'); + if (!fs.existsSync(facadeFile)) fail(`No declaration file for ${entry.sdkMethod}: ${facadeFile}`); const source = parseFile(facadeFile); const declarations = methodDeclarations(source, methodName); if (declarations.length !== 1) fail(`${entry.sdkMethod} resolved to ${declarations.length} declarations in ${facadeFile}`); diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 37184ab..dffd438 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -76,6 +76,22 @@ def rewrite_wasm_wrapper(wasm_file: Path) -> None: 'platform_address': 'tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5fmncsgf7evf', } +# Namespaces exported at package level (not on the EvoSDK instance); their +# examples and reference strings use `wallet.method()` instead of +# `sdk.wallet.method()`. Keep in sync with NAMESPACE_FILES in +# scripts/extract_sdk_types.mjs. +PACKAGE_LEVEL_NAMESPACES = {'wallet'} + +# Throwaway wallet fixtures (BIP39 reference vector + a derived test key). +# Deliberately public — never use for real funds. +WALLET_TEST_DATA = { + 'mnemonic': 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + 'private_key_hex': 'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a', + 'private_key_wif_testnet': 'cUB8G5cFtxc4usfgfovqRgCo8qTQUJtctLV8t6YYNfULg3GtehdX', + 'public_key_hex': '0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71', + 'address_testnet': 'yXSSUfQ8AFeWVionamYzedYSf3PANMazbw', +} + def example(text: str) -> str: return textwrap.dedent(text).strip() @@ -583,6 +599,44 @@ def evo_example_for_query(key: str, inputs: List[dict]): const nullifier = new Uint8Array(32); return await sdk.shielded.nullifiers([nullifier]) """), + # Wallet helpers: package-level `wallet` namespace, runs locally in + # wasm without a platform connection. Key material shown here is a + # throwaway test fixture — never use it for real funds. + # These are the RUNNABLE docs.html variants (no import line — the Run + # button executes them as a function body with `wallet` in scope); the + # standalone-module form lives in each op's sdk_example in + # api-definitions.json and feeds AI_REFERENCE.md. Keep both in sync. + 'walletGenerateMnemonic': example(""" + return await wallet.generateMnemonic({ wordCount: 12 }) + """), + 'walletValidateMnemonic': example(f""" + return await wallet.validateMnemonic('{WALLET_TEST_DATA['mnemonic']}') + """), + 'walletMnemonicToSeed': example(f""" + // Returns the 64-byte BIP39 seed as a Uint8Array. + return await wallet.mnemonicToSeed('{WALLET_TEST_DATA['mnemonic']}') + """), + 'walletGenerateKeyPair': example(""" + return await wallet.generateKeyPair('testnet') + """), + 'walletGenerateKeyPairs': example(""" + return await wallet.generateKeyPairs('testnet', 3) + """), + 'walletKeyPairFromWif': example(f""" + return await wallet.keyPairFromWif('{WALLET_TEST_DATA['private_key_wif_testnet']}') + """), + 'walletKeyPairFromHex': example(f""" + return await wallet.keyPairFromHex('{WALLET_TEST_DATA['private_key_hex']}', 'testnet') + """), + 'walletPubkeyToAddress': example(f""" + return await wallet.pubkeyToAddress('{WALLET_TEST_DATA['public_key_hex']}', 'testnet') + """), + 'walletValidateAddress': example(f""" + return await wallet.validateAddress('{WALLET_TEST_DATA['address_testnet']}', 'testnet') + """), + 'walletSignMessage': example(f""" + return await wallet.signMessage('Hello Dash', '{WALLET_TEST_DATA['private_key_wif_testnet']}') + """), } return examples.get(key) @@ -823,8 +877,11 @@ def render_operation( sdk_method = str(item.get('sdk_method') or '') sdk_namespace, separator, sdk_method_name = sdk_method.rpartition('.') if separator: + # Package-level namespaces are imported directly, not reached through + # an EvoSDK instance, so they get no `sdk.` prefix. + instance_prefix = '' if sdk_namespace in PACKAGE_LEVEL_NAMESPACES else 'sdk.' sdk_path_html = ( - f'sdk.{safe_value(sdk_namespace)}.' + f'{instance_prefix}{safe_value(sdk_namespace)}.' f'{safe_value(sdk_method_name)}' ) else: @@ -889,12 +946,15 @@ def render_categories( for cat_key, category, items in sections: label = safe_value(category.get('label', cat_key)) category_id = f'{prefix}-category-{cat_key}' + category_note = '' + if category.get('description'): + category_note = f'\n

{safe_value(category["description"])}

' operations = '\n'.join( render_operation(prefix, item_key, item, example, header, include_run_button) for item_key, item, example in items ) blocks.append(f'''
-

{label}

+

{label}

{category_note} {operations}
''') return '\n'.join(blocks) @@ -902,7 +962,7 @@ def render_categories( def generate_docs_script() -> str: script = """ - import { EvoSDK } from './dist/evo-sdk.module.js'; + import { EvoSDK, wallet } from './dist/evo-sdk.module.js'; let client = null; let clientPromise = null; @@ -1089,10 +1149,12 @@ def generate_docs_script() -> str: result.style.display = 'none'; try { - const sdk = await getClient(); const code = codeElement.textContent; - const fn = new Function('EvoSDK', 'getClient', 'sdk', 'return (async () => { ' + code + ' })();'); - const output = await fn(EvoSDK, getClient, sdk); + // Wallet helper examples run locally in wasm; only connect a + // platform client for examples that actually use `sdk`. + const sdk = /\\bsdk\\b/.test(code) ? await getClient() : null; + const fn = new Function('EvoSDK', 'getClient', 'sdk', 'wallet', 'return (async () => { ' + code + ' })();'); + const output = await fn(EvoSDK, getClient, sdk, wallet); result.className = 'example-result success'; result.textContent = formatResult(output); return { success: true, output }; @@ -1356,6 +1418,12 @@ def generate_docs_script() -> str: def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: dict) -> str: + # Deliberately NOT `item.get('sdk_example') or …` (unlike the AI + # reference): docs.html examples are runnable function bodies executed via + # `new Function(...)` by the page's Run button, so they must not contain + # import statements — sdk_example snippets are standalone modules that do. + # Runnable variants live in evo_example_for_query; keep the two in sync + # when editing either. query_sections = collect_sections( query_defs, 'queries', @@ -1570,6 +1638,10 @@ def generate_ai_reference_md(query_defs: dict, transition_defs: dict, type_metad 'const result = await sdk..(params);', '```', '', + 'Exception: the `wallet` namespace is a package-level export (`import { wallet } from ' + "'@dashevo/evo-sdk'`), called as `wallet.(…)` without an SDK instance or " + 'platform connection.', + '', '### Available Queries', ] @@ -1609,11 +1681,14 @@ def append_query_sections() -> None: lines.append(f"#### {category.get('label', cat_key)}") lines.append('') + if category.get('description'): + lines.append(f"*{category['description']}*") + lines.append('') for query_key, query in queries.items(): label = query.get('label', query_key) description = query.get('description', 'No description available') - example_code = evo_example_for_query(query_key, query.get('inputs', [])) + example_code = query.get('sdk_example') or evo_example_for_query(query_key, query.get('inputs', [])) # Use sdk_method field from api-definitions.json if available, otherwise fall back to query_key sdk_method = query.get('sdk_method', query_key) diff --git a/tests/e2e/fixtures/test-data.js b/tests/e2e/fixtures/test-data.js index baa20b6..1ec8b96 100644 --- a/tests/e2e/fixtures/test-data.js +++ b/tests/e2e/fixtures/test-data.js @@ -1063,6 +1063,37 @@ const testData = { } }, + // Wallet helper fixtures: throwaway, deliberately public key material + // (BIP39 reference vector + a key derived from a fixed hex private key). + // Never use for real funds. Expected values were produced with + // @dashevo/evo-sdk wallet.* and match the published BIP39 test vectors. + wallet: { + mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + invalidMnemonic: 'not a real mnemonic phrase at all', + // BIP39 seed for the mnemonic above with an empty passphrase + seedHex: '5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4', + // BIP39 seed for the mnemonic above with passphrase 'TREZOR' + seedHexTrezor: 'c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04', + // BIP39 seed for passphrase ' TREZOR ' (padded): passphrase whitespace is + // significant and must reach the SDK untrimmed + seedHexTrezorPadded: 'c3e2744f57e3f6e362753a4a240fa209988f367d09b2d77b05b62ced64c7f175ebebd7bfa2d424260697eafa26991241992d6627d64f4b4eba2d178db20a0275', + privateKeyHex: 'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a', + publicKeyHex: '0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71', + testnet: { + privateKeyWif: 'cUB8G5cFtxc4usfgfovqRgCo8qTQUJtctLV8t6YYNfULg3GtehdX', + address: 'yXSSUfQ8AFeWVionamYzedYSf3PANMazbw' + }, + mainnet: { + privateKeyWif: 'XHt4FRzmmaYFomCoKA7aZatkRdRaFfQBBsgaJCREBv6RpTM62nzK', + address: 'XmoqTiKgihzS9ytF1vEbcc86NktnqvCjih' + }, + signMessage: { + message: 'Hello Dash', + // Deterministic ECDSA (RFC 6979) compact signature, hex-encoded + expectedSignature: 'b8cc0d9b97f6c7e5c044dd777c7cdd891ac335b33d96ad7c8fc2a7a91038d8a46451cfffdbf57b2579c0f6ef91925526d30fe0d7d20f558c28b8defec40878fb' + } + }, + // Common where clauses for document queries whereClausesExamples: { dpnsDomain: [ diff --git a/tests/e2e/wallet/wallet-operations.spec.js b/tests/e2e/wallet/wallet-operations.spec.js new file mode 100644 index 0000000..72e0908 --- /dev/null +++ b/tests/e2e/wallet/wallet-operations.spec.js @@ -0,0 +1,202 @@ +const { test, expect } = require('@playwright/test'); +const { EvoSdkPage } = require('../utils/sdk-page'); +const { testData } = require('../fixtures/test-data'); + +/** + * Wallet helper operations: local wasm execution, no platform connection. + * + * All fixtures are throwaway, deliberately public key material (BIP39 + * reference vector + a key derived from a fixed hex private key) — never + * usable for real funds. + */ + +const wallet = testData.wallet; +const CATEGORY = 'keyGeneration'; + +async function runWalletOperation(evoSdkPage, operation, parameters = {}) { + await evoSdkPage.setupQuery(CATEGORY, operation, parameters, { operationType: 'wallet' }); + return evoSdkPage.executeQueryAndGetResult(); +} + +test.describe('Wallet operations', () => { + let evoSdkPage; + + test.beforeEach(async ({ page }) => { + evoSdkPage = new EvoSdkPage(page); + await evoSdkPage.initialize('testnet'); + }); + + test('generateMnemonic returns a phrase with the selected word count', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletGenerateMnemonic', { + wordCount: '24' + }); + expect(hasError).toBe(false); + const phrase = result.replace(/^"|"$/g, '').trim(); + expect(phrase.split(/\s+/)).toHaveLength(24); + }); + + test('validateMnemonic distinguishes valid and invalid phrases', async () => { + const good = await runWalletOperation(evoSdkPage, 'walletValidateMnemonic', { + mnemonic: wallet.mnemonic + }); + expect(good.hasError).toBe(false); + expect(good.result.trim()).toBe('true'); + + const bad = await runWalletOperation(evoSdkPage, 'walletValidateMnemonic', { + mnemonic: wallet.invalidMnemonic + }); + expect(bad.hasError).toBe(false); + expect(bad.result.trim()).toBe('false'); + }); + + test('mnemonicToSeed matches the BIP39 reference vector', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletMnemonicToSeed', { + mnemonic: wallet.mnemonic + }); + expect(hasError).toBe(false); + expect(result.replace(/^"|"$/g, '').trim()).toBe(wallet.seedHex); + + const withPassphrase = await runWalletOperation(evoSdkPage, 'walletMnemonicToSeed', { + mnemonic: wallet.mnemonic, + passphrase: 'TREZOR' + }); + expect(withPassphrase.hasError).toBe(false); + expect(withPassphrase.result.replace(/^"|"$/g, '').trim()).toBe(wallet.seedHexTrezor); + + // Passphrase whitespace is significant: ' TREZOR ' must reach the SDK + // untrimmed and produce a different seed than 'TREZOR'. + const paddedPassphrase = await runWalletOperation(evoSdkPage, 'walletMnemonicToSeed', { + mnemonic: wallet.mnemonic, + passphrase: ' TREZOR ' + }); + expect(paddedPassphrase.hasError).toBe(false); + expect(paddedPassphrase.result.replace(/^"|"$/g, '').trim()).toBe(wallet.seedHexTrezorPadded); + }); + + test('generateKeyPair returns a key pair for the selected network', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletGenerateKeyPair'); + expect(hasError).toBe(false); + const keyPair = JSON.parse(result); + expect(Object.keys(keyPair).sort()).toEqual( + ['address', 'network', 'privateKeyHex', 'privateKeyWif', 'publicKey'] + ); + expect(keyPair.network).toBe('testnet'); + }); + + test('generateKeyPairs returns the requested number of key pairs', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletGenerateKeyPairs', { + count: '3' + }); + expect(hasError).toBe(false); + const keyPairs = JSON.parse(result); + expect(keyPairs).toHaveLength(3); + for (const keyPair of keyPairs) { + expect(keyPair.network).toBe('testnet'); + expect(keyPair.privateKeyWif).toBeTruthy(); + } + }); + + test('keyPairFromWif derives the expected key pair (network from the WIF prefix)', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletKeyPairFromWif', { + privateKeyWif: wallet.testnet.privateKeyWif + }); + expect(hasError).toBe(false); + const keyPair = JSON.parse(result); + expect(keyPair).toEqual({ + privateKeyWif: wallet.testnet.privateKeyWif, + privateKeyHex: wallet.privateKeyHex, + publicKey: wallet.publicKeyHex, + address: wallet.testnet.address, + network: 'testnet' + }); + }); + + test('keyPairFromHex derives per-network key pairs from the same hex key', async () => { + const testnetRun = await runWalletOperation(evoSdkPage, 'walletKeyPairFromHex', { + privateKeyHex: wallet.privateKeyHex + }); + expect(testnetRun.hasError).toBe(false); + const testnetKeyPair = JSON.parse(testnetRun.result); + expect(testnetKeyPair.privateKeyWif).toBe(wallet.testnet.privateKeyWif); + expect(testnetKeyPair.address).toBe(wallet.testnet.address); + expect(testnetKeyPair.network).toBe('testnet'); + + // Switching the network selector changes the injected network argument. + await evoSdkPage.setNetwork('mainnet'); + const mainnetRun = await runWalletOperation(evoSdkPage, 'walletKeyPairFromHex', { + privateKeyHex: wallet.privateKeyHex + }); + expect(mainnetRun.hasError).toBe(false); + const mainnetKeyPair = JSON.parse(mainnetRun.result); + expect(mainnetKeyPair.privateKeyWif).toBe(wallet.mainnet.privateKeyWif); + expect(mainnetKeyPair.address).toBe(wallet.mainnet.address); + expect(mainnetKeyPair.network).toBe('mainnet'); + }); + + test('pubkeyToAddress converts a public key to the fixture address', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletPubkeyToAddress', { + pubkeyHex: wallet.publicKeyHex + }); + expect(hasError).toBe(false); + expect(result.replace(/^"|"$/g, '').trim()).toBe(wallet.testnet.address); + }); + + test('validateAddress accepts the right network and rejects the wrong one', async () => { + const good = await runWalletOperation(evoSdkPage, 'walletValidateAddress', { + address: wallet.testnet.address + }); + expect(good.hasError).toBe(false); + expect(good.result.trim()).toBe('true'); + + const wrongNetwork = await runWalletOperation(evoSdkPage, 'walletValidateAddress', { + address: wallet.mainnet.address + }); + expect(wrongNetwork.hasError).toBe(false); + expect(wrongNetwork.result.trim()).toBe('false'); + }); + + test('signMessage produces the deterministic expected signature', async () => { + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletSignMessage', { + message: wallet.signMessage.message, + privateKeyWif: wallet.testnet.privateKeyWif + }); + expect(hasError).toBe(false); + expect(result.replace(/^"|"$/g, '').trim()).toBe(wallet.signMessage.expectedSignature); + }); + + test('wallet UI shows no proof toggle and no authentication section', async ({ page }) => { + await evoSdkPage.setupQuery(CATEGORY, 'walletGenerateKeyPair', {}, { operationType: 'wallet' }); + await expect(page.locator('#proofToggleContainer')).toBeHidden(); + await expect(page.locator('#authenticationInputs')).toBeHidden(); + }); + + test('secret inputs are rendered as password fields', async ({ page }) => { + await evoSdkPage.setupQuery(CATEGORY, 'walletKeyPairFromWif', {}, { operationType: 'wallet' }); + const wifInput = page.locator('[data-input-name="privateKeyWif"]'); + await expect(wifInput).toHaveAttribute('type', 'password'); + }); + + test('wallet operations issue no external requests (offline guarantee)', async ({ page }) => { + // Recording matched external requests (not just aborting them) proves + // absence: a fire-and-forget request would still land in the list. + const externalRequests = []; + await page.route(/.*/, route => { + const url = new URL(route.request().url()); + if (!['localhost', '127.0.0.1'].includes(url.hostname)) { + externalRequests.push(url.href); + return route.abort(); + } + return route.continue(); + }); + + // Select the operation first, then clear the list right before executing + // so an unrelated page-level fetch cannot misattribute a failure. + await evoSdkPage.setupQuery(CATEGORY, 'walletGenerateKeyPair', {}, { operationType: 'wallet' }); + externalRequests.length = 0; + + const { result, hasError } = await evoSdkPage.executeQueryAndGetResult(); + expect(hasError).toBe(false); + expect(JSON.parse(result).network).toBe('testnet'); + expect(externalRequests).toEqual([]); + }); +}); diff --git a/tests/type-extraction.test.mjs b/tests/type-extraction.test.mjs index 60f1dc9..780fe25 100644 --- a/tests/type-extraction.test.mjs +++ b/tests/type-extraction.test.mjs @@ -21,6 +21,68 @@ function fixture({ method = 'sample', methodDocs = '', parameters = '', returnTy return { root, packageRoot, apiFile }; } +// Package-level namespace fixture: declarations are namespace functions in +// dist/wallet/functions.d.ts (per NAMESPACE_FILES), not facade class methods. +function walletFixture({ input = {} } = {}) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'evo-type-test-')); + const packageRoot = path.join(root, 'node_modules/@dashevo/evo-sdk'); + const wasmRoot = path.join(root, 'node_modules/@dashevo/wasm-sdk'); + fs.mkdirSync(path.join(packageRoot, 'dist/wallet'), { recursive: true }); + fs.mkdirSync(path.join(wasmRoot, 'dist/raw'), { recursive: true }); + fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ name: '@dashevo/evo-sdk', version: '4.0.0' })); + fs.writeFileSync(path.join(wasmRoot, 'package.json'), JSON.stringify({ name: '@dashevo/wasm-sdk', version: '4.0.0' })); + fs.writeFileSync( + path.join(packageRoot, 'dist/wallet/functions.d.ts'), + 'export declare namespace wallet {\n function sample(params?: wasm.SampleParams): Promise;\n}\n', + ); + fs.writeFileSync( + path.join(wasmRoot, 'dist/raw/wasm_sdk.d.ts'), + 'export interface SampleParams { wordCount?: number; languageCode?: string; }\nexport interface SampleResult { value: string; }\n', + ); + const apiFile = path.join(root, 'api.json'); + fs.writeFileSync(apiFile, JSON.stringify({ queries: { wallet: { queries: { operation: { sdk_method: 'wallet.sample', ...input } } } }, transitions: {} })); + return { root, packageRoot, apiFile }; +} + +test('extracts namespace functions from a NAMESPACE_FILES module with mappings and a package-level example', () => { + const fx = walletFixture({ + input: { + inputs: [{ name: 'wordCount', sdk_property: 'params.wordCount' }], + sdk_example: "import { wallet } from '@dashevo/evo-sdk';\nconst result = await wallet.sample({ wordCount: 12 });", + }, + }); + const extracted = extractTypes(fx); + const method = extracted.methods['wallet.sample']; + assert.equal(method.signature, 'sample(params?: wasm.SampleParams): Promise'); + assert.deepEqual(method.parameters.map(({ name, optional }) => ({ name, optional })), [ + { name: 'params', optional: true }, + ]); + assert.deepEqual(method.parameters[0].properties.map(({ name }) => name), ['wordCount', 'languageCode']); + assert.equal(method.returnType, 'Promise'); + assert.match(extracted.types.SampleResult.declaration, /interface SampleResult/); +}); + +test('namespace-function modules still validate sdk_property mappings', () => { + const fx = walletFixture({ input: { inputs: [{ name: 'bad', sdk_property: 'params.missing' }] } }); + assert.throws(() => extractTypes(fx), /unknown SDK property params\.missing/); +}); + +test('namespace-function examples reject unknown option keys', () => { + const fx = walletFixture({ input: { sdk_example: 'await wallet.sample({ missing: 1 });' } }); + assert.throws(() => extractTypes(fx), /sdk_example uses unknown SDK property missing/); +}); + +test('a two-part call only counts for NAMESPACE_FILES namespaces', () => { + // `example` is a facade namespace, so a bare `example.sample()` must NOT + // satisfy example validation — only `sdk.example.sample()` does. + const fx = fixture({ + parameters: 'options: wasm.SampleOptions', + declarations: 'export interface SampleOptions { value: string; }', + input: { sdk_example: 'await example.sample({ value });' }, + }); + assert.throws(() => extractTypes(fx), /sdk_example must call example\.sample exactly once/); +}); + test('preserves exact primitive, union, array, nested generic, inline object, and qualified types', () => { const returnType = 'Promise | Array<{ count: bigint; values: string[] }>>'; const fx = fixture({ returnType, declarations: 'export interface ResultType { value: bigint; }' }); diff --git a/tests/unit/definitions-data.test.js b/tests/unit/definitions-data.test.js index 0024fb0..cba68d5 100644 --- a/tests/unit/definitions-data.test.js +++ b/tests/unit/definitions-data.test.js @@ -21,8 +21,12 @@ describe('getTypeConfig', () => { expect(getTypeConfig('dpns')).toEqual({ definitionKey: 'dpns', itemsKey: 'operations', allowProof: true }); }); + it('returns the wallet config (operations itemsKey, proof not allowed)', () => { + expect(getTypeConfig('wallet')).toEqual({ definitionKey: 'wallet', itemsKey: 'operations', allowProof: false }); + }); + it('returns null for an unknown type', () => { - expect(getTypeConfig('wallet')).toBeNull(); + expect(getTypeConfig('teleporter')).toBeNull(); expect(getTypeConfig(undefined)).toBeNull(); }); }); diff --git a/tests/unit/parse-input.test.js b/tests/unit/parse-input.test.js index 082604e..9580df0 100644 --- a/tests/unit/parse-input.test.js +++ b/tests/unit/parse-input.test.js @@ -137,6 +137,18 @@ describe('parseInputValue — text / textarea / default', () => { expect(parseInputValue('somethingElse', {}, txt(' x '))).toBe('x'); expect(parseInputValue('somethingElse', {}, txt(''))).toBeNull(); }); + + it('preserveWhitespace passes the value through untrimmed (message, BIP39 passphrase)', () => { + expect(parseInputValue('textarea', { preserveWhitespace: true }, txt(' padded '))).toBe(' padded '); + expect(parseInputValue('password', { preserveWhitespace: true }, txt(' TREZOR '))).toBe(' TREZOR '); + expect(parseInputValue('password', { preserveWhitespace: true }, txt(' '))).toBe(' '); + }); + + it('preserveWhitespace still treats a fully empty value as missing', () => { + expect(parseInputValue('password', { preserveWhitespace: true }, txt(''))).toBeNull(); + expect(() => parseInputValue('textarea', { preserveWhitespace: true, required: true, label: 'Message' }, txt(''))) + .toThrow('Message is required'); + }); }); describe('namedArgs', () => { diff --git a/tests/unit/sdk-client-network.test.js b/tests/unit/sdk-client-network.test.js new file mode 100644 index 0000000..caa511c --- /dev/null +++ b/tests/unit/sdk-client-network.test.js @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +// Direct coverage of the real getSelectedNetwork() implementation in +// sdk-client.js: the checked radio wins, and the unified fallback is +// 'mainnet' (matching the checked default set by main.js at init). +// +// state.js captures elements.networkRadios from the DOM at import time, so +// stub `document` with mutable radio stand-ins before dynamically importing; +// getSelectedNetwork re-reads `checked` on every call, so the same array +// objects can be toggled between assertions. + +const radios = [ + { name: 'network', value: 'mainnet', checked: false }, + { name: 'network', value: 'testnet', checked: false }, +]; + +let getSelectedNetwork; + +beforeAll(async () => { + vi.stubGlobal('document', { + getElementById: () => null, + querySelectorAll: (selector) => (selector === 'input[name="network"]' ? radios : []), + }); + ({ getSelectedNetwork } = await import('../../public/src/sdk-client.js')); +}); + +describe('getSelectedNetwork', () => { + it('returns testnet when the testnet radio is checked', () => { + radios[0].checked = false; + radios[1].checked = true; + expect(getSelectedNetwork()).toBe('testnet'); + }); + + it('returns mainnet when the mainnet radio is checked', () => { + radios[0].checked = true; + radios[1].checked = false; + expect(getSelectedNetwork()).toBe('mainnet'); + }); + + it('falls back to mainnet when no radio is checked', () => { + radios[0].checked = false; + radios[1].checked = false; + expect(getSelectedNetwork()).toBe('mainnet'); + }); +}); diff --git a/tests/unit/wallet-dispatch.test.js b/tests/unit/wallet-dispatch.test.js new file mode 100644 index 0000000..35d4b14 --- /dev/null +++ b/tests/unit/wallet-dispatch.test.js @@ -0,0 +1,202 @@ +import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest'; + +// Dispatch coverage for the wallet cases in callEvo(): argument mapping, +// injected network, handler-level validation, and the guarantee that wallet +// operations never touch the (null) platform client. +// +// operations.js imports { wallet, getSelectedNetwork } from sdk-client.js, +// which transitively loads the real SDK bundle and browser state; mock the +// whole module so wallet calls are observable and no wasm is initialized. + +const mocks = vi.hoisted(() => ({ + wallet: { + generateMnemonic: vi.fn().mockResolvedValue('mnemonic'), + validateMnemonic: vi.fn().mockResolvedValue(true), + mnemonicToSeed: vi.fn().mockResolvedValue(new Uint8Array([0x5e, 0xb0, 0x0b])), + generateKeyPair: vi.fn().mockResolvedValue('keyPair'), + generateKeyPairs: vi.fn().mockResolvedValue(['keyPair']), + keyPairFromWif: vi.fn().mockResolvedValue('keyPairFromWif'), + keyPairFromHex: vi.fn().mockResolvedValue('keyPairFromHex'), + pubkeyToAddress: vi.fn().mockResolvedValue('address'), + validateAddress: vi.fn().mockResolvedValue(true), + signMessage: vi.fn().mockResolvedValue('signature'), + }, + getSelectedNetwork: vi.fn().mockReturnValue('testnet'), +})); + +vi.mock('../../public/src/sdk-client.js', () => ({ + wallet: mocks.wallet, + getSelectedNetwork: mocks.getSelectedNetwork, +})); + +let callEvo; + +beforeAll(async () => { + // operations.js transitively imports state.js, which touches `document` at + // import time; stub a minimal shim before dynamically importing (same + // approach as operations-dispatch.test.js). + vi.stubGlobal('document', { + getElementById: () => null, + querySelectorAll: () => [], + }); + ({ callEvo } = await import('../../public/src/operations.js')); +}); + +beforeEach(() => { + vi.clearAllMocks(); + mocks.getSelectedNetwork.mockReturnValue('testnet'); +}); + +// A proxy client that throws on ANY property access: proves wallet cases +// never touch the platform client (executeSelected passes null for wallet, +// so any access would be a bug either way). +const poisonClient = new Proxy({}, { + get(_target, prop) { + // callEvo probes the transition registry first, which only reads the + // client after matching a transition key; allow standard well-known + // symbol probes used by the runtime itself. + if (typeof prop === 'symbol' || prop === 'then') return undefined; + throw new Error(`wallet operation touched client.${String(prop)}`); + }, +}); + +function run(itemKey, values = {}, client = null) { + return callEvo(client, 'wallet', itemKey, [], [], false, values); +} + +describe('walletGenerateMnemonic', () => { + it('passes wordCount and languageCode as a params object', async () => { + await run('walletGenerateMnemonic', { wordCount: '24', languageCode: 'es' }); + expect(mocks.wallet.generateMnemonic).toHaveBeenCalledWith({ wordCount: 24, languageCode: 'es' }); + }); + + it('defaults to 12 words and omits an empty languageCode', async () => { + await run('walletGenerateMnemonic', { languageCode: '' }); + expect(mocks.wallet.generateMnemonic).toHaveBeenCalledWith({ wordCount: 12 }); + }); + + it('rejects an invalid word count', async () => { + for (const wordCount of ['13', '0', '-12', '12.5']) { + await expect(run('walletGenerateMnemonic', { wordCount })) + .rejects.toThrow('Word count must be 12, 15, 18, 21, or 24'); + } + expect(mocks.wallet.generateMnemonic).not.toHaveBeenCalled(); + }); +}); + +describe('walletValidateMnemonic', () => { + it('trims the mnemonic and forwards the language', async () => { + await run('walletValidateMnemonic', { mnemonic: ' abandon about ', languageCode: 'en' }); + expect(mocks.wallet.validateMnemonic).toHaveBeenCalledWith('abandon about', 'en'); + }); + + it('passes undefined for a blank language', async () => { + await run('walletValidateMnemonic', { mnemonic: 'abandon about', languageCode: '' }); + expect(mocks.wallet.validateMnemonic).toHaveBeenCalledWith('abandon about', undefined); + }); + + it('rejects a whitespace-only mnemonic', async () => { + await expect(run('walletValidateMnemonic', { mnemonic: ' ' })).rejects.toThrow('Mnemonic is required'); + }); +}); + +describe('walletMnemonicToSeed', () => { + it('hex-encodes the returned seed bytes', async () => { + await expect(run('walletMnemonicToSeed', { mnemonic: 'abandon about' })).resolves.toBe('5eb00b'); + expect(mocks.wallet.mnemonicToSeed).toHaveBeenCalledWith('abandon about', undefined); + }); + + it('forwards a passphrase', async () => { + await run('walletMnemonicToSeed', { mnemonic: 'abandon about', passphrase: 'TREZOR' }); + expect(mocks.wallet.mnemonicToSeed).toHaveBeenCalledWith('abandon about', 'TREZOR'); + }); + + it('never trims the passphrase (whitespace changes the derived seed)', async () => { + await run('walletMnemonicToSeed', { mnemonic: 'abandon about', passphrase: ' TREZOR ' }); + expect(mocks.wallet.mnemonicToSeed).toHaveBeenCalledWith('abandon about', ' TREZOR '); + }); + + it('rejects a missing mnemonic', async () => { + await expect(run('walletMnemonicToSeed', {})).rejects.toThrow('Mnemonic is required'); + }); +}); + +describe('key pair generation', () => { + it('walletGenerateKeyPair injects the selected network', async () => { + mocks.getSelectedNetwork.mockReturnValue('mainnet'); + await run('walletGenerateKeyPair'); + expect(mocks.wallet.generateKeyPair).toHaveBeenCalledWith('mainnet'); + }); + + it('walletGenerateKeyPairs injects the network and passes an integer count', async () => { + await run('walletGenerateKeyPairs', { count: '3' }); + expect(mocks.wallet.generateKeyPairs).toHaveBeenCalledWith('testnet', 3); + }); + + it('walletGenerateKeyPairs rejects out-of-range or non-integer counts', async () => { + for (const count of ['0', '-1', '101', '2.5', 'abc', '']) { + await expect(run('walletGenerateKeyPairs', { count })) + .rejects.toThrow('Count must be an integer between 1 and 100'); + } + expect(mocks.wallet.generateKeyPairs).not.toHaveBeenCalled(); + }); +}); + +describe('key import and address helpers', () => { + it('walletKeyPairFromWif trims the WIF and takes no network', async () => { + await run('walletKeyPairFromWif', { privateKeyWif: ' cWif ' }); + expect(mocks.wallet.keyPairFromWif).toHaveBeenCalledWith('cWif'); + expect(mocks.getSelectedNetwork).not.toHaveBeenCalled(); + }); + + it('walletKeyPairFromHex trims the hex and injects the network', async () => { + await run('walletKeyPairFromHex', { privateKeyHex: ' c4bb ' }); + expect(mocks.wallet.keyPairFromHex).toHaveBeenCalledWith('c4bb', 'testnet'); + }); + + it('walletPubkeyToAddress injects the network', async () => { + await run('walletPubkeyToAddress', { pubkeyHex: '0378d4' }); + expect(mocks.wallet.pubkeyToAddress).toHaveBeenCalledWith('0378d4', 'testnet'); + }); + + it('walletValidateAddress injects the network', async () => { + await run('walletValidateAddress', { address: 'yXSS' }); + expect(mocks.wallet.validateAddress).toHaveBeenCalledWith('yXSS', 'testnet'); + }); + + it('rejects whitespace-only trimmed inputs', async () => { + await expect(run('walletKeyPairFromWif', { privateKeyWif: ' ' })).rejects.toThrow('Private key WIF is required'); + await expect(run('walletKeyPairFromHex', { privateKeyHex: ' ' })).rejects.toThrow('Private key hex is required'); + await expect(run('walletPubkeyToAddress', { pubkeyHex: ' ' })).rejects.toThrow('Public key hex is required'); + await expect(run('walletValidateAddress', { address: ' ' })).rejects.toThrow('Address is required'); + }); +}); + +describe('walletSignMessage', () => { + it('passes the message through exactly, without trimming', async () => { + await run('walletSignMessage', { message: ' padded message ', privateKeyWif: 'cWif' }); + expect(mocks.wallet.signMessage).toHaveBeenCalledWith(' padded message ', 'cWif'); + }); + + it('accepts a whitespace-only message (whitespace is legitimate signed data)', async () => { + await run('walletSignMessage', { message: ' ', privateKeyWif: 'cWif' }); + expect(mocks.wallet.signMessage).toHaveBeenCalledWith(' ', 'cWif'); + }); + + it('rejects only an empty or missing message', async () => { + await expect(run('walletSignMessage', { message: '', privateKeyWif: 'cWif' })).rejects.toThrow('Message is required'); + await expect(run('walletSignMessage', { privateKeyWif: 'cWif' })).rejects.toThrow('Message is required'); + expect(mocks.wallet.signMessage).not.toHaveBeenCalled(); + }); +}); + +describe('client isolation', () => { + it('wallet operations never touch the platform client', async () => { + await run('walletGenerateMnemonic', {}, poisonClient); + await run('walletGenerateKeyPair', {}, poisonClient); + await run('walletSignMessage', { message: 'x', privateKeyWif: 'cWif' }, poisonClient); + expect(mocks.wallet.generateMnemonic).toHaveBeenCalled(); + expect(mocks.wallet.generateKeyPair).toHaveBeenCalled(); + expect(mocks.wallet.signMessage).toHaveBeenCalled(); + }); +}); From 0dd45b526993154947b7b8cbff7e0756b92386b8 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 2 Sep 2026 16:59:42 +0200 Subject: [PATCH 2/4] docs: apostrophe cleanup --- public/AI_REFERENCE.md | 14 +++++++------- public/api-definitions.json | 14 +++++++------- public/sdk-operation-catalog.json | 12 ++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index b76d403..04f8757 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -1910,7 +1910,7 @@ return await sdk.shielded.nullifiers([nullifier]); #### Wallet Utilities -*Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site’s network selector.* +*Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site's network selector.* **Generate Mnemonic** - `wallet.generateMnemonic` *Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only — never use it to store real funds.* @@ -1974,7 +1974,7 @@ const seedBytes = await wallet.mnemonicToSeed('your seed phrase here …'); ``` **Generate Key Pair** - `wallet.generateKeyPair` -*Generate a random private key with its public key and address. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.* +*Generate a random private key with its public key and address. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* Signature: `generateKeyPair(network: NetworkLike): Promise` @@ -1994,7 +1994,7 @@ const keyPair = await wallet.generateKeyPair('testnet'); ``` **Generate Key Pairs** - `wallet.generateKeyPairs` -*Generate multiple random key pairs at once. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.* +*Generate multiple random key pairs at once. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* Signature: `generateKeyPairs(network: NetworkLike, count: number): Promise` @@ -2016,7 +2016,7 @@ const keyPairs = await wallet.generateKeyPairs('testnet', 3); ``` **Key Pair from WIF** - `wallet.keyPairFromWif` -*Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site’s network selector does not affect this operation.* +*Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site's network selector does not affect this operation.* Signature: `keyPairFromWif(privateKeyWif: string): Promise` @@ -2035,7 +2035,7 @@ const keyPair = await wallet.keyPairFromWif('cYourTestnetPrivateKeyWif…'); ``` **Key Pair from Hex** - `wallet.keyPairFromHex` -*Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site’s network selector.* +*Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site's network selector.* Signature: `keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise` @@ -2057,7 +2057,7 @@ const keyPair = await wallet.keyPairFromHex('c4bb… (64 hex chars)', 'testnet') ``` **Public Key to Address** - `wallet.pubkeyToAddress` -*Convert a compressed public key (hex) to a Dash address. The network comes from the site’s network selector.* +*Convert a compressed public key (hex) to a Dash address. The network comes from the site's network selector.* Signature: `pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise` @@ -2078,7 +2078,7 @@ const address = await wallet.pubkeyToAddress('0378d4…', 'testnet'); ``` **Validate Address** - `wallet.validateAddress` -*Check whether a Dash address is valid for a network. The network comes from the site’s network selector.* +*Check whether a Dash address is valid for a network. The network comes from the site's network selector.* Signature: `validateAddress(address: string, network: NetworkLike): Promise` diff --git a/public/api-definitions.json b/public/api-definitions.json index b70e4a5..18eaf37 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -1786,7 +1786,7 @@ }, "wallet": { "label": "Wallet Utilities", - "description": "Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site’s network selector.", + "description": "Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site's network selector.", "queries": { "walletGenerateMnemonic": { "label": "Generate Mnemonic", @@ -1971,14 +1971,14 @@ }, "walletGenerateKeyPair": { "label": "Generate Key Pair", - "description": "Generate a random private key with its public key and address. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.", + "description": "Generate a random private key with its public key and address. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.", "inputs": [], "sdk_method": "wallet.generateKeyPair", "sdk_example": "import { wallet } from '@dashevo/evo-sdk';\nconst keyPair = await wallet.generateKeyPair('testnet');" }, "walletGenerateKeyPairs": { "label": "Generate Key Pairs", - "description": "Generate multiple random key pairs at once. The network comes from the site’s network selector. Generated key material is for testing only — never use it to store real funds.", + "description": "Generate multiple random key pairs at once. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.", "inputs": [ { "name": "count", @@ -1996,7 +1996,7 @@ }, "walletKeyPairFromWif": { "label": "Key Pair from WIF", - "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site’s network selector does not affect this operation.", + "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site's network selector does not affect this operation.", "inputs": [ { "name": "privateKeyWif", @@ -2011,7 +2011,7 @@ }, "walletKeyPairFromHex": { "label": "Key Pair from Hex", - "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site’s network selector.", + "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site's network selector.", "inputs": [ { "name": "privateKeyHex", @@ -2026,7 +2026,7 @@ }, "walletPubkeyToAddress": { "label": "Public Key to Address", - "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site’s network selector.", + "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site's network selector.", "inputs": [ { "name": "pubkeyHex", @@ -2042,7 +2042,7 @@ }, "walletValidateAddress": { "label": "Validate Address", - "description": "Check whether a Dash address is valid for a network. The network comes from the site’s network selector.", + "description": "Check whether a Dash address is valid for a network. The network comes from the site's network selector.", "inputs": [ { "name": "address", diff --git a/public/sdk-operation-catalog.json b/public/sdk-operation-catalog.json index ed31546..4a4c63c 100644 --- a/public/sdk-operation-catalog.json +++ b/public/sdk-operation-catalog.json @@ -3042,7 +3042,7 @@ "category": "wallet", "sdkMethod": "wallet.generateKeyPair", "label": "Generate Key Pair", - "description": "Generate a random private key with its public key and address. The network comes from the site\u2019s network selector. Generated key material is for testing only \u2014 never use it to store real funds.", + "description": "Generate a random private key with its public key and address. The network comes from the site's network selector. Generated key material is for testing only \u2014 never use it to store real funds.", "disabled": null, "signature": "generateKeyPair(network: NetworkLike): Promise", "parameters": [ @@ -3069,7 +3069,7 @@ "category": "wallet", "sdkMethod": "wallet.generateKeyPairs", "label": "Generate Key Pairs", - "description": "Generate multiple random key pairs at once. The network comes from the site\u2019s network selector. Generated key material is for testing only \u2014 never use it to store real funds.", + "description": "Generate multiple random key pairs at once. The network comes from the site's network selector. Generated key material is for testing only \u2014 never use it to store real funds.", "disabled": null, "signature": "generateKeyPairs(network: NetworkLike, count: number): Promise", "parameters": [ @@ -3105,7 +3105,7 @@ "category": "wallet", "sdkMethod": "wallet.keyPairFromWif", "label": "Key Pair from WIF", - "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site\u2019s network selector does not affect this operation.", + "description": "Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site's network selector does not affect this operation.", "disabled": null, "signature": "keyPairFromWif(privateKeyWif: string): Promise", "parameters": [ @@ -3130,7 +3130,7 @@ "category": "wallet", "sdkMethod": "wallet.keyPairFromHex", "label": "Key Pair from Hex", - "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site\u2019s network selector.", + "description": "Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site's network selector.", "disabled": null, "signature": "keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise", "parameters": [ @@ -3166,7 +3166,7 @@ "category": "wallet", "sdkMethod": "wallet.pubkeyToAddress", "label": "Public Key to Address", - "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site\u2019s network selector.", + "description": "Convert a compressed public key (hex) to a Dash address. The network comes from the site's network selector.", "disabled": null, "signature": "pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise", "parameters": [ @@ -3200,7 +3200,7 @@ "category": "wallet", "sdkMethod": "wallet.validateAddress", "label": "Validate Address", - "description": "Check whether a Dash address is valid for a network. The network comes from the site\u2019s network selector.", + "description": "Check whether a Dash address is valid for a network. The network comes from the site's network selector.", "disabled": null, "signature": "validateAddress(address: string, network: NetworkLike): Promise", "parameters": [ From 788253fee222ebea42945544060a32c497f87f72 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 2 Sep 2026 17:16:57 +0200 Subject: [PATCH 3/4] docs: move wallet helpers into their own top-level section Co-Authored-By: Claude Fable 5 --- public/AI_REFERENCE.md | 432 +++++++++++++------------- public/documentation-check-report.txt | 2 +- scripts/generate_docs.py | 67 +++- 3 files changed, 274 insertions(+), 227 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 04f8757..5e136bc 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -41,8 +41,6 @@ All queries follow this pattern: const result = await sdk..(params); ``` -Exception: the `wallet` namespace is a package-level export (`import { wallet } from '@dashevo/evo-sdk'`), called as `wallet.(…)` without an SDK instance or platform connection. - ### Available Queries #### Identity Queries @@ -1908,216 +1906,6 @@ const nullifier = new Uint8Array(32); return await sdk.shielded.nullifiers([nullifier]); ``` -#### Wallet Utilities - -*Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site's network selector.* - -**Generate Mnemonic** - `wallet.generateMnemonic` -*Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only — never use it to store real funds.* - -Signature: `generateMnemonic(params?: wasm.GenerateMnemonicParams): Promise` - -Parameters: -- `params`: `wasm.GenerateMnemonicParams` (optional) - - Type declarations: [`wasm.GenerateMnemonicParams`](TYPE_REFERENCE.md#type-generatemnemonicparams) - - `wordCount`: `number` (optional) - - `languageCode`: `string` (optional) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const mnemonic = await wallet.generateMnemonic({ wordCount: 12 }); -``` - -**Validate Mnemonic** - `wallet.validateMnemonic` -*Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.* - -Signature: `validateMnemonic(mnemonic: string, languageCode?: string): Promise` - -Parameters: -- `mnemonic`: `string` (required) - -- `languageCode`: `string` (optional) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const isValid = await wallet.validateMnemonic('your seed phrase here …'); -``` - -**Mnemonic to Seed** - `wallet.mnemonicToSeed` -*Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.* - -Signature: `mnemonicToSeed(mnemonic: string, passphrase?: string): Promise` - -Parameters: -- `mnemonic`: `string` (required) - -- `passphrase`: `string` (optional) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const seedBytes = await wallet.mnemonicToSeed('your seed phrase here …'); -``` - -**Generate Key Pair** - `wallet.generateKeyPair` -*Generate a random private key with its public key and address. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* - -Signature: `generateKeyPair(network: NetworkLike): Promise` - -Parameters: -- `network`: `NetworkLike` (required) - - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) - -Returns: - -- `Promise` - - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const keyPair = await wallet.generateKeyPair('testnet'); -``` - -**Generate Key Pairs** - `wallet.generateKeyPairs` -*Generate multiple random key pairs at once. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* - -Signature: `generateKeyPairs(network: NetworkLike, count: number): Promise` - -Parameters: -- `network`: `NetworkLike` (required) - - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) - -- `count`: `number` (required) - -Returns: - -- `Promise` - - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const keyPairs = await wallet.generateKeyPairs('testnet', 3); -``` - -**Key Pair from WIF** - `wallet.keyPairFromWif` -*Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site's network selector does not affect this operation.* - -Signature: `keyPairFromWif(privateKeyWif: string): Promise` - -Parameters: -- `privateKeyWif`: `string` (required) - -Returns: - -- `Promise` - - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const keyPair = await wallet.keyPairFromWif('cYourTestnetPrivateKeyWif…'); -``` - -**Key Pair from Hex** - `wallet.keyPairFromHex` -*Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site's network selector.* - -Signature: `keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise` - -Parameters: -- `privateKeyHex`: `string` (required) - -- `network`: `NetworkLike` (required) - - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) - -Returns: - -- `Promise` - - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const keyPair = await wallet.keyPairFromHex('c4bb… (64 hex chars)', 'testnet'); -``` - -**Public Key to Address** - `wallet.pubkeyToAddress` -*Convert a compressed public key (hex) to a Dash address. The network comes from the site's network selector.* - -Signature: `pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise` - -Parameters: -- `pubkeyHex`: `string` (required) - -- `network`: `NetworkLike` (required) - - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const address = await wallet.pubkeyToAddress('0378d4…', 'testnet'); -``` - -**Validate Address** - `wallet.validateAddress` -*Check whether a Dash address is valid for a network. The network comes from the site's network selector.* - -Signature: `validateAddress(address: string, network: NetworkLike): Promise` - -Parameters: -- `address`: `string` (required) - -- `network`: `NetworkLike` (required) - - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const isValid = await wallet.validateAddress('yXSSUfQ8AFeWVionamYzedYSf3PANMazbw', 'testnet'); -``` - -**Sign Message** - `wallet.signMessage` -*Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered.* - -Signature: `signMessage(message: string, privateKeyWif: string): Promise` - -Parameters: -- `message`: `string` (required) - -- `privateKeyWif`: `string` (required) - -Returns: - -- `Promise` - -Example: -```javascript -import { wallet } from '@dashevo/evo-sdk'; -const signature = await wallet.signMessage('Hello Dash', 'cYourPrivateKeyWif…'); -``` - ## State Transition Operations ### Pattern @@ -3864,6 +3652,226 @@ identitySigner.addKey(identityPrivateKey); await sdk.addresses.createIdentity({ identity, inputs, identitySigner, addressSigner }); ``` +## Wallet Helpers + +### Pattern +The `wallet` namespace is a package-level export, called without an SDK instance or platform connection — it runs locally in the WebAssembly runtime: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const result = await wallet.(params); +``` + +### Available Wallet Helpers +#### Wallet Utilities + +*Wallet helpers run locally in the WebAssembly runtime and never contact Dash Platform — no connection is created or required. Operations that take a network use the value selected in the site's network selector.* + +**Generate Mnemonic** - `wallet.generateMnemonic` +*Generate a random BIP39 mnemonic seed phrase. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateMnemonic(params?: wasm.GenerateMnemonicParams): Promise` + +Parameters: +- `params`: `wasm.GenerateMnemonicParams` (optional) + - Type declarations: [`wasm.GenerateMnemonicParams`](TYPE_REFERENCE.md#type-generatemnemonicparams) + - `wordCount`: `number` (optional) + - `languageCode`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const mnemonic = await wallet.generateMnemonic({ wordCount: 12 }); +``` + +**Validate Mnemonic** - `wallet.validateMnemonic` +*Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.* + +Signature: `validateMnemonic(mnemonic: string, languageCode?: string): Promise` + +Parameters: +- `mnemonic`: `string` (required) + +- `languageCode`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const isValid = await wallet.validateMnemonic('your seed phrase here …'); +``` + +**Mnemonic to Seed** - `wallet.mnemonicToSeed` +*Derive the 64-byte BIP39 seed (hex-encoded) from a mnemonic seed phrase and optional passphrase.* + +Signature: `mnemonicToSeed(mnemonic: string, passphrase?: string): Promise` + +Parameters: +- `mnemonic`: `string` (required) + +- `passphrase`: `string` (optional) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const seedBytes = await wallet.mnemonicToSeed('your seed phrase here …'); +``` + +**Generate Key Pair** - `wallet.generateKeyPair` +*Generate a random private key with its public key and address. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateKeyPair(network: NetworkLike): Promise` + +Parameters: +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.generateKeyPair('testnet'); +``` + +**Generate Key Pairs** - `wallet.generateKeyPairs` +*Generate multiple random key pairs at once. The network comes from the site's network selector. Generated key material is for testing only — never use it to store real funds.* + +Signature: `generateKeyPairs(network: NetworkLike, count: number): Promise` + +Parameters: +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +- `count`: `number` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPairs = await wallet.generateKeyPairs('testnet', 3); +``` + +**Key Pair from WIF** - `wallet.keyPairFromWif` +*Import a private key from WIF and show its public key and address. The WIF version prefix determines the network, so the site's network selector does not affect this operation.* + +Signature: `keyPairFromWif(privateKeyWif: string): Promise` + +Parameters: +- `privateKeyWif`: `string` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.keyPairFromWif('cYourTestnetPrivateKeyWif…'); +``` + +**Key Pair from Hex** - `wallet.keyPairFromHex` +*Import a raw 32-byte private key (64 hex characters) and show its WIF, public key, and address. The network comes from the site's network selector.* + +Signature: `keyPairFromHex(privateKeyHex: string, network: NetworkLike): Promise` + +Parameters: +- `privateKeyHex`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + - Type declarations: [`wasm.KeyPair`](TYPE_REFERENCE.md#type-keypair) + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const keyPair = await wallet.keyPairFromHex('c4bb… (64 hex chars)', 'testnet'); +``` + +**Public Key to Address** - `wallet.pubkeyToAddress` +*Convert a compressed public key (hex) to a Dash address. The network comes from the site's network selector.* + +Signature: `pubkeyToAddress(pubkeyHex: string, network: NetworkLike): Promise` + +Parameters: +- `pubkeyHex`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const address = await wallet.pubkeyToAddress('0378d4…', 'testnet'); +``` + +**Validate Address** - `wallet.validateAddress` +*Check whether a Dash address is valid for a network. The network comes from the site's network selector.* + +Signature: `validateAddress(address: string, network: NetworkLike): Promise` + +Parameters: +- `address`: `string` (required) + +- `network`: `NetworkLike` (required) + - Type declarations: [`NetworkLike`](TYPE_REFERENCE.md#type-networklike) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const isValid = await wallet.validateAddress('yXSSUfQ8AFeWVionamYzedYSf3PANMazbw', 'testnet'); +``` + +**Sign Message** - `wallet.signMessage` +*Sign an arbitrary message with a private key (WIF) using deterministic ECDSA. The message is signed exactly as entered.* + +Signature: `signMessage(message: string, privateKeyWif: string): Promise` + +Parameters: +- `message`: `string` (required) + +- `privateKeyWif`: `string` (required) + +Returns: + +- `Promise` + +Example: +```javascript +import { wallet } from '@dashevo/evo-sdk'; +const signature = await wallet.signMessage('Hello Dash', 'cYourPrivateKeyWif…'); +``` + ## Common Patterns ### Error Handling diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 6b6db51..0df0c5c 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-09-02T14:17:54.606479 +Timestamp: 2026-09-02T17:05:02.416406 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index dffd438..a705945 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -1417,18 +1417,26 @@ def generate_docs_script() -> str: return textwrap.dedent(script).strip() +def split_package_level_defs(query_defs: dict) -> tuple[dict, dict]: + """Split package-level namespace categories (wallet) out of the query + definitions so they can be rendered as their own top-level docs section + instead of trailing the platform queries.""" + platform = {key: value for key, value in query_defs.items() if key not in PACKAGE_LEVEL_NAMESPACES} + package_level = {key: value for key, value in query_defs.items() if key in PACKAGE_LEVEL_NAMESPACES} + return platform, package_level + + def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: dict) -> str: + platform_query_defs, wallet_defs = split_package_level_defs(query_defs) # Deliberately NOT `item.get('sdk_example') or …` (unlike the AI # reference): docs.html examples are runnable function bodies executed via # `new Function(...)` by the page's Run button, so they must not contain # import statements — sdk_example snippets are standalone modules that do. # Runnable variants live in evo_example_for_query; keep the two in sync # when editing either. - query_sections = collect_sections( - query_defs, - 'queries', - lambda key, item: evo_example_for_query(key, item.get('inputs', [])) - ) + runnable_example = lambda key, item: evo_example_for_query(key, item.get('inputs', [])) + query_sections = collect_sections(platform_query_defs, 'queries', runnable_example) + wallet_sections = collect_sections(wallet_defs, 'queries', runnable_example) transition_sections = collect_sections( transition_defs, 'transitions', @@ -1436,11 +1444,29 @@ def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: d ) sidebar_queries = build_sidebar_entries(query_sections, 'query') + sidebar_wallet = build_sidebar_entries(wallet_sections, 'wallet') sidebar_transitions = build_sidebar_entries(transition_sections, 'transition') query_content = render_categories(query_sections, 'query', '// Evo SDK example', True) if query_sections else '' + wallet_content = render_categories(wallet_sections, 'wallet', '// Evo SDK example', True) if wallet_sections else '' transition_content = render_categories(transition_sections, 'transition', '// Evo SDK example (requires keys/funding)', False) if transition_sections else '' + # Wallet helpers render as their own top-level section below transitions. + sidebar_wallet_block = '' + wallet_section_block = '' + if wallet_sections: + sidebar_wallet_block = f''' +
Wallet Helpers
+
    +{textwrap.indent(sidebar_wallet, ' ')} +
+''' + wallet_section_block = f''' +

Wallet Helpers

+

Package-level helpers (import {{ wallet }} from '@dashevo/evo-sdk') that run locally in the WebAssembly runtime — no SDK instance or platform connection required.

+{wallet_content} +''' + docs_script = generate_docs_script() overview_block = f'''
@@ -1512,7 +1538,7 @@ def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: d
    {textwrap.indent(sidebar_transitions, ' ')}
-
+{sidebar_wallet_block}
+{wallet_section_block} """ @@ -1587,6 +1613,7 @@ def format_ai_example_block(code: str | None, item_key: str) -> str: def generate_ai_reference_md(query_defs: dict, transition_defs: dict, type_metadata: dict) -> str: + platform_query_defs, wallet_defs = split_package_level_defs(query_defs) identity_sample = TESTNET_TEST_DATA['identity_id'] contract_sample = TESTNET_TEST_DATA['data_contract_id'] @@ -1638,10 +1665,6 @@ def generate_ai_reference_md(query_defs: dict, transition_defs: dict, type_metad 'const result = await sdk..(params);', '```', '', - 'Exception: the `wallet` namespace is a package-level export (`import { wallet } from ' - "'@dashevo/evo-sdk'`), called as `wallet.(…)` without an SDK instance or " - 'platform connection.', - '', '### Available Queries', ] @@ -1673,8 +1696,8 @@ def append_param_section(target: List[str], params: List[dict]) -> None: target.append('') - def append_query_sections() -> None: - for cat_key, category in query_defs.items(): + def append_query_sections(definitions: dict) -> None: + for cat_key, category in definitions.items(): queries = category.get('queries') or {} if not queries: continue @@ -1718,7 +1741,7 @@ def append_query_sections() -> None: lines.append('```') lines.append('') - append_query_sections() + append_query_sections(platform_query_defs) lines.extend([ '## State Transition Operations', @@ -1773,6 +1796,22 @@ def append_query_sections() -> None: lines.append('```') lines.append('') + if wallet_defs: + lines.extend([ + '## Wallet Helpers', + '', + '### Pattern', + 'The `wallet` namespace is a package-level export, called without an SDK instance ' + 'or platform connection — it runs locally in the WebAssembly runtime:', + '```javascript', + "import { wallet } from '@dashevo/evo-sdk';", + 'const result = await wallet.(params);', + '```', + '', + '### Available Wallet Helpers', + ]) + append_query_sections(wallet_defs) + lines.extend([ '## Common Patterns', '', From 7e2e552f7e718950d10f09fdb6a1c4df76d213b5 Mon Sep 17 00:00:00 2001 From: thephez Date: Thu, 10 Sep 2026 17:26:19 -0400 Subject: [PATCH 4/4] test(wallet): cover mnemonic language and message whitespace --- public/AI_REFERENCE.md | 2 +- public/api-definitions.json | 2 +- public/documentation-check-report.txt | 2 +- public/sdk-operation-catalog.json | 2 +- public/src/definitions-data.js | 2 +- tests/e2e/fixtures/test-data.js | 13 ++++++- tests/e2e/wallet/wallet-operations.spec.js | 41 ++++++++++++++++++++++ 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 5e136bc..0397744 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -3688,7 +3688,7 @@ const mnemonic = await wallet.generateMnemonic({ wordCount: 12 }); ``` **Validate Mnemonic** - `wallet.validateMnemonic` -*Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.* +*Check whether a BIP39 mnemonic seed phrase is valid. Select the matching language for non-English wordlists; the default check does not detect every language.* Signature: `validateMnemonic(mnemonic: string, languageCode?: string): Promise` diff --git a/public/api-definitions.json b/public/api-definitions.json index 18eaf37..b31b35c 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -1880,7 +1880,7 @@ }, "walletValidateMnemonic": { "label": "Validate Mnemonic", - "description": "Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.", + "description": "Check whether a BIP39 mnemonic seed phrase is valid. Select the matching language for non-English wordlists; the default check does not detect every language.", "inputs": [ { "name": "mnemonic", diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 0df0c5c..b90dff3 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-09-02T17:05:02.416406 +Timestamp: 2026-09-10T16:27:14.409649 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/public/sdk-operation-catalog.json b/public/sdk-operation-catalog.json index 4a4c63c..9d787b5 100644 --- a/public/sdk-operation-catalog.json +++ b/public/sdk-operation-catalog.json @@ -2978,7 +2978,7 @@ "category": "wallet", "sdkMethod": "wallet.validateMnemonic", "label": "Validate Mnemonic", - "description": "Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.", + "description": "Check whether a BIP39 mnemonic seed phrase is valid. Select the matching language for non-English wordlists; the default check does not detect every language.", "disabled": null, "signature": "validateMnemonic(mnemonic: string, languageCode?: string): Promise", "parameters": [ diff --git a/public/src/definitions-data.js b/public/src/definitions-data.js index 3005e15..b6f8ee2 100644 --- a/public/src/definitions-data.js +++ b/public/src/definitions-data.js @@ -170,7 +170,7 @@ export const WALLET_CATEGORY_DEFINITIONS = { }, walletValidateMnemonic: { label: 'Validate Mnemonic', - description: 'Check whether a BIP39 mnemonic seed phrase is valid. Without a language, all supported wordlists are tried.', + description: 'Check whether a BIP39 mnemonic seed phrase is valid. Select the matching language for non-English wordlists; the default check does not detect every language.', inputs: [ { name: 'mnemonic', label: 'Mnemonic Seed Phrase', type: 'password', required: true }, { name: 'languageCode', label: 'Language', type: 'select', options: WALLET_LANGUAGE_OPTIONS }, diff --git a/tests/e2e/fixtures/test-data.js b/tests/e2e/fixtures/test-data.js index 1ec8b96..0169c0b 100644 --- a/tests/e2e/fixtures/test-data.js +++ b/tests/e2e/fixtures/test-data.js @@ -1090,7 +1090,18 @@ const testData = { signMessage: { message: 'Hello Dash', // Deterministic ECDSA (RFC 6979) compact signature, hex-encoded - expectedSignature: 'b8cc0d9b97f6c7e5c044dd777c7cdd891ac335b33d96ad7c8fc2a7a91038d8a46451cfffdbf57b2579c0f6ef91925526d30fe0d7d20f558c28b8defec40878fb' + expectedSignature: 'b8cc0d9b97f6c7e5c044dd777c7cdd891ac335b33d96ad7c8fc2a7a91038d8a46451cfffdbf57b2579c0f6ef91925526d30fe0d7d20f558c28b8defec40878fb', + // The same message with surrounding whitespace signs to a DIFFERENT + // value: whitespace is part of the signed data and must not be trimmed. + paddedMessage: ' Hello Dash ', + expectedPaddedSignature: '468989af5298c9fff9d1b476889d56298bf01aaa9839e68ae8a1533c250dab78029d30faaee09227f8f0d2aceaaca7b1514e29f6319fa609314d5ea1af2b626b' + }, + // Spanish (es) BIP39 wordlist fixtures. validateMnemonic without a + // language uses parse_normalized, which does NOT accept this phrase — + // the language code must be supplied explicitly. + spanish: { + languageCode: 'es', + mnemonic: 'ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco abierto' } }, diff --git a/tests/e2e/wallet/wallet-operations.spec.js b/tests/e2e/wallet/wallet-operations.spec.js index 72e0908..f7e7140 100644 --- a/tests/e2e/wallet/wallet-operations.spec.js +++ b/tests/e2e/wallet/wallet-operations.spec.js @@ -164,6 +164,47 @@ test.describe('Wallet operations', () => { expect(result.replace(/^"|"$/g, '').trim()).toBe(wallet.signMessage.expectedSignature); }); + test('generateMnemonic and validateMnemonic honour a non-English wordlist', async () => { + // The language dropdown's option values must match the codes the wasm + // runtime accepts; an unsupported code is a hard error there, so this + // guards the dropdown set against drift from the SDK. + const generated = await runWalletOperation(evoSdkPage, 'walletGenerateMnemonic', { + wordCount: '12', + languageCode: wallet.spanish.languageCode + }); + expect(generated.hasError).toBe(false); + expect(generated.result.replace(/^"|"$/g, '').trim().split(/\s+/)).toHaveLength(12); + + // A known Spanish phrase validates under 'es' but not under 'en'. + const asSpanish = await runWalletOperation(evoSdkPage, 'walletValidateMnemonic', { + mnemonic: wallet.spanish.mnemonic, + languageCode: wallet.spanish.languageCode + }); + expect(asSpanish.hasError).toBe(false); + expect(asSpanish.result.trim()).toBe('true'); + + const asEnglish = await runWalletOperation(evoSdkPage, 'walletValidateMnemonic', { + mnemonic: wallet.spanish.mnemonic, + languageCode: 'en' + }); + expect(asEnglish.hasError).toBe(false); + expect(asEnglish.result.trim()).toBe('false'); + }); + + test('signMessage signs surrounding whitespace as part of the message', async () => { + // Proves the whole chain — textarea -> collectArgs -> callEvo -> wasm — + // preserves whitespace: the padded message must produce a different, + // specific signature rather than the trimmed message's. + const { result, hasError } = await runWalletOperation(evoSdkPage, 'walletSignMessage', { + message: wallet.signMessage.paddedMessage, + privateKeyWif: wallet.testnet.privateKeyWif + }); + expect(hasError).toBe(false); + const signature = result.replace(/^"|"$/g, '').trim(); + expect(signature).toBe(wallet.signMessage.expectedPaddedSignature); + expect(signature).not.toBe(wallet.signMessage.expectedSignature); + }); + test('wallet UI shows no proof toggle and no authentication section', async ({ page }) => { await evoSdkPage.setupQuery(CATEGORY, 'walletGenerateKeyPair', {}, { operationType: 'wallet' }); await expect(page.locator('#proofToggleContainer')).toBeHidden();