import loadWasm from "./wasm.js";
import { CallbackType, MethodName, WorkerAction } from "./constants.js";
import {
acquireSyncLock,
releaseSyncLock,
releaseSyncLockWithError,
} from "./syncLock.js";
import { MidenClient } from "./client.js";
import { CompilerResource } from "./resources/compiler.js";
import {
createP2IDNote,
createP2IDENote,
buildSwapTag,
_setWasm as _setStandaloneWasm,
_setWebClient as _setStandaloneWebClient,
} from "./standalone.js";
export * from "../Cargo.toml";
export const AccountType = Object.freeze({
FungibleFaucet: 0,
NonFungibleFaucet: 1,
RegularAccountImmutableCode: 2,
RegularAccountUpdatableCode: 3,
MutableWallet: 3,
ImmutableWallet: 2,
ImmutableContract: 2,
MutableContract: 3,
});
export const AuthScheme = Object.freeze({
Falcon: "falcon",
ECDSA: "ecdsa",
});
export const NoteVisibility = Object.freeze({
Public: "public",
Private: "private",
});
export const StorageMode = Object.freeze({
Public: "public",
Private: "private",
Network: "network",
});
export const Linking = Object.freeze({
Dynamic: "dynamic",
Static: "static",
});
export { MidenClient };
export { CompilerResource };
export { createP2IDNote, createP2IDENote, buildSwapTag };
export { WebClient as WasmWebClient, MockWebClient as MockWasmWebClient };
const SYNC_METHODS = new Set([
"buildSwapTag",
"createCodeBuilder",
"newConsumeTransactionRequest",
"newMintTransactionRequest",
"newSendTransactionRequest",
"newSwapTransactionRequest",
"proveBlock",
"serializeMockChain",
"serializeMockNoteTransportNode",
"setDebugMode",
"storeIdentifier",
"usesMockChain",
]);
const WRITE_METHODS = new Set([
"addTag",
"executeForSummary",
"executeProgram",
"fetchAllPrivateNotes",
"fetchPrivateNotes",
"forceImportStore",
"importAccountById",
"importAccountFile",
"importNoteFile",
"importPublicAccountFromSeed",
"insertAccountAddress",
"newAccount",
"pruneAccountHistory",
"removeAccountAddress",
"removeTag",
"removeSetting",
"sendPrivateNote",
"setSetting",
"submitProvenTransaction",
]);
const READ_METHODS = new Set([
"accountReader",
"exportAccountFile",
"exportNoteFile",
"exportStore",
"getAccount",
"getAccountCode",
"getAccountStorage",
"getAccountVault",
"getAccounts",
"getConsumableNotes",
"getInputNote",
"getInputNotes",
"getOutputNote",
"getOutputNotes",
"getSetting",
"getSyncHeight",
"getTransactions",
"listSettingKeys",
"listTags",
"executeProgram",
]);
const MOCK_STORE_NAME = "mock_client_db";
void SYNC_METHODS;
void WRITE_METHODS;
void READ_METHODS;
const buildTypedArraysExport = (exportObject) => {
return Object.entries(exportObject).reduce(
(exports, [exportName, _export]) => {
if (exportName.endsWith("Array")) {
exports[exportName] = _export;
}
return exports;
},
{}
);
};
const deserializeError = (errorLike) => {
if (!errorLike) {
return new Error("Unknown error received from worker");
}
const { name, message, stack, cause, ...rest } = errorLike;
const reconstructedError = new Error(message ?? "Unknown worker error");
reconstructedError.name = name ?? reconstructedError.name;
if (stack) {
reconstructedError.stack = stack;
}
if (cause) {
reconstructedError.cause = deserializeError(cause);
}
Object.entries(rest).forEach(([key, value]) => {
if (value !== undefined) {
reconstructedError[key] = value;
}
});
return reconstructedError;
};
export const MidenArrays = {};
let wasmModule = null;
let wasmLoadPromise = null;
let webClientStaticsCopied = false;
const ensureWasm = async () => {
if (wasmModule) {
return wasmModule;
}
if (!wasmLoadPromise) {
wasmLoadPromise = loadWasm().then((module) => {
wasmModule = module;
if (module) {
Object.assign(MidenArrays, buildTypedArraysExport(module));
if (!webClientStaticsCopied && module.WebClient) {
copyWebClientStatics(module.WebClient);
webClientStaticsCopied = true;
}
_setStandaloneWasm(module);
}
return module;
});
}
return wasmLoadPromise;
};
export const getWasmOrThrow = async () => {
const module = await ensureWasm();
if (!module) {
throw new Error(
"Miden WASM bindings are unavailable in this environment (SSR is disabled)."
);
}
return module;
};
function createClientProxy(instance) {
return new Proxy(instance, {
get(target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop, receiver);
}
if (target.wasmWebClient && prop in target.wasmWebClient) {
const value = target.wasmWebClient[prop];
if (typeof value === "function") {
return value.bind(target.wasmWebClient);
}
return value;
}
return undefined;
},
});
}
class WebClient {
static workerMode = "auto";
static _shouldUseClassicWorker() {
const mode = WebClient.workerMode;
if (mode === "module") return false;
if (mode === "classic") return true;
const ua =
typeof navigator !== "undefined" && navigator.userAgent
? navigator.userAgent
: "";
if (/Chrome\/|Chromium\//.test(ua)) return false;
if (/AppleWebKit/.test(ua)) return true;
return false;
}
constructor(
rpcUrl,
noteTransportUrl,
seed,
storeName,
getKeyCb,
insertKeyCb,
signCb,
logLevel
) {
this.rpcUrl = rpcUrl;
this.noteTransportUrl = noteTransportUrl;
this.seed = seed;
this.storeName = storeName;
this.getKeyCb = getKeyCb;
this.insertKeyCb = insertKeyCb;
this.signCb = signCb;
this.logLevel = logLevel;
if (typeof Worker !== "undefined") {
console.log("WebClient: Web Workers are available.");
if (WebClient._shouldUseClassicWorker()) {
this.worker = new Worker(
new URL("./workers/web-client-methods-worker.js", import.meta.url)
);
} else {
this.worker = new Worker(
new URL(
"./workers/web-client-methods-worker.module.js",
import.meta.url
),
{ type: "module" }
);
}
this.pendingRequests = new Map();
this.loaded = new Promise((resolve) => {
this.loadedResolver = resolve;
});
this.ready = new Promise((resolve) => {
this.readyResolver = resolve;
});
this.worker.addEventListener("message", async (event) => {
const data = event.data;
if (data.loaded) {
this.loadedResolver();
return;
}
if (data.ready) {
this.readyResolver();
return;
}
if (data.action === WorkerAction.EXECUTE_CALLBACK) {
const { callbackType, args, requestId } = data;
try {
const callbackMapping = {
[CallbackType.GET_KEY]: this.getKeyCb,
[CallbackType.INSERT_KEY]: this.insertKeyCb,
[CallbackType.SIGN]: this.signCb,
};
if (!callbackMapping[callbackType]) {
throw new Error(`Callback ${callbackType} not available`);
}
const callbackFunction = callbackMapping[callbackType];
let result = callbackFunction.apply(this, args);
if (result instanceof Promise) {
result = await result;
}
this.worker.postMessage({
callbackResult: result,
callbackRequestId: requestId,
});
} catch (error) {
this.worker.postMessage({
callbackError: error.message,
callbackRequestId: requestId,
});
}
return;
}
const { requestId, error, result, methodName } = data;
if (requestId && this.pendingRequests.has(requestId)) {
const { resolve, reject } = this.pendingRequests.get(requestId);
this.pendingRequests.delete(requestId);
if (error) {
const workerError =
error instanceof Error ? error : deserializeError(error);
console.error(
`WebClient: Error from worker in ${methodName}:`,
workerError
);
reject(workerError);
} else {
resolve(result);
}
}
});
this.loaded.then(() => this.initializeWorker());
} else {
console.log("WebClient: Web Workers are not available.");
this.worker = null;
this.pendingRequests = null;
this.loaded = Promise.resolve();
this.ready = Promise.resolve();
}
this.wasmWebClient = null;
this.wasmWebClientPromise = null;
this._wasmCallChain = Promise.resolve();
}
_serializeWasmCall(fn) {
const result = this._wasmCallChain.catch(() => {}).then(fn);
this._wasmCallChain = result.catch(() => {});
return result;
}
initializeWorker() {
this.worker.postMessage({
action: WorkerAction.INIT,
args: [
this.rpcUrl,
this.noteTransportUrl,
this.seed,
this.storeName,
!!this.getKeyCb,
!!this.insertKeyCb,
!!this.signCb,
this.logLevel,
],
});
}
async getWasmWebClient() {
if (this.wasmWebClient) {
return this.wasmWebClient;
}
if (!this.wasmWebClientPromise) {
this.wasmWebClientPromise = (async () => {
const wasm = await getWasmOrThrow();
const client = new wasm.WebClient();
this.wasmWebClient = client;
return client;
})();
}
return this.wasmWebClientPromise;
}
static async createClient(rpcUrl, noteTransportUrl, seed, network, logLevel) {
const instance = new WebClient(
rpcUrl,
noteTransportUrl,
seed,
network,
undefined,
undefined,
undefined,
logLevel
);
if (logLevel) {
const wasm = await getWasmOrThrow();
wasm.setupLogging(logLevel);
}
const wasmWebClient = await instance.getWasmWebClient();
await wasmWebClient.createClient(rpcUrl, noteTransportUrl, seed, network);
await instance.ready;
return createClientProxy(instance);
}
static async createClientWithExternalKeystore(
rpcUrl,
noteTransportUrl,
seed,
storeName,
getKeyCb,
insertKeyCb,
signCb,
logLevel
) {
const instance = new WebClient(
rpcUrl,
noteTransportUrl,
seed,
storeName,
getKeyCb,
insertKeyCb,
signCb,
logLevel
);
if (logLevel) {
const wasm = await getWasmOrThrow();
wasm.setupLogging(logLevel);
}
const wasmWebClient = await instance.getWasmWebClient();
await wasmWebClient.createClientWithExternalKeystore(
rpcUrl,
noteTransportUrl,
seed,
storeName,
getKeyCb,
insertKeyCb,
signCb
);
await instance.ready;
return createClientProxy(instance);
}
async callMethodWithWorker(methodName, ...args) {
await this.ready;
const requestId = `${methodName}-${Date.now()}-${Math.random()}`;
return new Promise((resolve, reject) => {
this.pendingRequests.set(requestId, { resolve, reject });
this.worker.postMessage({
action: WorkerAction.CALL_METHOD,
methodName,
args,
requestId,
});
});
}
async newWallet(storageMode, mutable, authSchemeId, seed) {
return this._serializeWasmCall(async () => {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.newWallet(
storageMode,
mutable,
authSchemeId,
seed
);
});
}
async newFaucet(
storageMode,
nonFungible,
tokenSymbol,
decimals,
maxSupply,
authSchemeId
) {
return this._serializeWasmCall(async () => {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.newFaucet(
storageMode,
nonFungible,
tokenSymbol,
decimals,
maxSupply,
authSchemeId
);
});
}
async newAccount(account, overwrite) {
return this._serializeWasmCall(async () => {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.newAccount(account, overwrite);
});
}
async newAccountWithSecretKey(account, secretKey) {
return this._serializeWasmCall(async () => {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.newAccountWithSecretKey(account, secretKey);
});
}
async submitNewTransaction(accountId, transactionRequest) {
try {
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.submitNewTransaction(
accountId,
transactionRequest
);
}
const wasm = await getWasmOrThrow();
const serializedTransactionRequest = transactionRequest.serialize();
const result = await this.callMethodWithWorker(
MethodName.SUBMIT_NEW_TRANSACTION,
accountId.toString(),
serializedTransactionRequest
);
const transactionResult = wasm.TransactionResult.deserialize(
new Uint8Array(result.serializedTransactionResult)
);
return transactionResult.id();
} catch (error) {
console.error("INDEX.JS: Error in submitNewTransaction:", error);
throw error;
}
}
async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
try {
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.submitNewTransactionWithProver(
accountId,
transactionRequest,
prover
);
}
const wasm = await getWasmOrThrow();
const serializedTransactionRequest = transactionRequest.serialize();
const proverPayload = prover.serialize();
const result = await this.callMethodWithWorker(
MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER,
accountId.toString(),
serializedTransactionRequest,
proverPayload
);
const transactionResult = wasm.TransactionResult.deserialize(
new Uint8Array(result.serializedTransactionResult)
);
return transactionResult.id();
} catch (error) {
console.error(
"INDEX.JS: Error in submitNewTransactionWithProver:",
error
);
throw error;
}
}
async executeTransaction(accountId, transactionRequest) {
try {
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.executeTransaction(
accountId,
transactionRequest
);
}
const wasm = await getWasmOrThrow();
const serializedTransactionRequest = transactionRequest.serialize();
const serializedResultBytes = await this.callMethodWithWorker(
MethodName.EXECUTE_TRANSACTION,
accountId.toString(),
serializedTransactionRequest
);
return wasm.TransactionResult.deserialize(
new Uint8Array(serializedResultBytes)
);
} catch (error) {
console.error("INDEX.JS: Error in executeTransaction:", error);
throw error;
}
}
async proveTransaction(transactionResult, prover) {
try {
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.proveTransaction(transactionResult, prover);
}
const wasm = await getWasmOrThrow();
const serializedTransactionResult = transactionResult.serialize();
const proverPayload = prover ? prover.serialize() : null;
const serializedProvenBytes = await this.callMethodWithWorker(
MethodName.PROVE_TRANSACTION,
serializedTransactionResult,
proverPayload
);
return wasm.ProvenTransaction.deserialize(
new Uint8Array(serializedProvenBytes)
);
} catch (error) {
console.error("INDEX.JS: Error in proveTransaction:", error);
throw error;
}
}
async applyTransaction(transactionResult, submissionHeight) {
try {
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
return await wasmWebClient.applyTransaction(
transactionResult,
submissionHeight
);
}
const wasm = await getWasmOrThrow();
const serializedTransactionResult = transactionResult.serialize();
const serializedUpdateBytes = await this.callMethodWithWorker(
MethodName.APPLY_TRANSACTION,
serializedTransactionResult,
submissionHeight
);
return wasm.TransactionStoreUpdate.deserialize(
new Uint8Array(serializedUpdateBytes)
);
} catch (error) {
console.error("INDEX.JS: Error in applyTransaction:", error);
throw error;
}
}
async syncState() {
return this.syncStateWithTimeout(0);
}
async syncStateWithTimeout(timeoutMs = 0) {
const dbId = this.storeName || "default";
try {
const lockHandle = await acquireSyncLock(dbId, timeoutMs);
if (!lockHandle.acquired) {
return lockHandle.coalescedResult;
}
try {
let result;
if (!this.worker) {
const wasmWebClient = await this.getWasmWebClient();
result = await wasmWebClient.syncStateImpl();
} else {
const wasm = await getWasmOrThrow();
const serializedSyncSummaryBytes = await this.callMethodWithWorker(
MethodName.SYNC_STATE
);
result = wasm.SyncSummary.deserialize(
new Uint8Array(serializedSyncSummaryBytes)
);
}
releaseSyncLock(dbId, result);
return result;
} catch (error) {
releaseSyncLockWithError(dbId, error);
throw error;
}
} catch (error) {
console.error("INDEX.JS: Error in syncState:", error);
throw error;
}
}
terminate() {
if (this.worker) {
this.worker.terminate();
}
}
}
class MockWebClient extends WebClient {
constructor(seed, logLevel) {
super(
null,
null,
seed,
MOCK_STORE_NAME,
undefined,
undefined,
undefined,
logLevel
);
}
initializeWorker() {
this.worker.postMessage({
action: WorkerAction.INIT_MOCK,
args: [this.seed, this.logLevel],
});
}
static async createClient(
serializedMockChain,
serializedMockNoteTransportNode,
seed,
logLevel
) {
const instance = new MockWebClient(seed, logLevel);
if (logLevel) {
const wasm = await getWasmOrThrow();
wasm.setupLogging(logLevel);
}
const wasmWebClient = await instance.getWasmWebClient();
await wasmWebClient.createMockClient(
seed,
serializedMockChain,
serializedMockNoteTransportNode
);
await instance.ready;
return createClientProxy(instance);
}
async syncState() {
return this.syncStateWithTimeout(0);
}
async syncStateWithTimeout(timeoutMs = 0) {
const dbId = this.storeName || "mock";
try {
const lockHandle = await acquireSyncLock(dbId, timeoutMs);
if (!lockHandle.acquired) {
return lockHandle.coalescedResult;
}
try {
let result;
const wasmWebClient = await this.getWasmWebClient();
if (!this.worker) {
result = await wasmWebClient.syncStateImpl();
} else {
let serializedMockChain = wasmWebClient.serializeMockChain().buffer;
let serializedMockNoteTransportNode =
wasmWebClient.serializeMockNoteTransportNode().buffer;
const wasm = await getWasmOrThrow();
const serializedSyncSummaryBytes = await this.callMethodWithWorker(
MethodName.SYNC_STATE_MOCK,
serializedMockChain,
serializedMockNoteTransportNode
);
result = wasm.SyncSummary.deserialize(
new Uint8Array(serializedSyncSummaryBytes)
);
}
releaseSyncLock(dbId, result);
return result;
} catch (error) {
releaseSyncLockWithError(dbId, error);
throw error;
}
} catch (error) {
console.error("INDEX.JS: Error in syncState:", error);
throw error;
}
}
async submitNewTransaction(accountId, transactionRequest) {
try {
if (!this.worker) {
return await super.submitNewTransaction(accountId, transactionRequest);
}
const wasmWebClient = await this.getWasmWebClient();
const wasm = await getWasmOrThrow();
const serializedTransactionRequest = transactionRequest.serialize();
const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
const serializedMockNoteTransportNode =
wasmWebClient.serializeMockNoteTransportNode().buffer;
const result = await this.callMethodWithWorker(
MethodName.SUBMIT_NEW_TRANSACTION_MOCK,
accountId.toString(),
serializedTransactionRequest,
serializedMockChain,
serializedMockNoteTransportNode
);
const newMockChain = new Uint8Array(result.serializedMockChain);
const newMockNoteTransportNode = result.serializedMockNoteTransportNode
? new Uint8Array(result.serializedMockNoteTransportNode)
: undefined;
const transactionResult = wasm.TransactionResult.deserialize(
new Uint8Array(result.serializedTransactionResult)
);
if (!(this instanceof MockWebClient)) {
return transactionResult.id();
}
this.wasmWebClient = new wasm.WebClient();
this.wasmWebClientPromise = Promise.resolve(this.wasmWebClient);
await this.wasmWebClient.createMockClient(
this.seed,
newMockChain,
newMockNoteTransportNode
);
return transactionResult.id();
} catch (error) {
console.error("INDEX.JS: Error in submitNewTransaction:", error);
throw error;
}
}
async submitNewTransactionWithProver(accountId, transactionRequest, prover) {
try {
if (!this.worker) {
return await super.submitNewTransactionWithProver(
accountId,
transactionRequest,
prover
);
}
const wasmWebClient = await this.getWasmWebClient();
const wasm = await getWasmOrThrow();
const serializedTransactionRequest = transactionRequest.serialize();
const proverPayload = prover.serialize();
const serializedMockChain = wasmWebClient.serializeMockChain().buffer;
const serializedMockNoteTransportNode =
wasmWebClient.serializeMockNoteTransportNode().buffer;
const result = await this.callMethodWithWorker(
MethodName.SUBMIT_NEW_TRANSACTION_WITH_PROVER_MOCK,
accountId.toString(),
serializedTransactionRequest,
proverPayload,
serializedMockChain,
serializedMockNoteTransportNode
);
const newMockChain = new Uint8Array(result.serializedMockChain);
const newMockNoteTransportNode = result.serializedMockNoteTransportNode
? new Uint8Array(result.serializedMockNoteTransportNode)
: undefined;
const transactionResult = wasm.TransactionResult.deserialize(
new Uint8Array(result.serializedTransactionResult)
);
if (!(this instanceof MockWebClient)) {
return transactionResult.id();
}
this.wasmWebClient = new wasm.WebClient();
this.wasmWebClientPromise = Promise.resolve(this.wasmWebClient);
await this.wasmWebClient.createMockClient(
this.seed,
newMockChain,
newMockNoteTransportNode
);
return transactionResult.id();
} catch (error) {
console.error(
"INDEX.JS: Error in submitNewTransactionWithProver:",
error
);
throw error;
}
}
}
function copyWebClientStatics(WasmWebClient) {
if (!WasmWebClient) {
return;
}
Object.getOwnPropertyNames(WasmWebClient).forEach((prop) => {
if (
typeof WasmWebClient[prop] === "function" &&
prop !== "constructor" &&
prop !== "prototype"
) {
WebClient[prop] = WasmWebClient[prop];
}
});
}
MidenClient._WasmWebClient = WebClient;
MidenClient._MockWasmWebClient = MockWebClient;
MidenClient._getWasmOrThrow = getWasmOrThrow;
_setStandaloneWebClient(WebClient);