From c573a10d993ae2202a657724ab1c44e6b4eea27c Mon Sep 17 00:00:00 2001 From: quantum Date: Tue, 29 Jul 2025 18:17:16 -0500 Subject: [PATCH 1/2] feat: migrate js-dash-sdk to use wasm-sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace DPP with wasm-sdk for all platform methods - Add WasmPlatformAdapter for wasm-sdk integration - Update all identity, document, contract, and name methods - Add compatibility layer for platform test suite - Fix createAssetLockProof to handle undefined dpp - Document test suite issues and findings 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 21 +- packages/js-dash-sdk/package.json | 25 ++ .../src/SDK/Client/Platform/Platform.ts | 143 +++++---- .../Platform/adapters/WasmPlatformAdapter.ts | 286 ++++++++++++++++++ .../Platform/adapters/wasm-sdk-loader.js | 16 + .../Platform/broadcastStateTransition.ts | 46 +++ .../Client/Platform/methods/contracts/get.ts | 45 ++- .../Platform/methods/contracts/history.ts | 43 +++ .../Platform/methods/contracts/publish.ts | 38 +++ .../Platform/methods/contracts/update.ts | 47 +++ .../Platform/methods/documents/broadcast.ts | 124 ++++++++ .../Client/Platform/methods/documents/get.ts | 79 ++++- .../methods/identities/creditTransfer.ts | 37 ++- .../methods/identities/creditWithdrawal.ts | 81 ++++- .../Client/Platform/methods/identities/get.ts | 29 +- .../Platform/methods/identities/register.ts | 107 ++++++- .../Platform/methods/identities/topUp.ts | 35 ++- .../Platform/methods/identities/update.ts | 59 +++- .../Client/Platform/methods/names/register.ts | 37 +++ .../Platform/methods/names/resolveByRecord.ts | 33 ++ .../Client/Platform/methods/names/search.ts | 24 ++ .../SDK/Client/Platform/types/wasm-sdk.d.ts | 121 ++++++++ .../WASM_SDK_TEST_ISSUES.md | 88 ++++++ .../lib/test/createClientWithFundedWallet.js | 1 + .../lib/test/createFaucetClient.js | 1 + .../lib/test/wasm-sdk-compat.js | 91 ++++++ 26 files changed, 1573 insertions(+), 84 deletions(-) create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/adapters/wasm-sdk-loader.js create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/types/wasm-sdk.d.ts create mode 100644 packages/platform-test-suite/WASM_SDK_TEST_ISSUES.md create mode 100644 packages/platform-test-suite/lib/test/wasm-sdk-compat.js diff --git a/CLAUDE.md b/CLAUDE.md index 2c6a306e558..15903662d64 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -127,4 +127,23 @@ Platform uses data contracts to define application data schemas: - **Serialization**: Custom serialization with `rs-platform-serialization` - **Value Handling**: `rs-platform-value` for cross-language data representation - **Proof Verification**: `rs-drive-proof-verifier` for cryptographic proofs -- **State Transitions**: Documents and data contracts use state transitions for updates \ No newline at end of file +- **State Transitions**: Documents and data contracts use state transitions for updates + +### Platform Test Suite Issues (WASM SDK Integration) + +When running the platform test suite after wasm-sdk integration: + +1. **Faucet Wallet Setup**: The faucet wallet needs funded UTXOs. Generate blocks to the faucet address: + ```bash + yarn dashmate core cli "generatetoaddress 100 yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5" + ``` + +2. **Known Issues**: + - "utxosList must contain at least 1 utxo" - This is a wallet sync timing issue, not a lack of funds + - ES module loading errors - Some tests fail to load wasm-sdk properly + - See `packages/platform-test-suite/WASM_SDK_TEST_ISSUES.md` for detailed troubleshooting + +3. **Test Environment**: + - Requires running local dashmate network + - Uses `.env` file for configuration (FAUCET_1_ADDRESS, FAUCET_1_PRIVATE_KEY, etc.) + - Bootstrap.js maps FAUCET_1_* vars to FAUCET_* based on worker ID \ No newline at end of file diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 5651d049454..65593770147 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -6,11 +6,33 @@ "unpkg": "dist/dash.min.js", "browser": "dist/dash.min.js", "types": "build/index.d.ts", + "exports": { + ".": { + "import": "./build/index.js", + "require": "./build/index.js", + "types": "./build/index.d.ts" + }, + "./core": { + "import": "./build/index.core.js", + "require": "./build/index.core.js", + "types": "./build/index.core.d.ts" + }, + "./platform": { + "import": "./build/index.platform.js", + "require": "./build/index.platform.js", + "types": "./build/index.platform.d.ts" + }, + "./build/*": "./build/*" + }, "scripts": { "start:dev": "nodemon --exec 'yarn run build && yarn run test:unit'", "start:ts": "tsc -p tsconfig.build.json --watch", "build": "yarn run build:ts && webpack --stats-error-details", "build:ts": "tsc -p tsconfig.build.json", + "build:full": "yarn run build:ts && webpack --config webpack.config.js --env target=full", + "build:core": "yarn run build:ts && webpack --config webpack.config.js --env target=core", + "build:platform": "yarn run build:ts && webpack --config webpack.config.js --env target=platform", + "build:all": "yarn run build:ts && webpack --config webpack.config.js", "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "yarn run test:types && yarn run test:unit && yarn run test:browsers", @@ -108,5 +130,8 @@ "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^4.9.1" + }, + "optionalDependencies": { + "@dashevo/wasm-sdk": "file:../wasm-sdk/pkg" } } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts index 873fe62c53d..95f90b061f4 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts @@ -1,9 +1,8 @@ -import loadWasmDpp, { DashPlatformProtocol, getLatestProtocolVersion } from '@dashevo/wasm-dpp'; -import type { DPPModule } from '@dashevo/wasm-dpp'; import crypto from 'crypto'; import Client from '../Client'; import { IStateTransitionResult } from './IStateTransitionResult'; +import { WasmPlatformAdapter } from './adapters/WasmPlatformAdapter'; import createAssetLockTransaction from './createAssetLockTransaction'; @@ -48,6 +47,7 @@ export interface PlatformOpts { client: Client, network: string, driveProtocolVersion?: number, + enablePlatform?: boolean, } /** @@ -107,41 +107,43 @@ interface DataContracts { * @param contracts - contracts */ export class Platform { - // TODO: Address in further type system improvements - // Do we want to refactor all methods to check - // whether dpp is initialized instead of ts-ignoring? - // @ts-ignore - dpp: DashPlatformProtocol; - + // WASM SDK adapter for platform operations + private adapter?: WasmPlatformAdapter; + // Direct access to wasm-sdk instance for method delegation + public wasmSdk?: any; + + // Legacy DPP - will be removed once migration is complete + dpp?: any; + protocolVersion?: number; public documents: Records; /** - * @param {Function} get - get identities from the platform - * @param {Function} register - register identities on the platform - */ + * @param {Function} get - get identities from the platform + * @param {Function} register - register identities on the platform + */ public identities: Identities; /** - * @param {Function} get - get names from the platform - * @param {Function} register - register names on the platform - */ + * @param {Function} get - get names from the platform + * @param {Function} register - register names on the platform + */ public names: DomainNames; /** - * @param {Function} get - get contracts from the platform - * @param {Function} create - create contracts which can be broadcasted - * @param {Function} register - register contracts on the platform - */ + * @param {Function} get - get contracts from the platform + * @param {Function} create - create contracts which can be broadcasted + * @param {Function} register - register contracts on the platform + */ public contracts: DataContracts; public logger: ConfigurableLogger; /** - * Broadcasts state transition - * @param {Object} stateTransition - */ + * Broadcasts state transition + * @param {Object} stateTransition + */ public broadcastStateTransition(stateTransition: any): Promise { return broadcastStateTransition(this, stateTransition); } @@ -156,12 +158,17 @@ export class Platform { public nonceManager: NonceManager; + private platformEnabled: boolean; + /** - * Construct some instance of Platform - * - * @param {PlatformOpts} options - options for Platform - */ + * Construct some instance of Platform + * + * @param {PlatformOpts} options - options for Platform + */ constructor(options: PlatformOpts) { + // Platform functionality can be disabled for core-only builds + this.platformEnabled = options.enablePlatform !== false; + this.documents = { broadcast: broadcastDocument.bind(this), create: createDocument.bind(this), @@ -207,45 +214,69 @@ export class Platform { this.fetcher = new Fetcher(this.client.getDAPIClient()); this.nonceManager = new NonceManager(this.client.getDAPIClient()); + + // Initialize adapter if platform is enabled + if (this.platformEnabled) { + this.adapter = new WasmPlatformAdapter( + this.client.getDAPIClient(), + this.client.network, + true // proofs enabled by default + ); + // Set the platform reference in the adapter + this.adapter.setPlatform(this); + } } async initialize() { - if (!this.dpp) { - await Platform.initializeDppModule(); - - if (this.protocolVersion === undefined) { - // use mapped protocol version otherwise - // fallback to one that set in dpp as the last option - - const mappedProtocolVersion = Platform.networkToProtocolVersion.get( - this.client.network, - ); + if (!this.platformEnabled) { + throw new Error('Platform functionality is disabled. Use full SDK build or enable platform.'); + } - this.protocolVersion = mappedProtocolVersion !== undefined - ? mappedProtocolVersion : getLatestProtocolVersion(); + if (!this.wasmSdk && this.adapter) { + try { + await this.adapter.initialize(); + this.wasmSdk = await this.adapter.getSdk(); + + // Set protocol version if not already set + if (this.protocolVersion === undefined) { + const mappedProtocolVersion = Platform.networkToProtocolVersion.get( + this.client.network, + ); + + this.protocolVersion = mappedProtocolVersion !== undefined + ? mappedProtocolVersion : 1; // Default to 1 + } + } catch (error) { + this.logger.error('Failed to initialize wasm-sdk:', error); + throw error; } + } + } - // eslint-disable-next-line + /** + * Get the platform adapter for advanced usage + */ + getAdapter(): WasmPlatformAdapter | undefined { + return this.adapter; + } - this.dpp = new DashPlatformProtocol( - { - generate: () => crypto.randomBytes(32), - }, - this.protocolVersion, - ); + /** + * Dispose of platform resources + */ + async dispose(): Promise { + if (this.adapter) { + await this.adapter.dispose(); } + this.wasmSdk = undefined; } - // Explicitly provide DPPModule as return type. - // If we don't do it, typescript behaves weird and in compiled Platform.d.ts - // this code looks like this. - // - // ``` - // static initializeDppModule(): Promise; - // ``` - // - // Slash is missing before `dist` and TS compilation in consumers is breaking - static async initializeDppModule(): Promise { - return loadWasmDpp(); + /** + * Legacy method for backward compatibility during migration + * Will be removed once migration is complete + */ + static async initializeDppModule(): Promise { + // This is now a no-op as we use wasm-sdk + console.warn('Platform.initializeDppModule() is deprecated. Platform now uses wasm-sdk.'); + return {}; } -} +} \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts new file mode 100644 index 00000000000..85350b1add6 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts @@ -0,0 +1,286 @@ +import { DAPIClient } from '@dashevo/dapi-client'; + +// Import type definitions +/// + +// Type definitions for wasm-sdk module +interface WasmSdkModule { + default: () => Promise; + WasmSdk: any; // Will be properly typed when wasm-sdk is available +} + +export class WasmPlatformAdapter { + private wasmSdk?: WasmSdkModule; + private sdkInstance?: any; + private initialized = false; + private dapiClient: DAPIClient; + private network: string; + private proofs: boolean; + private platform?: any; // Reference to Platform instance + + // Cache for frequently accessed data + private cache: Map = new Map(); + private cacheTTL = 60000; // 60 seconds default TTL + + constructor(dapiClient: DAPIClient, network: string, proofs: boolean = true) { + this.dapiClient = dapiClient; + this.network = network; + this.proofs = proofs; + } + + /** + * Set the platform instance for response conversion + */ + setPlatform(platform: any): void { + this.platform = platform; + } + + /** + * Initialize the WASM SDK module dynamically + */ + async initialize(): Promise { + if (this.initialized) { + return; + } + + try { + // Use loader for proper ES module handling in Node.js + // @ts-ignore - Using JS loader + const { loadWasmSdk } = require('./wasm-sdk-loader'); + const wasmModule = await loadWasmSdk() as WasmSdkModule; + + // Initialize WASM module + await wasmModule.default(); + + this.wasmSdk = wasmModule; + this.initialized = true; + } catch (error) { + throw new Error(`Failed to initialize wasm-sdk: ${error.message}`); + } + } + + /** + * Get or create SDK instance + */ + async getSdk(): Promise { + if (!this.initialized) { + await this.initialize(); + } + + if (!this.sdkInstance) { + const transport: any = { + url: this.dapiClient.dapiAddresses[0].toString(), + network: this.network, + }; + + this.sdkInstance = await this.wasmSdk!.WasmSdk.new(transport, this.proofs); + } + + return this.sdkInstance; + } + + /** + * Convert js-dash-sdk asset lock to wasm-sdk format + */ + convertAssetLockProof(assetLock: any): string { + // Convert asset lock from js-dash-sdk format to wasm-sdk hex format + const proofObject = { + type: assetLock.type || 0, + transaction: assetLock.transaction.toString('hex'), + outputIndex: assetLock.outputIndex, + instantLock: assetLock.instantLock ? assetLock.instantLock.toString('hex') : undefined, + }; + + return JSON.stringify(proofObject); + } + + /** + * Convert private key to WIF format + */ + convertPrivateKeyToWIF(privateKey: any): string { + // If already WIF, return as is + if (typeof privateKey === 'string' && (privateKey.startsWith('X') || privateKey.startsWith('7'))) { + return privateKey; + } + + // Convert PrivateKey object to WIF + if (privateKey && typeof privateKey.toWIF === 'function') { + return privateKey.toWIF(); + } + + throw new Error('Invalid private key format'); + } + + /** + * Convert wasm-sdk response to js-dash-sdk format + */ + convertResponse(wasmResponse: any, responseType: string): any { + // This will be extended as we implement more methods + switch (responseType) { + case 'identity': + return this.convertIdentityResponse(wasmResponse); + case 'document': + return this.convertDocumentResponse(wasmResponse); + case 'dataContract': + return this.convertDataContractResponse(wasmResponse); + case 'stateTransition': + return this.convertStateTransitionResponse(wasmResponse); + default: + return wasmResponse; + } + } + + private convertIdentityResponse(wasmIdentity: any): any { + // Convert wasm-sdk identity to js-dash-sdk Identity instance + if (!wasmIdentity) return null; + + // If the platform has DPP available, use it to create proper Identity instance + if (this.platform && this.platform.dpp) { + try { + // Convert from wasm-sdk format to js-dash-sdk Identity + const identityData = { + protocolVersion: wasmIdentity.protocolVersion || 1, + id: wasmIdentity.id, + publicKeys: wasmIdentity.publicKeys || [], + balance: wasmIdentity.balance || 0, + revision: wasmIdentity.revision || 0, + }; + + return this.platform.dpp.identity.create( + identityData.id, + identityData.publicKeys, + identityData.balance, + identityData.revision + ); + } catch (e) { + // Fallback to raw object if DPP creation fails + return wasmIdentity; + } + } + + return wasmIdentity; + } + + private convertDocumentResponse(wasmDocument: any): any { + // Convert wasm-sdk document to js-dash-sdk Document instance + if (!wasmDocument) return null; + + // If the platform has DPP available, use it to create proper Document instance + if (this.platform && this.platform.dpp) { + try { + // For now, return the raw document as the platform will handle conversion + // when it has the data contract context + return wasmDocument; + } catch (e) { + // Fallback to raw object + return wasmDocument; + } + } + + return wasmDocument; + } + + private convertDataContractResponse(wasmContract: any): any { + // Convert wasm-sdk data contract to js-dash-sdk DataContract instance + if (!wasmContract) return null; + + // If the platform has DPP available, use it to create proper DataContract instance + if (this.platform && this.platform.dpp) { + try { + // Create data contract from the wasm response + const contractData = { + protocolVersion: wasmContract.protocolVersion || 1, + $id: wasmContract.id, + $schema: wasmContract.schema || 'https://schema.dash.org/dpp-0-4-0/meta/data-contract', + version: wasmContract.version || 1, + ownerId: wasmContract.ownerId, + documents: wasmContract.documents || wasmContract.documentSchemas || {}, + }; + + return this.platform.dpp.dataContract.createFromObject(contractData); + } catch (e) { + // Fallback to raw object if DPP creation fails + return wasmContract; + } + } + + return wasmContract; + } + + private convertStateTransitionResponse(wasmResponse: any): any { + // Convert wasm-sdk state transition response + return { + success: wasmResponse.success, + data: wasmResponse.data, + error: wasmResponse.error, + }; + } + + /** + * Get cached data if available and not expired + */ + private getCached(key: string): any | null { + const cached = this.cache.get(key); + if (cached && Date.now() - cached.timestamp < this.cacheTTL) { + return cached.data; + } + // Remove expired entry + if (cached) { + this.cache.delete(key); + } + return null; + } + + /** + * Set cache data + */ + private setCache(key: string, data: any): void { + this.cache.set(key, { + data, + timestamp: Date.now(), + }); + } + + /** + * Clear all cache + */ + clearCache(): void { + this.cache.clear(); + } + + /** + * Set cache TTL + */ + setCacheTTL(ttl: number): void { + this.cacheTTL = ttl; + } + + /** + * Cached query wrapper + */ + async cachedQuery(key: string, queryFn: () => Promise): Promise { + // Check cache first + const cached = this.getCached(key); + if (cached !== null) { + return cached; + } + + // Execute query and cache result + const result = await queryFn(); + if (result !== null && result !== undefined) { + this.setCache(key, result); + } + + return result; + } + + /** + * Clean up resources + */ + async dispose(): Promise { + this.sdkInstance = undefined; + this.wasmSdk = undefined; + this.initialized = false; + this.cache.clear(); + } +} \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/wasm-sdk-loader.js b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/wasm-sdk-loader.js new file mode 100644 index 00000000000..4bc38c2bed5 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/wasm-sdk-loader.js @@ -0,0 +1,16 @@ +/** + * Loader for wasm-sdk ES module in CommonJS environment + * This file must remain as .js to use real dynamic import + */ + +async function loadWasmSdk() { + try { + // Use actual dynamic import, not transpiled version + const wasmModule = await import('@dashevo/wasm-sdk'); + return wasmModule; + } catch (error) { + throw new Error(`Failed to load wasm-sdk: ${error.message}`); + } +} + +module.exports = { loadWasmSdk }; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts index 2dce38a116e..9e6b77d5e8d 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts @@ -26,6 +26,52 @@ export default async function broadcastStateTransition( ): Promise { const { client } = platform; + // If wasm-sdk is available, delegate to it + if (platform.wasmSdk && platform.getAdapter()) { + platform.logger.debug('[broadcastStateTransition] Using wasm-sdk to broadcast state transition'); + + // Convert state transition to hex string + const stateTransitionHex = stateTransition.toBuffer().toString('hex'); + + try { + // Call wasm-sdk broadcastStateTransition + const result = await platform.wasmSdk.broadcastStateTransition(stateTransitionHex); + + // Wait for the result + const hash = crypto.createHash('sha256') + .update(stateTransition.toBuffer()) + .digest(); + + const stateTransitionResult = await platform.wasmSdk.waitForStateTransitionResult( + hash.toString('hex') + ); + + if (stateTransitionResult.error) { + const error = stateTransitionResult.error; + throw new StateTransitionBroadcastError( + error.code || 0, + error.message || 'Unknown error', + error + ); + } + + platform.logger.debug('[broadcastStateTransition] State transition broadcasted successfully via wasm-sdk'); + + return stateTransitionResult; + } catch (error) { + if (error instanceof StateTransitionBroadcastError) { + throw error; + } + + // Wrap other errors + throw new StateTransitionBroadcastError( + error.code || 0, + error.message || 'Broadcast failed', + error + ); + } + } + // TODO(versioning): restore // @ts-ignore // if (!options.skipValidation) { diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/get.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/get.ts index 1a08b3d78a0..a51bed2edbd 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/get.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/get.ts @@ -28,6 +28,49 @@ export async function get(this: Platform, identifier: ContractIdentifier): Promi } } + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + const contractIdString = contractId.toString(); + const cacheKey = `dataContract:${contractIdString}`; + + try { + // Use cached query for better performance + const result = await adapter.cachedQuery(cacheKey, async () => { + const sdk = await adapter.getSdk(); + // data_contract_fetch is a standalone function from wasm-sdk + const wasmAdapter = adapter as any; + return await wasmAdapter.wasmSdk.data_contract_fetch(sdk, contractIdString); + }); + + if (!result) { + return null; + } + + // Convert wasm-sdk response to js-dash-sdk format + const contract = adapter.convertResponse(result, 'dataContract'); + + // Store contract to the cache + // eslint-disable-next-line + for (const appName of this.client.getApps().getNames()) { + const appDefinition = this.client.getApps().get(appName); + if (appDefinition.contractId.equals(contractId)) { + appDefinition.contract = contract; + } + } + + this.logger.debug(`[Contracts#get] Obtained Data Contract "${identifier}"`); + + return contract; + } catch (e) { + if (e.message?.includes('not found') || e.message?.includes('does not exist')) { + return null; + } + throw e; + } + } + + // Legacy implementation - will be removed once migration is complete // Fetch contract otherwise let dataContractResponse: GetDataContractResponse; try { @@ -58,4 +101,4 @@ export async function get(this: Platform, identifier: ContractIdentifier): Promi return contract; } -export default get; +export default get; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/history.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/history.ts index 55d2d9985ef..0d94ab24ef2 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/history.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/history.ts @@ -29,6 +29,49 @@ export async function history( const contractId : Identifier = Identifier.from(identifier); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + try { + this.logger.debug(`[Contracts#history] Calling wasm-sdk getDataContractHistory`); + + // Call wasm-sdk getDataContractHistory + const result = await this.wasmSdk.getDataContractHistory( + contractId.toString(), + startAtMs, + limit, + offset + ); + + if (!result) { + return null; + } + + // Convert the response to the expected format + const contractHistory: { [key: number]: DataContract } = {}; + + if (result.entries && Array.isArray(result.entries)) { + for (const entry of result.entries) { + if (entry.date && entry.value) { + // Convert wasm-sdk data contract to js-dash-sdk format + const dataContract = adapter.convertResponse(entry.value, 'dataContract'); + contractHistory[Number(entry.date)] = dataContract; + } + } + } + + this.logger.debug(`[Contracts#history] Obtained Data Contract history for "${identifier}" via wasm-sdk`); + + return contractHistory; + } catch (e) { + if (e.message?.includes('not found') || e.message?.includes('does not exist')) { + return null; + } + throw e; + } + } + let dataContractHistoryResponse: GetDataContractHistoryResponse; try { dataContractHistoryResponse = await this.fetcher diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/publish.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/publish.ts index 7eb12f7c053..41a52f341d8 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/publish.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/publish.ts @@ -19,6 +19,44 @@ export default async function publish( this.logger.debug(`[Contracts#publish] publish data contract ${dataContract.getId()}`); await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get identity private key for signing + const account = await this.client.getWalletAccount(); + + // Get the key for data contract operations (index 2) + const { privateKey: contractPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 2); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(contractPrivateKey); + + // Convert identity to hex format + const identityHex = identity.toBuffer().toString('hex'); + + // Convert data contract to JSON + const dataContractJson = JSON.stringify(dataContract.toJSON()); + + this.logger.debug(`[Contracts#publish] Calling wasm-sdk dataContractCreate`); + + // Call wasm-sdk dataContractCreate + const result = await this.wasmSdk.dataContractCreate( + dataContractJson, + identityHex, + privateKeyWIF + ); + + // Acknowledge identifier to handle retry attempts + this.fetcher.acknowledgeIdentifier(dataContract.getId()); + + this.logger.debug(`[Contracts#publish] Published data contract ${dataContract.getId()} via wasm-sdk`); + + // Return the result as a DataContractCreateTransition + return result as DataContractCreateTransition; + } + const { dpp } = this; const dataContractCreateTransition = dpp.dataContract diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/update.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/update.ts index 7da552988ba..439b8f43860 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/update.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/contracts/update.ts @@ -18,6 +18,53 @@ export default async function update( this.logger.debug(`[DataContract#update] Update data contract ${dataContract.getId()}`); await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get identity private key for signing + const account = await this.client.getWalletAccount(); + + // Get the key for data contract operations (index 2) + const { privateKey: contractPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 2); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(contractPrivateKey); + + // Convert identity to hex format + const identityHex = identity.toBuffer().toString('hex'); + + // Clone and increment version + const updatedDataContract = dataContract.clone(); + updatedDataContract.incrementVersion(); + + // Convert updated data contract to JSON + const dataContractJson = JSON.stringify(updatedDataContract.toJSON()); + + this.logger.debug(`[DataContract#update] Calling wasm-sdk dataContractUpdate`); + + // Call wasm-sdk dataContractUpdate + const result = await this.wasmSdk.dataContractUpdate( + dataContractJson, + identityHex, + privateKeyWIF + ); + + // Update app with updated data contract if available + // eslint-disable-next-line + for (const appName of this.client.getApps().getNames()) { + const appDefinition = this.client.getApps().get(appName); + if (appDefinition.contractId.equals(updatedDataContract.getId()) && appDefinition.contract) { + appDefinition.contract = updatedDataContract; + } + } + + this.logger.debug(`[DataContract#update] Updated data contract ${dataContract.getId()} via wasm-sdk`); + + return result; + } + const { dpp } = this; // Clone contract diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts index 4a6a7a06026..07b9f06ae63 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts @@ -29,6 +29,130 @@ export default async function broadcast( }); await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get identity private key for signing + const account = await this.client.getWalletAccount(); + + // Get the key for document operations (index 1) + const { privateKey: documentPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 1); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(documentPrivateKey); + + // Convert identity to hex format + const identityHex = identity.toBuffer().toString('hex'); + + // Process documents for each operation type + const results: any[] = []; + + // Handle document creation/replacement + if (documents.create || documents.replace) { + const documentsToUpsert = [ + ...(documents.create || []), + ...(documents.replace || []) + ]; + + if (documentsToUpsert.length > 0) { + // Group by data contract and document type + const groupedDocs = new Map(); + + for (const doc of documentsToUpsert) { + const key = `${doc.getDataContractId().toString()}_${doc.getType()}`; + if (!groupedDocs.has(key)) { + groupedDocs.set(key, []); + } + groupedDocs.get(key)!.push(doc); + } + + // Process each group + for (const [key, docs] of groupedDocs) { + const [dataContractId, documentType] = key.split('_'); + + // Convert documents to JSON + const documentsData = docs.map(doc => ({ + id: doc.getId()?.toString(), + data: doc.getData(), + createdAt: doc.getCreatedAt(), + updatedAt: doc.getUpdatedAt(), + })); + + const documentsJson = JSON.stringify(documentsData); + const putType = documents.replace?.includes(docs[0]) ? 'replace' : 'create'; + + this.logger.debug(`[Document#broadcast] Calling wasm-sdk documentsPut for ${documentType}`); + + const result = await this.wasmSdk.documentsPut( + dataContractId, + documentType, + documentsJson, + identityHex, + privateKeyWIF, + putType + ); + + results.push(result); + } + } + } + + // Handle document deletion + if (documents.delete && documents.delete.length > 0) { + // Group by data contract and document type + const groupedDeletes = new Map(); + + for (const doc of documents.delete) { + const key = `${doc.getDataContractId().toString()}_${doc.getType()}`; + if (!groupedDeletes.has(key)) { + groupedDeletes.set(key, []); + } + groupedDeletes.get(key)!.push(doc.getId().toString()); + } + + // Process each group + for (const [key, docIds] of groupedDeletes) { + const [dataContractId, documentType] = key.split('_'); + + this.logger.debug(`[Document#broadcast] Calling wasm-sdk documentsDelete for ${documentType}`); + + const result = await this.wasmSdk.documentsDelete( + dataContractId, + documentType, + docIds, + identityHex, + privateKeyWIF + ); + + results.push(result); + } + } + + // Handle document acknowledgment for cache + if (documents.create) { + documents.create.forEach((document) => { + const documentLocator = `${document.getDataContractId().toString()}/${document.getType()}`; + this.fetcher.acknowledgeKey(documentLocator); + }); + } + + if (documents.delete) { + documents.delete.forEach((document) => { + const documentLocator = `${document.getDataContractId().toString()}/${document.getType()}`; + this.fetcher.forgetKey(documentLocator); + }); + } + + this.logger.debug('[Document#broadcast] Broadcasted documents via wasm-sdk', { + results: results.length, + }); + + // Return the results + return results; + } + const { dpp } = this; const identityId = identity.getId(); diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/get.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/get.ts index 1b1f7d06635..a41d5867c4c 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/get.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/get.ts @@ -96,6 +96,83 @@ export async function get(this: Platform, typeLocator: string, opts: QueryOption throw new Error(`Missing contract ID for ${appName}`); } + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + try { + // Convert where conditions to wasm-sdk format + let whereClause: string | undefined; + if (opts.where) { + // Ensure app contract is fetched for binary properties + await ensureAppContractFetched.call(this, appName); + const binaryProperties = appDefinition.contract.getBinaryProperties(fieldType); + + const convertedWhere = opts.where.map((whereCondition) => + convertIdentifierProperties(whereCondition, binaryProperties) + ); + + // Convert to JSON string for wasm-sdk + whereClause = JSON.stringify(convertedWhere); + } + + // Convert orderBy to wasm-sdk format + let orderByClause: string | undefined; + if (opts.orderBy) { + orderByClause = JSON.stringify(opts.orderBy); + } + + // Convert startAt/startAfter + let startAt = opts.startAt; + let startAfter = opts.startAfter; + + if (startAt instanceof ExtendedDocument) { + startAt = startAt.getId().toString(); + } else if (typeof startAt === 'string') { + startAt = Identifier.from(startAt).toString(); + } else if (startAt && typeof startAt === 'object') { + startAt = startAt.toString(); + } + + if (startAfter instanceof ExtendedDocument) { + startAfter = startAfter.getId().toString(); + } else if (typeof startAfter === 'string') { + startAfter = Identifier.from(startAfter).toString(); + } else if (startAfter && typeof startAfter === 'object') { + startAfter = startAfter.toString(); + } + + // Call wasm-sdk getDocuments + const result = await this.wasmSdk.getDocuments( + appDefinition.contractId.toString(), + fieldType, + whereClause, + orderByClause, + opts.limit, + startAt ? parseInt(startAt as string) : undefined, + startAfter ? parseInt(startAfter as string) : undefined + ); + + if (!result || !Array.isArray(result)) { + return []; + } + + // Convert wasm-sdk documents to js-dash-sdk format + const documents = result.map(doc => adapter.convertResponse(doc, 'document')); + + this.logger.debug(`[Documents#get] Obtained ${documents.length} document(s) for "${typeLocator}"`); + + return documents; + } catch (e) { + if (e.message?.includes('not found') || e.message?.includes('does not exist')) { + this.logger.debug(`[Documents#get] Obtained 0 documents for "${typeLocator}"`); + return []; + } + throw e; + } + } + + // Legacy implementation - will be removed once migration is complete // If not present, will fetch contract based on appName and contractId store in this.apps. await ensureAppContractFetched.call(this, appName); this.logger.silly(`[Documents#get] Ensured app contract is fetched "${typeLocator}"`); @@ -170,4 +247,4 @@ export async function get(this: Platform, typeLocator: string, opts: QueryOption return result; } -export default get; +export default get; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditTransfer.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditTransfer.ts index 71c0f196584..7daa7a410e6 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditTransfer.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditTransfer.ts @@ -12,10 +12,41 @@ export async function creditTransfer( this.logger.debug(`[Identity#creditTransfer] credit transfer from ${identity.getId().toString()} to ${recipientId.toString()} with amount ${amount}`); await this.initialize(); - const { dpp } = this; - recipientId = Identifier.from(recipientId); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get the identity's private key for signing + const account = await this.client.getWalletAccount(); + + // Get the transfer key (index 3) + const { privateKey: transferPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 3); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(transferPrivateKey); + + // Convert identity to hex format + const identityHex = identity.toBuffer().toString('hex'); + + // Call wasm-sdk identityCreditTransfer + const result = await this.wasmSdk.identityCreditTransfer( + identityHex, + privateKeyWIF, + recipientId.toString(), + amount + ); + + this.logger.debug(`[Identity#creditTransfer] Transferred ${amount} credits from ${identity.getId().toString()} to ${recipientId.toString()}`); + + return result.success !== false; + } + + // Legacy implementation - will be removed once migration is complete + const { dpp } = this; + const identityNonce = await this.nonceManager.bumpIdentityNonce(identity.getId()); const identityCreditTransferTransition = dpp.identity @@ -42,4 +73,4 @@ export async function creditTransfer( return true; } -export default creditTransfer; +export default creditTransfer; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts index 359c569b4b7..bc64d556aef 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/creditWithdrawal.ts @@ -53,6 +53,72 @@ export async function creditWithdrawal( ...options, }; + const balance = identity.getBalance(); + if (amount > balance) { + throw new Error(`Withdrawal amount "${amount}" is bigger that identity balance "${balance}"`); + } + + if (amount < MINIMAL_WITHDRAWAL_AMOUNT) { + throw new Error(`Withdrawal amount "${amount}" is less than minimal allowed withdrawal amount "${MINIMAL_WITHDRAWAL_AMOUNT}"`); + } + + if (!this.client.wallet) { + throw new Error('Wallet is not initialized'); + } + + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get the identity's private key for signing + const account = await this.client.getWalletAccount(); + + // Get the signing key (default to transfer key at index 3) + const { privateKey: signingPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), options.signingKeyIndex!); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(signingPrivateKey); + + // Use provided address or generate from wallet + let toAddress = options.toAddress; + if (!toAddress) { + // Get a new address from wallet for withdrawal + const { address } = await account.getUnusedAddress(); + toAddress = address.toString(); + } else { + // Validate the provided address + try { + new Address(toAddress, this.client.network); + } catch (e) { + throw new Error(`Invalid core recipient "${toAddress}" for network ${this.client.network}`); + } + } + + // Calculate core fee per byte + const minRelayFeePerByte = Math.ceil(this.client.wallet.storage + .getDefaultChainStore().state.fees.minRelay / 1000); + const coreFeePerByte = nearestGreaterFibonacci(minRelayFeePerByte); + + this.logger.debug(`[Identity#creditWithdrawal] credits withdrawal from ${identity.getId().toString()} to ${toAddress} with amount ${amount}`); + + // Call wasm-sdk identityCreditWithdrawal + const result = await this.wasmSdk.identityCreditWithdrawal( + identity.getId().toString(), + toAddress, + amount, + coreFeePerByte, + privateKeyWIF, + options.signingKeyIndex + ); + + this.logger.debug(`[Identity#creditWithdrawal] Withdrawal request created`); + + // Return metadata from the result + return result.metadata || new Metadata(); + } + + // Legacy implementation - will be removed once migration is complete const { dpp } = this; let outputScriptBytes: Buffer | undefined; @@ -73,19 +139,6 @@ export async function creditWithdrawal( this.logger.debug(`[Identity#creditWithdrawal] credits withdrawal from ${identity.getId().toString()} to recent withdrawal address with amount ${amount}`); } - const balance = identity.getBalance(); - if (amount > balance) { - throw new Error(`Withdrawal amount "${amount}" is bigger that identity balance "${balance}"`); - } - - if (amount < MINIMAL_WITHDRAWAL_AMOUNT) { - throw new Error(`Withdrawal amount "${amount}" is less than minimal allowed withdrawal amount "${MINIMAL_WITHDRAWAL_AMOUNT}"`); - } - - if (!this.client.wallet) { - throw new Error('Wallet is not initialized'); - } - // Divide by 1000 as stated in policy for GetDustThreshold // https://github.com/dashpay/dash/blob/master/src/policy/policy.cpp#L23 const minRelayFeePerByte = Math.ceil(this.client.wallet.storage @@ -128,4 +181,4 @@ export async function creditWithdrawal( return stateTransitionResult.metadata; } -export default creditWithdrawal; +export default creditWithdrawal; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/get.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/get.ts index 8c572f79bac..5b50d8c4164 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/get.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/get.ts @@ -14,6 +14,33 @@ const NotFoundError = require('@dashevo/dapi-client/lib/transport/GrpcTransport/ export async function get(this: Platform, id: Identifier | string): Promise { await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + const identityId = typeof id === 'string' ? id : id.toString(); + const cacheKey = `identity:${identityId}`; + + try { + // Use cached query for better performance + const result = await adapter.cachedQuery(cacheKey, async () => { + return await this.wasmSdk.getIdentity(identityId); + }); + + if (!result) { + return null; + } + + // Convert wasm-sdk response to js-dash-sdk format if needed + return adapter.convertResponse(result, 'identity'); + } catch (e) { + if (e.message?.includes('not found') || e.message?.includes('does not exist')) { + return null; + } + throw e; + } + } + + // Legacy implementation - will be removed once migration is complete const identifier = Identifier.from(id); let identityResponse: GetIdentityResponse; @@ -44,4 +71,4 @@ export async function get(this: Platform, id: Identifier | string): Promise return identity; } -export default get; +export default get; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts index 432f70773e3..72c58a9306e 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts @@ -15,9 +15,9 @@ export default async function register( await this.initialize(); const { client } = this; - const account = await client.getWalletAccount(); + // Create asset lock transaction const { transaction: assetLockTransaction, privateKey: assetLockPrivateKey, @@ -28,10 +28,113 @@ export default async function register( await account.broadcastTransaction(assetLockTransaction); this.logger.silly(`[Identity#register] Broadcasted asset lock transaction "${assetLockTransaction.hash}"`); + // Create asset lock proof const assetLockProof = await this.identities.utils .createAssetLockProof(assetLockTransaction, assetLockOutputIndex); this.logger.silly(`[Identity#register] Created asset lock proof with tx "${assetLockTransaction.hash}"`); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get identity index for HD key derivation + const identityIndex = await account.getUnusedIdentityIndex(); + + // Generate public keys for the identity + const publicKeys: any[] = []; + + // Authentication master key (id: 0) + const { privateKey: identityMasterPrivateKey } = account.identities + .getIdentityHDKeyByIndex(identityIndex, 0); + const identityMasterPublicKey = identityMasterPrivateKey.toPublicKey(); + publicKeys.push({ + id: 0, + type: 0, // ECDSA_SECP256K1 + purpose: 0, // AUTHENTICATION + securityLevel: 0, // MASTER + data: identityMasterPublicKey.toBuffer().toString('base64'), + readOnly: false, + }); + + // Authentication high level key (id: 1) + const { privateKey: identityHighAuthPrivateKey } = account.identities + .getIdentityHDKeyByIndex(identityIndex, 1); + const identityHighAuthPublicKey = identityHighAuthPrivateKey.toPublicKey(); + publicKeys.push({ + id: 1, + type: 0, // ECDSA_SECP256K1 + purpose: 0, // AUTHENTICATION + securityLevel: 2, // HIGH + data: identityHighAuthPublicKey.toBuffer().toString('base64'), + readOnly: false, + }); + + // Authentication critical level key (id: 2) + const { privateKey: identityCriticalAuthPrivateKey } = account.identities + .getIdentityHDKeyByIndex(identityIndex, 2); + const identityCriticalAuthPublicKey = identityCriticalAuthPrivateKey.toPublicKey(); + publicKeys.push({ + id: 2, + type: 0, // ECDSA_SECP256K1 + purpose: 0, // AUTHENTICATION + securityLevel: 1, // CRITICAL + data: identityCriticalAuthPublicKey.toBuffer().toString('base64'), + readOnly: false, + }); + + // Transfer key (id: 3) + const { privateKey: identityTransferPrivateKey } = account.identities + .getIdentityHDKeyByIndex(identityIndex, 3); + const identityTransferPublicKey = identityTransferPrivateKey.toPublicKey(); + publicKeys.push({ + id: 3, + type: 0, // ECDSA_SECP256K1 + purpose: 3, // TRANSFER (3) + securityLevel: 1, // CRITICAL + data: identityTransferPublicKey.toBuffer().toString('base64'), + readOnly: false, + }); + + // Convert asset lock proof to hex format for wasm-sdk + const assetLockProofHex = adapter.convertAssetLockProof(assetLockProof); + + // Convert private key to WIF format + const assetLockPrivateKeyWIF = adapter.convertPrivateKeyToWIF(assetLockPrivateKey); + + // Call wasm-sdk identityCreate + const result = await this.wasmSdk.identityCreate( + assetLockProofHex, + assetLockPrivateKeyWIF, + JSON.stringify(publicKeys) + ); + + // Parse the identity ID from the result + const identityId = result.identity?.id || result.id; + + // Store identity in wallet + account.storage + .getWalletStore(account.walletId) + .insertIdentityIdAtIndex( + identityId.toString(), + identityIndex, + ); + + // Acknowledge identifier to handle retry attempts + this.fetcher.acknowledgeIdentifier(identityId); + + // Fetch the created identity + const registeredIdentity = await this.identities.get(identityId); + + if (registeredIdentity === null) { + throw new Error(`Can't fetch created identity with id ${identityId}`); + } + + this.logger.debug(`[Identity#register] Registered identity "${identityId}"`); + + return registeredIdentity; + } + + // Legacy implementation - will be removed once migration is complete const { identity, identityCreateTransition, identityIndex } = await this.identities.utils .createIdentityCreateTransition(assetLockProof, assetLockPrivateKey); @@ -72,4 +175,4 @@ export default async function register( this.logger.debug(`[Identity#register] Registered identity "${identityId}"`); return identity; -} +} \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/topUp.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/topUp.ts index 44b576fc0a7..3044f0308c0 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/topUp.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/topUp.ts @@ -20,11 +20,9 @@ export async function topUp( await this.initialize(); const { client } = this; - - identityId = Identifier.from(identityId); - const account = await client.getWalletAccount(); + // Create asset lock transaction const { transaction: assetLockTransaction, privateKey: assetLockPrivateKey, @@ -34,11 +32,40 @@ export async function topUp( // Broadcast Asset Lock transaction await account.broadcastTransaction(assetLockTransaction); this.logger.silly(`[Identity#topUp] Broadcasted asset lock transaction "${assetLockTransaction.hash}"`); + // Create a proof for the asset lock transaction const assetLockProof = await this.identities.utils .createAssetLockProof(assetLockTransaction, assetLockOutputIndex); this.logger.silly(`[Identity#topUp] Created asset lock proof with tx "${assetLockTransaction.hash}"`); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Convert identity ID to string + const identityIdString = typeof identityId === 'string' ? identityId : identityId.toString(); + + // Convert asset lock proof to hex format for wasm-sdk + const assetLockProofHex = adapter.convertAssetLockProof(assetLockProof); + + // Convert private key to WIF format + const assetLockPrivateKeyWIF = adapter.convertPrivateKeyToWIF(assetLockPrivateKey); + + // Call wasm-sdk identityTopUp + const result = await this.wasmSdk.identityTopUp( + identityIdString, + assetLockProofHex, + assetLockPrivateKeyWIF + ); + + this.logger.debug(`[Identity#topUp] Topped up identity "${identityIdString}"`); + + return result.success !== false; + } + + // Legacy implementation - will be removed once migration is complete + identityId = Identifier.from(identityId); + const identityTopUpTransition = await this.identities.utils .createIdentityTopUpTransition(assetLockProof, assetLockPrivateKey, identityId); this.logger.silly(`[Identity#topUp] Created IdentityTopUpTransition with asset lock tx "${assetLockTransaction.hash}"`); @@ -52,4 +79,4 @@ export async function topUp( return true; } -export default topUp; +export default topUp; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/update.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/update.ts index 941e75d303b..04398331ba9 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/update.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/update.ts @@ -26,6 +26,63 @@ export async function update( }); await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get the identity's private key for signing (master key at index 0) + const account = await this.client.getWalletAccount(); + + // Get the master key for signing + const { privateKey: masterPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 0); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(masterPrivateKey); + + // Prepare public keys to add + let addPublicKeysJson: string | undefined; + if (publicKeys.add && publicKeys.add.length > 0) { + const keysToAdd = publicKeys.add.map(key => { + // Get the private key for this public key to sign the proof + const privateKey = privateKeys[key.getId()]; + if (!privateKey) { + throw new Error(`Private key for key ${key.getId()} not found`); + } + + return { + id: key.getId(), + type: key.getType(), + purpose: key.getPurpose(), + securityLevel: key.getSecurityLevel(), + data: key.getData().toString('base64'), + readOnly: key.isReadOnly(), + // Include the private key for signing the proof + privateKey: adapter.convertPrivateKeyToWIF(privateKey), + }; + }); + addPublicKeysJson = JSON.stringify(keysToAdd); + } + + // Prepare keys to disable + const disableKeyIds = publicKeys.disable?.map(key => key.getId()); + + this.logger.debug(`[Identity#update] Calling wasm-sdk identityUpdate`); + + // Call wasm-sdk identityUpdate + const result = await this.wasmSdk.identityUpdate( + identity.getId().toString(), + addPublicKeysJson, + disableKeyIds, + privateKeyWIF + ); + + this.logger.debug(`[Identity#update] Updated identity ${identity.getId().toString()}`); + + return result.success !== false; + } + + // Legacy implementation - will be removed once migration is complete const { dpp } = this; const identityNonce = await this.nonceManager.bumpIdentityNonce(identity.getId()); @@ -103,4 +160,4 @@ export async function update( return true; } -export default update; +export default update; \ No newline at end of file diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts index 07f6576e782..b35d4b329d0 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts @@ -29,6 +29,43 @@ export async function register( ): Promise { await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Get identity private key for signing + const account = await this.client.getWalletAccount(); + + // Get the key for document operations (index 1) + const { privateKey: documentPrivateKey } = account.identities + .getIdentityHDKeyById(identity.getId().toString(), 1); + + // Convert private key to WIF format + const privateKeyWIF = adapter.convertPrivateKeyToWIF(documentPrivateKey); + + // Convert identity to hex format + const identityHex = identity.getId().toBuffer().toString('hex'); + + // Convert records to wasm-sdk format + const recordsJson = JSON.stringify({ + identity: records.identity ? Identifier.from(records.identity).toString() : undefined, + }); + + this.logger.debug(`[Names#register] Calling wasm-sdk dpnsRegister for "${name}"`); + + // Call wasm-sdk dpnsRegister + const result = await this.wasmSdk.dpnsRegister( + identityHex, + privateKeyWIF, + name, + recordsJson + ); + + this.logger.debug(`[Names#register] Registered name "${name}" via wasm-sdk`); + + return result; + } + if (records.identity) { records.identity = Identifier.from(records.identity); } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/resolveByRecord.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/resolveByRecord.ts index 0bcd4b2a75c..6e224ceb9d6 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/resolveByRecord.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/resolveByRecord.ts @@ -9,6 +9,39 @@ import { Platform } from '../../Platform'; export async function resolveByRecord(this: Platform, record: string, value: any): Promise { await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + // Convert identity values to string format + let recordValue = value; + if (record === 'identity') { + recordValue = Identifier.from(value).toString(); + } else if (typeof value === 'object' && value.toString) { + recordValue = value.toString(); + } + + this.logger.debug(`[Names#resolveByRecord] Calling wasm-sdk getNameByRecord for record "${record}" with value "${recordValue}"`); + + // Call wasm-sdk getNameByRecord + const result = await this.wasmSdk.getNameByRecord( + record, + recordValue, + undefined // parentDomainName is optional + ); + + if (!result) { + return []; + } + + // Convert the result to array of documents + const documents = Array.isArray(result) ? result : [result]; + + this.logger.debug(`[Names#resolveByRecord] Found ${documents.length} names via wasm-sdk`); + + return documents.map(doc => adapter.convertResponse(doc, 'document')); + } + if (record === 'identity') { value = Identifier.from(value); } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/search.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/search.ts index e162bc1ba19..311f8cffb13 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/search.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/search.ts @@ -11,6 +11,30 @@ import convertToHomographSafeChars from '../../../../../utils/convertToHomograph export async function search(this: Platform, labelPrefix: string, parentDomainName: string = '') { await this.initialize(); + // If wasm-sdk is available, delegate to it + if (this.wasmSdk && this.getAdapter()) { + const adapter = this.getAdapter()!; + + this.logger.debug(`[Names#search] Calling wasm-sdk getNameBySearch for "${labelPrefix}"`); + + // Call wasm-sdk getNameBySearch + const result = await this.wasmSdk.getNameBySearch( + labelPrefix, + parentDomainName || undefined + ); + + if (!result) { + return []; + } + + // Convert the result to array of documents + const documents = Array.isArray(result) ? result : [result]; + + this.logger.debug(`[Names#search] Found ${documents.length} names via wasm-sdk`); + + return documents.map(doc => adapter.convertResponse(doc, 'document')); + } + const normalizedParentDomainName = convertToHomographSafeChars(parentDomainName); const normalizedLabelPrefix = convertToHomographSafeChars(labelPrefix); diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/types/wasm-sdk.d.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/types/wasm-sdk.d.ts new file mode 100644 index 00000000000..bf7c08c34ba --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/types/wasm-sdk.d.ts @@ -0,0 +1,121 @@ +/** + * TypeScript definitions for @dashevo/wasm-sdk + * This file ensures type compatibility between js-dash-sdk and wasm-sdk + */ + +declare module '@dashevo/wasm-sdk' { + export interface WasmTransport { + url: string; + network: string; + } + + export interface IdentityPublicKey { + id: number; + type: number; // 0 = ECDSA_SECP256K1, 1 = BLS12_381, 2 = ECDSA_HASH160 + purpose: number; // 0 = AUTHENTICATION, 1 = ENCRYPTION, 2 = DECRYPTION, 3 = TRANSFER + securityLevel: number; // 0 = MASTER, 1 = CRITICAL, 2 = HIGH, 3 = MEDIUM + data: string; // Base64-encoded public key + readOnly: boolean; + } + + export interface Identity { + id: string; + publicKeys: IdentityPublicKey[]; + balance: number; + revision: number; + } + + export interface Document { + id: string; + dataContractId: string; + ownerId: string; + revision: number; + createdAt?: number; + updatedAt?: number; + data: Record; + } + + export interface DataContract { + id: string; + version: number; + ownerId: string; + schema: object; + documents: Record; + } + + export interface StateTransitionResult { + success: boolean; + data?: any; + error?: string; + } + + export class WasmSdk { + static new(transport: WasmTransport, proofs: boolean): Promise; + + // Identity queries + getIdentity(id: string): Promise; + getIdentityKeys(identityId: string, keyRequestType?: string, specificKeyIds?: number[], searchPurposeMap?: string): Promise; + getIdentityNonce(identityId: string): Promise; + getIdentityContractNonce(identityId: string, contractId: string): Promise; + getIdentityBalance(id: string): Promise; + getIdentityBalanceAndRevision(id: string): Promise<{ balance: number; revision: number }>; + + // Identity state transitions + identityCreate(assetLockProof: string, assetLockProofPrivateKey: string, publicKeys: string): Promise; + identityTopUp(identityId: string, assetLockProof: string, assetLockProofPrivateKey: string): Promise; + identityUpdate(identityHex: string, identityPrivateKeyHex: string, addPublicKeys?: string, disablePublicKeys?: string): Promise; + identityCreditTransfer(identityHex: string, identityPrivateKeyHex: string, recipientId: string, amount: number): Promise; + identityCreditWithdrawal(identityHex: string, identityPrivateKeyHex: string, amount: number, coreFeePerByte: number, pooling: string, outputScriptBytes: string): Promise; + + // Document queries + getDocument(dataContractId: string, documentType: string, documentId: string): Promise; + getDocuments(dataContractId: string, documentType: string, where?: string, orderBy?: string, limit?: number, startAt?: number, startAfter?: number): Promise; + + // Document state transitions + documentsPut(dataContractId: string, documentType: string, documentsJson: string, identityHex: string, identityPrivateKeyHex: string, putType: string): Promise; + documentsDelete(dataContractId: string, documentType: string, documentIds: string[], identityHex: string, identityPrivateKeyHex: string): Promise; + + // Data contract queries + getDataContract(contractId: string): Promise; + getDataContractHistory(identifier: string, startAtMs: bigint, limit: number, offset: number): Promise; + + // Data contract state transitions + dataContractCreate(dataContractJson: string, identityHex: string, identityPrivateKeyHex: string): Promise; + dataContractUpdate(dataContractJson: string, identityHex: string, identityPrivateKeyHex: string): Promise; + + // Name queries + getNameBySearch(searchString: string, parentDomainName?: string): Promise; + getNameByRecord(recordName: string, recordValue: string, parentDomainName?: string): Promise; + + // Name state transitions + dpnsRegister(identityHex: string, identityPrivateKeyHex: string, name: string, recordsJson: string): Promise; + + // Token queries + getIdentityTokenBalances(identityId: string, tokenIds: string[]): Promise; + getIdentitiesTokenBalances(identityIds: string[], tokenId: string): Promise; + getIdentityTokenInfos(identityId: string, tokenIds?: string[]): Promise; + getIdentitiesTokenInfos(identityIds: string[], tokenId: string): Promise; + getTokenInfo(tokenId: string): Promise; + getTokenInfos(tokenIds: string[]): Promise; + getTokenTotalSupply(tokenId: string): Promise; + getTokenTotalSupplies(tokenIds: string[]): Promise; + getTokenCurrentSupply(tokenId: string): Promise; + getTokenCurrentSupplies(tokenIds: string[]): Promise; + getGroupInfo(groupId: string): Promise; + getGroupInfos(groupIds: string[]): Promise; + + // Epoch queries + getCurrentEpochInfo(): Promise; + getRecentEpochInfo(count: number, startEpoch?: number): Promise; + getFutureEpochInfo(count: number, startEpoch?: number): Promise; + getEpochStartTimestamp(epoch: number): Promise; + getEpochStartTimestamps(epochs: number[]): Promise; + + // Other queries + waitForStateTransitionResult(stateTransitionHash: string): Promise; + broadcastStateTransition(stateTransition: string): Promise; + } + + // Export the default initialization function + export default function init(): Promise; +} \ No newline at end of file diff --git a/packages/platform-test-suite/WASM_SDK_TEST_ISSUES.md b/packages/platform-test-suite/WASM_SDK_TEST_ISSUES.md new file mode 100644 index 00000000000..00126db909d --- /dev/null +++ b/packages/platform-test-suite/WASM_SDK_TEST_ISSUES.md @@ -0,0 +1,88 @@ +# Platform Test Suite - WASM SDK Integration Issues Documentation + +## Overview +This document captures the issues encountered and solutions found while migrating the platform test suite to work with wasm-sdk. + +## Key Findings + +### 1. Faucet Wallet Has UTXOs +The faucet wallet DOES have UTXOs (38 found when checking directly). The "utxosList must contain at least 1 utxo" error is NOT because the faucet lacks funds. + +### 2. Test Environment Setup +- Local dashmate network must be running (`yarn dashmate status`) +- Blocks must be generated to the faucet address: `yarn dashmate core cli "generatetoaddress 100 yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5"` +- Environment variables are loaded from `.env` file via `bootstrap.js` + +### 3. Critical Environment Variables +```bash +DAPI_SEED=127.0.0.1:3000 +FAUCET_1_ADDRESS=yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5 +FAUCET_1_PRIVATE_KEY=cR4t6evwVZoCp1JsLk4wURK4UmBCZzZotNzn9T1mhBT19SH9JtNt +NETWORK=regtest +``` + +### 4. Bootstrap Process +The `lib/test/bootstrap.js` file: +- Sets `FAUCET_PRIVATE_KEY` from `FAUCET_1_PRIVATE_KEY` (or FAUCET_2 for worker 2) +- Sets `FAUCET_ADDRESS` from `FAUCET_1_ADDRESS` +- This mapping happens based on MOCHA_WORKER_ID + +### 5. No Regressions from Our Changes +We verified that our changes did NOT cause the wallet sync issues: +- Adding `patchClientForTests` doesn't affect wallet sync +- The network configuration was already correct +- Reverting our changes didn't fix the UTXO issues + +### 6. Current Test Status +- 6-7 tests passing (up from 0) +- 5 tests failing with various issues: + - 3 tests: "utxosList must contain at least 1 utxo" + - 1 test: ES module loading error for wasm-sdk + - 1 test: "Insufficient funds" + +### 7. Root Cause of UTXO Error +The issue appears to be timing-related. The faucet wallet HAS UTXOs but `fundWallet` is being called before the wallet has fully synced. The wallet sync is asynchronous and the tests don't wait long enough. + +### 8. DPP Compatibility +We successfully fixed the `dpp.identity` undefined error by: +- Adding a fallback in `createAssetLockProof.ts` when dpp is undefined +- Creating a compatibility layer in `wasm-sdk-compat.js` + +### 9. ES Module Loading +Some tests fail with ES module loading errors. This is because: +- wasm-sdk is an ES module +- The build system uses CommonJS require() +- We have a dynamic loader but it's not working in all test scenarios + +## Recommendations for Future Fixes + +1. **Wallet Sync Issue**: Add proper waiting/retry logic in `fundWallet` to ensure the faucet wallet has synced its UTXOs before attempting to create transactions. + +2. **ES Module Loading**: The wasm-sdk loader needs to be more robust. Consider using dynamic imports consistently across all test scenarios. + +3. **Test Isolation**: Some tests may be interfering with each other's wallet state. Consider better test isolation. + +4. **Storage Settings**: The FAUCET_WALLET_USE_STORAGE setting affects wallet persistence. When disabled, the wallet must sync from scratch each time. + +## Test Commands + +```bash +# Generate blocks to fund faucet +cd ../.. && yarn dashmate core cli "generatetoaddress 100 yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5" + +# Run specific test +./bin/test.sh test/functional/core/broadcastTransaction.spec.js + +# Run all tests +npm test +``` + +## Files Modified +- `packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/internal/createAssetLockProof.ts` - Added fallback for undefined dpp +- `packages/platform-test-suite/lib/test/wasm-sdk-compat.js` - Created compatibility layer +- Various platform method files - Migrated to use wasm-sdk + +## What Was NOT Changed +- Wallet sync logic +- Test environment setup +- Faucet funding mechanism \ No newline at end of file diff --git a/packages/platform-test-suite/lib/test/createClientWithFundedWallet.js b/packages/platform-test-suite/lib/test/createClientWithFundedWallet.js index b2b302ff4e2..93792a75e57 100644 --- a/packages/platform-test-suite/lib/test/createClientWithFundedWallet.js +++ b/packages/platform-test-suite/lib/test/createClientWithFundedWallet.js @@ -64,6 +64,7 @@ async function createClientWithFundedWallet(amount, HDPrivateKey = undefined) { await faucetClient.disconnect(); } + return client; } diff --git a/packages/platform-test-suite/lib/test/createFaucetClient.js b/packages/platform-test-suite/lib/test/createFaucetClient.js index 99d94e70952..72a7cc40576 100644 --- a/packages/platform-test-suite/lib/test/createFaucetClient.js +++ b/packages/platform-test-suite/lib/test/createFaucetClient.js @@ -52,6 +52,7 @@ function createFaucetClient() { wallet: walletOptions, }); + return faucetClient; } diff --git a/packages/platform-test-suite/lib/test/wasm-sdk-compat.js b/packages/platform-test-suite/lib/test/wasm-sdk-compat.js new file mode 100644 index 00000000000..be0d31dc03a --- /dev/null +++ b/packages/platform-test-suite/lib/test/wasm-sdk-compat.js @@ -0,0 +1,91 @@ +/** + * Compatibility layer for tests that use platform.dpp directly + * This allows tests to work with both legacy DPP and new wasm-sdk delegation + */ + +/** + * Create a DPP compatibility shim for wasm-sdk + * @param {Platform} platform - Platform instance + * @returns {Object} DPP-like interface + */ +function createDPPCompat(platform) { + // If legacy DPP is available, use it + if (platform.dpp) { + return platform.dpp; + } + + // Otherwise, create a compatibility layer + return { + identity: { + createInstantAssetLockProof: (instantLock, transaction, outputIndex) => { + // Create a mock instant asset lock proof that matches DPP format + return { + type: 0, // Instant lock type + instantLock: Buffer.isBuffer(instantLock) ? instantLock : Buffer.from(instantLock), + transaction: Buffer.isBuffer(transaction) ? transaction : Buffer.from(transaction), + outputIndex: outputIndex + }; + }, + + createChainAssetLockProof: (coreChainLockedHeight, outPoint) => { + // Create a mock chain asset lock proof that matches DPP format + return { + type: 1, // Chain lock type + coreChainLockedHeight: coreChainLockedHeight, + outPoint: Buffer.isBuffer(outPoint) ? outPoint : Buffer.from(outPoint) + }; + }, + + createIdentityTopUpTransition: async (assetLockProof, outputPrivateKey, identity) => { + return platform.identities.utils.createIdentityTopUpTransition( + assetLockProof, + outputPrivateKey, + identity + ); + }, + + createFromBuffer: async (buffer) => { + // This would need to be implemented based on wasm-sdk's response format + throw new Error('createFromBuffer not yet implemented for wasm-sdk compatibility'); + } + }, + + dataContract: { + createFromBuffer: async (buffer) => { + // This would need to be implemented based on wasm-sdk's response format + throw new Error('createFromBuffer not yet implemented for wasm-sdk compatibility'); + } + }, + + document: { + createFromBuffer: async (buffer) => { + // This would need to be implemented based on wasm-sdk's response format + throw new Error('createFromBuffer not yet implemented for wasm-sdk compatibility'); + } + } + }; +} + +/** + * Patch client to add DPP compatibility + * @param {Client} client - Dash client instance + * @returns {Client} Patched client + */ +function patchClientForTests(client) { + // Add DPP compatibility if not already present + if (!client.platform.dpp && client.platform.wasmSdk) { + Object.defineProperty(client.platform, 'dpp', { + get() { + return createDPPCompat(this); + }, + configurable: true + }); + } + + return client; +} + +module.exports = { + createDPPCompat, + patchClientForTests +}; \ No newline at end of file From 0e5b8a8f6f0f19cfb1e25388a403642db2380d7b Mon Sep 17 00:00:00 2001 From: quantum Date: Tue, 29 Jul 2025 19:19:51 -0500 Subject: [PATCH 2/2] more work --- .pnp.cjs | 10 +++ .../Platform/adapters/WasmPlatformAdapter.ts | 64 +++++++++++++++++-- packages/js-dash-sdk/tsconfig.build.json | 1 + yarn.lock | 11 ++++ 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index d47f66d73cf..5351d7093f9 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3064,6 +3064,15 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@dashevo/wasm-sdk", [\ + ["file:../wasm-sdk/pkg#../wasm-sdk/pkg::hash=944b57&locator=dash%40workspace%3Apackages%2Fjs-dash-sdk", {\ + "packageLocation": "./.yarn/cache/@dashevo-wasm-sdk-file-7f6fe61b82-1ccf5cd50c.zip/node_modules/@dashevo/wasm-sdk/",\ + "packageDependencies": [\ + ["@dashevo/wasm-sdk", "file:../wasm-sdk/pkg#../wasm-sdk/pkg::hash=944b57&locator=dash%40workspace%3Apackages%2Fjs-dash-sdk"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@dashevo/withdrawals-contract", [\ ["workspace:packages/withdrawals-contract", {\ "packageLocation": "./packages/withdrawals-contract/",\ @@ -8572,6 +8581,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/masternode-reward-shares-contract", "workspace:packages/masternode-reward-shares-contract"],\ ["@dashevo/wallet-lib", "workspace:packages/wallet-lib"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ + ["@dashevo/wasm-sdk", "file:../wasm-sdk/pkg#../wasm-sdk/pkg::hash=944b57&locator=dash%40workspace%3Apackages%2Fjs-dash-sdk"],\ ["@dashevo/withdrawals-contract", "workspace:packages/withdrawals-contract"],\ ["@types/chai", "npm:4.2.22"],\ ["@types/dirty-chai", "npm:2.0.2"],\ diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts index 85350b1add6..ac1f57f6f60 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/adapters/WasmPlatformAdapter.ts @@ -6,7 +6,9 @@ import { DAPIClient } from '@dashevo/dapi-client'; // Type definitions for wasm-sdk module interface WasmSdkModule { default: () => Promise; + initSync?: (buffer: Buffer) => void; WasmSdk: any; // Will be properly typed when wasm-sdk is available + WasmSdkBuilder: any; // Builder class for creating SDK instances } export class WasmPlatformAdapter { @@ -23,6 +25,12 @@ export class WasmPlatformAdapter { private cacheTTL = 60000; // 60 seconds default TTL constructor(dapiClient: DAPIClient, network: string, proofs: boolean = true) { + console.log('WasmPlatformAdapter constructor:', { + dapiClient: dapiClient ? 'present' : 'missing', + hasDapiAddresses: dapiClient ? dapiClient.dapiAddresses : 'N/A', + network, + proofs + }); this.dapiClient = dapiClient; this.network = network; this.proofs = proofs; @@ -50,10 +58,37 @@ export class WasmPlatformAdapter { const wasmModule = await loadWasmSdk() as WasmSdkModule; // Initialize WASM module - await wasmModule.default(); + console.log('WasmPlatformAdapter: Initializing WASM module...'); + try { + // In Node.js environments, we need to handle the WASM file path + if (typeof window === 'undefined' && typeof global !== 'undefined') { + // Node.js environment - use initSync with buffer + const fs = require('fs'); + const path = require('path'); + const wasmPath = path.join(__dirname, '..', '..', '..', '..', '..', '..', '..', '.yarn', 'cache', '@dashevo-wasm-sdk-file-7f6fe61b82-1ccf5cd50c.zip', 'node_modules', '@dashevo', 'wasm-sdk', 'wasm_sdk_bg.wasm'); + console.log('WasmPlatformAdapter: Loading WASM from:', wasmPath); + const wasmBuffer = fs.readFileSync(wasmPath); + + // Use initSync for synchronous initialization in Node.js + if (wasmModule.initSync) { + wasmModule.initSync(wasmBuffer); + } else { + // Fallback to default init + await wasmModule.default(); + } + } else { + // Browser environment + await wasmModule.default(); + } + } catch (initError) { + console.error('WasmPlatformAdapter: WASM module initialization error:', initError); + throw new Error(`Failed to initialize WASM module: ${initError.message}`); + } this.wasmSdk = wasmModule; this.initialized = true; + console.log('WasmPlatformAdapter: WASM module initialized successfully'); + console.log('WasmPlatformAdapter: Available exports:', Object.keys(wasmModule)); } catch (error) { throw new Error(`Failed to initialize wasm-sdk: ${error.message}`); } @@ -68,12 +103,27 @@ export class WasmPlatformAdapter { } if (!this.sdkInstance) { - const transport: any = { - url: this.dapiClient.dapiAddresses[0].toString(), - network: this.network, - }; - - this.sdkInstance = await this.wasmSdk!.WasmSdk.new(transport, this.proofs); + try { + // Use WasmSdkBuilder to create SDK instance + let builder; + if (this.network === 'mainnet') { + builder = this.wasmSdk!.WasmSdkBuilder.new_mainnet(); + } else if (this.network === 'testnet') { + builder = this.wasmSdk!.WasmSdkBuilder.new_testnet(); + } else { + // For regtest/local, use testnet configuration + // TODO: For local/regtest networks, we need to configure custom URL + console.log('WasmPlatformAdapter: Using testnet builder for regtest network'); + builder = this.wasmSdk!.WasmSdkBuilder.new_testnet(); + } + + // Build the SDK instance + this.sdkInstance = builder.build(); + console.log('WasmPlatformAdapter: SDK instance created successfully'); + } catch (error) { + console.error('WasmPlatformAdapter: Failed to create SDK instance:', error); + throw error; + } } return this.sdkInstance; diff --git a/packages/js-dash-sdk/tsconfig.build.json b/packages/js-dash-sdk/tsconfig.build.json index c32b920fef7..b03a78728db 100644 --- a/packages/js-dash-sdk/tsconfig.build.json +++ b/packages/js-dash-sdk/tsconfig.build.json @@ -10,5 +10,6 @@ "src/test/**", "src/**/*.spec.ts", "test", + "src/SDK/Client/Platform/adapters/wasm-sdk-loader.js" ] } diff --git a/yarn.lock b/yarn.lock index 238c2cb6114..a4a7c9f93c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2018,6 +2018,13 @@ __metadata: languageName: unknown linkType: soft +"@dashevo/wasm-sdk@file:../wasm-sdk/pkg::locator=dash%40workspace%3Apackages%2Fjs-dash-sdk": + version: 0.0.0 + resolution: "@dashevo/wasm-sdk@file:../wasm-sdk/pkg#../wasm-sdk/pkg::hash=944b57&locator=dash%40workspace%3Apackages%2Fjs-dash-sdk" + checksum: 1ccf5cd50c317664b6921a32813de629000dfdf8da07153442f9647cf8d17853bbbcceb46faeca7d360cf8b2e3d8f09c8cd1ee92b30cf50237547feb1b05a9c0 + languageName: node + linkType: hard + "@dashevo/withdrawals-contract@workspace:*, @dashevo/withdrawals-contract@workspace:packages/withdrawals-contract": version: 0.0.0-use.local resolution: "@dashevo/withdrawals-contract@workspace:packages/withdrawals-contract" @@ -6534,6 +6541,7 @@ __metadata: "@dashevo/masternode-reward-shares-contract": "workspace:*" "@dashevo/wallet-lib": "workspace:*" "@dashevo/wasm-dpp": "workspace:*" + "@dashevo/wasm-sdk": "file:../wasm-sdk/pkg" "@dashevo/withdrawals-contract": "workspace:*" "@types/chai": "npm:^4.2.12" "@types/dirty-chai": "npm:^2.0.2" @@ -6592,6 +6600,9 @@ __metadata: webpack: "npm:^5.94.0" webpack-cli: "npm:^4.9.1" winston: "npm:^3.2.1" + dependenciesMeta: + "@dashevo/wasm-sdk": + optional: true languageName: unknown linkType: soft