diff --git a/.github/workflows/full_check.yaml b/.github/workflows/full_check.yaml index 3119c301..3d253ce0 100644 --- a/.github/workflows/full_check.yaml +++ b/.github/workflows/full_check.yaml @@ -808,7 +808,7 @@ jobs: - name: Run tests run: | cd impls/monero.ts - deno run --unstable-ffi --allow-ffi monero_bindings.ts + deno run --unstable-ffi --allow-ffi checksum.ts comment_pr: name: comment on pr diff --git a/impls/monero.ts/README.md b/impls/monero.ts/README.md index 1d318abf..27a2df80 100644 --- a/impls/monero.ts/README.md +++ b/impls/monero.ts/README.md @@ -1 +1,4 @@ -# monero.ts wip library \ No newline at end of file +# monero.ts + +# Usage +monero.ts uses Deno as its runtime of choice diff --git a/impls/monero.ts/checksum.ts b/impls/monero.ts/checksum.ts new file mode 100644 index 00000000..e923e40f --- /dev/null +++ b/impls/monero.ts/checksum.ts @@ -0,0 +1,63 @@ +import { moneroChecksum } from "./checksum_monero.ts"; +import { readCString } from "./src/utils.ts"; +import { dylib } from "./src/bindings.ts"; + +export class ChecksumError extends Error { + readonly code: number; + readonly errors: string[]; + + constructor(code: number, errors: string[]) { + super("MoneroC binding checksum failed:\n" + errors.join("\n")); + this.code = code; + this.errors = errors; + } +} + +/** + * Validates MoneroC checksums + * @returns {null} if checksums are correct + * @returns {ChecksumError} which contains information about why checksum failed + */ +export async function validateChecksum(): Promise { + const cppHeaderHash = readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_h()); + const tsHeaderHash = moneroChecksum.wallet2_api_c_h_sha256; + + const errors: string[] = []; + + let errorCode = 0; + if (cppHeaderHash !== tsHeaderHash) { + errors.push("ERR: Header file check mismatch"); + errorCode++; + } + + const cppSourceHash = readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_cpp()); + const tsSourceHash = moneroChecksum.wallet2_api_c_cpp_sha256; + if (cppSourceHash !== tsSourceHash) { + errors.push(`ERR: CPP source file check mismatch ${cppSourceHash} == ${tsSourceHash}`); + errorCode++; + } + + const cppExportHash = readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_exp()); + const tsExportHash = moneroChecksum.wallet2_api_c_exp_sha256; + if (cppExportHash !== tsExportHash) { + if (Deno.build.os !== "darwin") { + errors.push("WARN: EXP source file check mismatch"); + } else { + errors.push(`ERR: EXP source file check mismatch ${cppExportHash} == ${tsExportHash}`); + } + errorCode++; + } + + if (errorCode) { + return new ChecksumError(errorCode, errors); + } + + return null; +} + +if (import.meta.main) { + const maybeError = await validateChecksum(); + if (maybeError) { + throw maybeError; + } +} diff --git a/impls/monero.ts/deno.jsonc b/impls/monero.ts/deno.jsonc new file mode 100644 index 00000000..a7b75eca --- /dev/null +++ b/impls/monero.ts/deno.jsonc @@ -0,0 +1,5 @@ +{ + "fmt": { + "lineWidth": 120 + } +} diff --git a/impls/monero.ts/mod.ts b/impls/monero.ts/mod.ts new file mode 100644 index 00000000..1eca7734 --- /dev/null +++ b/impls/monero.ts/mod.ts @@ -0,0 +1,6 @@ +export * from "./src/bindings.ts"; +export * from "./src/pending_transaction.ts"; +export * from "./src/transaction_history.ts"; +export * from "./src/transaction_info.ts"; +export * from "./src/wallet.ts"; +export * from "./src/wallet_manager.ts"; diff --git a/impls/monero.ts/monero_bindings.ts b/impls/monero.ts/monero_bindings.ts deleted file mode 100644 index 13145253..00000000 --- a/impls/monero.ts/monero_bindings.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { exit, platform } from "node:process"; -import { moneroChecksum } from "./checksum_monero.ts"; -const libPath = () => { - if (platform == 'win32') return './lib/monero_libwallet2_api_c.dll' - // ios missing, if you ever happen to run this on iOS - // 1) seek help - // 2) return - // 2.1) if compliant with app store - MoneroWallet.framework/MoneroWallet - // 2.2) if hacked around 'monero_libwallet2_api_c.dylib' - if (platform == 'darwin') return './lib/monero_libwallet2_api_c.dylib' - if (platform == 'android') return './lib/libmonero_libwallet2_api_c.so' - return './lib/monero_libwallet2_api_c.so' -}; - -export const dylib = Deno.dlopen(libPath(), { - "MONERO_WalletManagerFactory_getWalletManager": { - nonblocking: true, - parameters: [], - // void* - result: "pointer", - }, - - //#region WalletManager - "MONERO_WalletManager_createWallet": { - nonblocking: true, - // void* wm_ptr, const char* path, const char* password, const char* language, int networkType - parameters: ["pointer", "pointer", "pointer", "pointer", "i32"], - // void* - result: "pointer", - }, - "MONERO_WalletManager_openWallet": { - nonblocking: true, - // void* wm_ptr, const char* path, const char* password, int networkType - "parameters": ["pointer", "pointer", "pointer", "i32"], - // void* - result: "pointer", - }, - "MONERO_WalletManager_recoveryWallet": { - nonblocking: true, - // void* wm_ptr, const char* path, const char* password, const char* mnemonic, int networkType, uint64_t restoreHeight, uint64_t kdfRounds, const char* seedOffset - parameters: ["pointer", "pointer", "pointer", "pointer", "i32", "u64", "u64", "pointer"], - // void* - result: "pointer", - }, - //#endregion - - //#region Wallet - "MONERO_Wallet_address": { - nonblocking: true, - // void* wallet_ptr, uint64_t accountIndex, uint64_t addressIndex - parameters: ["pointer", "u64", "u64"], - // char* - result: "pointer", - }, - "MONERO_Wallet_balance": { - nonblocking: true, - // void* wallet_ptr, uint32_t accountIndex - parameters: ["pointer", "u32"], - // uint64_t - result: "u64", - }, - "MONERO_Wallet_status": { - nonblocking: true, - // void* wallet_ptr - parameters: ["pointer"], - // int - result: "i32", - }, - "MONERO_Wallet_errorString": { - nonblocking: true, - // void* wallet_ptr - parameters: ["pointer"], - // char* - result: "pointer", - }, - "MONERO_Wallet_blockChainHeight": { - nonblocking: true, - // void* wallet_ptr - parameters: ["pointer"], - // uint64_t - result: "u64", - }, - "MONERO_Wallet_daemonBlockChainHeight": { - nonblocking: true, - // void* wallet_ptr - parameters: ["pointer"], - // uint64_t - result: "u64", - }, - "MONERO_checksum_wallet2_api_c_h": { - parameters: [], - result: "pointer" - }, - "MONERO_checksum_wallet2_api_c_cpp": { - parameters: [], - result: "pointer" - }, - "MONERO_checksum_wallet2_api_c_exp": { - parameters: [], - result: "pointer" - } - //#endregion -}); - -if (import.meta.main) { - const hashHcpp = new Deno.UnsafePointerView(dylib.symbols.MONERO_checksum_wallet2_api_c_h()!).getCString(); - const hashHts = moneroChecksum.wallet2_api_c_h_sha256; - const hashCPPcpp = new Deno.UnsafePointerView(dylib.symbols.MONERO_checksum_wallet2_api_c_cpp()!).getCString(); - const hashCPPts = moneroChecksum.wallet2_api_c_cpp_sha256; - const hashEXPcpp = new Deno.UnsafePointerView(dylib.symbols.MONERO_checksum_wallet2_api_c_exp()!).getCString(); - const hashEXPts = moneroChecksum.wallet2_api_c_exp_sha256; - let errCode = 0; - if (hashHcpp == hashHts) { - console.log("Header file check match") - } else { - console.log("ERR: Header file check mismatch") - errCode++; - } - if (hashCPPcpp == hashCPPts) { - console.log("CPP source file check match") - } else { - console.log(`ERR: CPP source file check mismatch ${hashCPPcpp} == ${hashCPPts}`) - errCode++; - } - if (hashEXPcpp == hashEXPts) { - console.log("EXP file check match") - } else { - if (platform != "darwin") { - console.log("WARN: EXP source file check mismatch") - } else { - console.log(`ERR: EXP source file check mismatch ${hashEXPcpp} == ${hashEXPts}`) - } - errCode++; - } - exit(errCode); -} \ No newline at end of file diff --git a/impls/monero.ts/src/bindings.ts b/impls/monero.ts/src/bindings.ts new file mode 100644 index 00000000..6762533b --- /dev/null +++ b/impls/monero.ts/src/bindings.ts @@ -0,0 +1,542 @@ +let libPath: string; +switch (Deno.build.os) { + case "darwin": + libPath = "./lib/monero_libwallet2_api_c.dylib"; + break; + case "android": + libPath = "./lib/libmonero_libwallet2_api_c.so"; + break; + case "windows": + libPath = "./lib/monero_libwallet2_api_c.dll"; + break; + default: + libPath = "./lib/monero_libwallet2_api_c.so"; + break; +} + +export const dylib = Deno.dlopen(libPath, { + "MONERO_WalletManagerFactory_getWalletManager": { + nonblocking: true, + parameters: [], + // void* + result: "pointer", + }, + + //#region WalletManager + "MONERO_WalletManager_createWallet": { + nonblocking: true, + // void* wm_ptr, const char* path, const char* password, const char* language, int networkType + parameters: ["pointer", "pointer", "pointer", "pointer", "i32"], + // void* + result: "pointer", + }, + "MONERO_WalletManager_openWallet": { + nonblocking: true, + // void* wm_ptr, const char* path, const char* password, int networkType + "parameters": ["pointer", "pointer", "pointer", "i32"], + // void* + result: "pointer", + }, + "MONERO_WalletManager_recoveryWallet": { + nonblocking: true, + // void* wm_ptr, const char* path, const char* password, const char* mnemonic, + // int networkType, uint64_t restoreHeight, uint64_t kdfRounds, const char* seedOffset + parameters: ["pointer", "pointer", "pointer", "pointer", "i32", "u64", "u64", "pointer"], + // void* + result: "pointer", + }, + "MONERO_WalletManager_blockchainHeight": { + nonblocking: true, + // void* wm_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_WalletManager_blockchainTargetHeight": { + nonblocking: true, + // void* wm_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_WalletManager_setDaemonAddress": { + nonblocking: true, + // void* wm_ptr, const char* address + parameters: ["pointer", "pointer"], + // void + result: "void", + }, + //#endregion + + //#region Wallet + "MONERO_Wallet_init": { + nonblocking: true, + // void* wallet_ptr, const char* daemon_address, uint64_t upper_transaction_size_limit, + // const char* daemon_username, const char* daemon_password, bool use_ssl, bool lightWallet, + // const char* proxy_address + parameters: ["pointer", "pointer", "u64", "pointer", "pointer", "bool", "bool", "pointer"], + // bool + result: "bool", + }, + "MONERO_Wallet_init3": { + nonblocking: true, + // void* wallet_ptr, const char* argv0, const char* default_log_base_name, + // const char* log_path, bool console + parameters: ["pointer", "pointer", "pointer", "pointer", "bool"], + // void + result: "void", + }, + "MONERO_Wallet_setTrustedDaemon": { + nonblocking: true, + // void* wallet_ptr, bool arg + parameters: ["pointer", "bool"], + // void + result: "void", + }, + "MONERO_Wallet_startRefresh": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // void + result: "void", + }, + "MONERO_Wallet_refreshAsync": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // void + result: "void", + }, + "MONERO_Wallet_blockChainHeight": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_Wallet_daemonBlockChainHeight": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_Wallet_synchronized": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // bool + result: "bool", + }, + "MONERO_Wallet_store": { + nonblocking: true, + // void* wallet_ptr, const char* path + parameters: ["pointer", "pointer"], + // bool + result: "bool", + }, + "MONERO_Wallet_address": { + nonblocking: true, + // void* wallet_ptr, uint64_t accountIndex, uint64_t addressIndex + parameters: ["pointer", "u64", "u64"], + // char* + result: "pointer", + }, + "MONERO_Wallet_balance": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex + parameters: ["pointer", "u32"], + // uint64_t + result: "u64", + }, + "MONERO_Wallet_unlockedBalance": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex + parameters: ["pointer", "u32"], + // uint64_t + result: "u64", + }, + "MONERO_Wallet_addSubaddressAccount": { + nonblocking: true, + // void* wallet_ptr, const char* label + parameters: ["pointer", "pointer"], + // void + result: "void", + }, + "MONERO_Wallet_numSubaddressAccounts": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // size_t + result: "usize", + }, + "MONERO_Wallet_addSubaddress": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex, const char* label + parameters: ["pointer", "u32", "pointer"], + // void + result: "void", + }, + "MONERO_Wallet_numSubaddresses": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex + parameters: ["pointer", "u32"], + // size_t + result: "usize", + }, + "MONERO_Wallet_getSubaddressLabel": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex, uint32_t addressIndex + parameters: ["pointer", "u32", "u32"], + // const char* + result: "pointer", + }, + "MONERO_Wallet_setSubaddressLabel": { + nonblocking: true, + // void* wallet_ptr, uint32_t accountIndex, uint32_t addressIndex, const char* label + parameters: ["pointer", "u32", "u32", "pointer"], + // void + result: "void", + }, + "MONERO_Wallet_status": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // int + result: "i32", + }, + "MONERO_Wallet_errorString": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // char* + result: "pointer", + }, + "MONERO_Wallet_history": { + nonblocking: true, + // void* wallet_ptr + parameters: ["pointer"], + // void* + result: "pointer", + }, + "MONERO_Wallet_createTransaction": { + nonblocking: true, + // void* wallet_ptr, const char* dst_addr, const char* payment_id + // uint64_t amount, uint32_t mixin_count, int pendingTransactionPriority, + // uint32_t subaddr_account, const char* preferredInputs, const char* separator + parameters: ["pointer", "pointer", "pointer", "u64", "u32", "i32", "u32", "pointer", "pointer"], + // void* + result: "pointer", + }, + "MONERO_Wallet_amountFromString": { + nonblocking: true, + // const char* amount + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + //#endregion + + //#region TransactionHistory + "MONERO_TransactionHistory_count": { + nonblocking: true, + // void* txHistory_ptr + parameters: ["pointer"], + // int + result: "i32", + }, + "MONERO_TransactionHistory_transaction": { + nonblocking: true, + // void* txHistory_ptr, int index + parameters: ["pointer", "i32"], + // void* + result: "pointer", + }, + "MONERO_TransactionHistory_transactionById": { + nonblocking: true, + // void* txHistory_ptr, const char* id + parameters: ["pointer", "pointer"], + // void* + result: "pointer", + }, + "MONERO_TransactionHistory_refresh": { + nonblocking: true, + // void* txHistory_ptr + parameters: ["pointer"], + // void + result: "void", + }, + "MONERO_TransactionHistory_setTxNote": { + nonblocking: true, + // void* txHistory_ptr, const char* txid, const char* note + parameters: ["pointer", "pointer", "pointer"], + // void + result: "void", + }, + //#endregion + + //#region TransactionInfo + "MONERO_TransactionInfo_direction": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // int + result: "i32", + }, + "MONERO_TransactionInfo_isPending": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // bool + result: "bool", + }, + "MONERO_TransactionInfo_isFailed": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // bool + result: "bool", + }, + "MONERO_TransactionInfo_isCoinbase": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // bool + result: "bool", + }, + "MONERO_TransactionInfo_amount": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_fee": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_blockHeight": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_description": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_TransactionInfo_subaddrIndex": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_TransactionInfo_subaddrAccount": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint32_t + result: "u32", + }, + "MONERO_TransactionInfo_label": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_TransactionInfo_confirmations": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_unlockTime": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_hash": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_TransactionInfo_timestamp": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_paymentId": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_TransactionInfo_transfers_count": { + nonblocking: true, + // void* txInfo_ptr + parameters: ["pointer"], + // int + result: "i32", + }, + "MONERO_TransactionInfo_transfers_amount": { + nonblocking: true, + // void* txInfo_ptr, int index + parameters: ["pointer", "i32"], + // uint64_t + result: "u64", + }, + "MONERO_TransactionInfo_transfers_address": { + nonblocking: true, + // void* txInfo_ptr, int index + parameters: ["pointer", "i32"], + // const char* + result: "pointer", + }, + //#endregion + + //#region PendingTransaction + "MONERO_PendingTransaction_status": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // int + result: "i32", + }, + "MONERO_PendingTransaction_errorString": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_commit": { + nonblocking: true, + // void* pendingTx_ptr, const char* filename, bool overwrite + parameters: ["pointer", "pointer", "bool"], + // bool + result: "bool", + }, + "MONERO_PendingTransaction_commitUR": { + nonblocking: true, + // void* pendingTx_ptr, int max_fragment_length + parameters: ["pointer", "i32"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_amount": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_PendingTransaction_dust": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_PendingTransaction_fee": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_PendingTransaction_txid": { + nonblocking: true, + // void* pendingTx_ptr, const char* separator + parameters: ["pointer", "pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_txCount": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // uint64_t + result: "u64", + }, + "MONERO_PendingTransaction_subaddrAccount": { + nonblocking: true, + // void* pendingTx_ptr, const char* separator + parameters: ["pointer", "pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_subaddrIndices": { + nonblocking: true, + // void* pendingTx_ptr, const char* separator + parameters: ["pointer", "pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_multisigSignData": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_signMultisigTx": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // void + result: "void", + }, + "MONERO_PendingTransaction_signersKeys": { + nonblocking: true, + // void* pendingTx_ptr + parameters: ["pointer"], + // const char* + result: "pointer", + }, + "MONERO_PendingTransaction_hex": { + nonblocking: true, + // void* pendingTx_ptr, const char* separator + parameters: ["pointer", "pointer"], + // const char* + result: "pointer", + }, + //#endregion + + //#region Checksum + "MONERO_checksum_wallet2_api_c_h": { + nonblocking: true, + parameters: [], + // const char* + result: "pointer", + }, + "MONERO_checksum_wallet2_api_c_cpp": { + nonblocking: true, + parameters: [], + // const char* + result: "pointer", + }, + "MONERO_checksum_wallet2_api_c_exp": { + nonblocking: true, + parameters: [], + // const char* + result: "pointer", + }, + //#endregion +}); diff --git a/impls/monero.ts/src/pending_transaction.ts b/impls/monero.ts/src/pending_transaction.ts new file mode 100644 index 00000000..e734c759 --- /dev/null +++ b/impls/monero.ts/src/pending_transaction.ts @@ -0,0 +1,81 @@ +import { dylib } from "./bindings.ts"; +import { CString, readCString, type Sanitizer } from "./utils.ts"; + +export type PendingTransactionPtr = Deno.PointerObject<"transactionInfo">; + +export class PendingTransaction { + #pendingTxPtr: PendingTransactionPtr; + sanitizer?: Sanitizer; + + constructor(pendingTxPtr: PendingTransactionPtr, sanitizer?: Sanitizer) { + this.sanitizer = sanitizer; + this.#pendingTxPtr = pendingTxPtr; + } + + async status(): Promise { + return await dylib.symbols.MONERO_PendingTransaction_status(this.#pendingTxPtr); + } + + async errorString(): Promise { + if (!await this.status()) return null; + + const error = await dylib.symbols.MONERO_PendingTransaction_errorString(this.#pendingTxPtr); + if (!error) return null; + + return readCString(error) || null; + } + + async throwIfError(sanitize = true): Promise { + const maybeError = await this.errorString(); + if (maybeError) { + if (sanitize) this.sanitizer?.(); + throw new Error(maybeError); + } + } + + async commit(fileName: string, overwrite: boolean, sanitize = true): Promise { + const bool = await dylib.symbols.MONERO_PendingTransaction_commit( + this.#pendingTxPtr, + CString(fileName), + overwrite, + ); + await this.throwIfError(sanitize); + return bool; + } + + async commitUR(maxFragmentLength: number): Promise { + const result = await dylib.symbols.MONERO_PendingTransaction_commitUR( + this.#pendingTxPtr, + maxFragmentLength, + ); + if (!result) return null; + await this.throwIfError(); + return readCString(result) || null; + } + + async amount(): Promise { + return await dylib.symbols.MONERO_PendingTransaction_amount(this.#pendingTxPtr); + } + + async dust(): Promise { + return await dylib.symbols.MONERO_PendingTransaction_dust(this.#pendingTxPtr); + } + + async fee(): Promise { + return await dylib.symbols.MONERO_PendingTransaction_fee(this.#pendingTxPtr); + } + + async txid(separator: string, sanitize = true): Promise { + const result = await dylib.symbols.MONERO_PendingTransaction_txid( + this.#pendingTxPtr, + CString(separator), + ); + if (!result) return null; + await this.throwIfError(sanitize); + return readCString(result) || null; + } + + async txCount(): Promise { + return await dylib.symbols.MONERO_PendingTransaction_txCount(this.#pendingTxPtr); + } +} diff --git a/impls/monero.ts/src/transaction_history.ts b/impls/monero.ts/src/transaction_history.ts new file mode 100644 index 00000000..cc76fc25 --- /dev/null +++ b/impls/monero.ts/src/transaction_history.ts @@ -0,0 +1,38 @@ +import { dylib } from "./bindings.ts"; +import { TransactionInfo, TransactionInfoPtr } from "./transaction_info.ts"; +import { CString } from "./utils.ts"; + +export type TransactionHistoryPtr = Deno.PointerObject<"transactionHistory">; + +export class TransactionHistory { + #txHistoryPtr: TransactionHistoryPtr; + + constructor(txHistoryPtr: TransactionHistoryPtr) { + this.#txHistoryPtr = txHistoryPtr; + } + + async count(): Promise { + return await dylib.symbols.MONERO_TransactionHistory_count(this.#txHistoryPtr); + } + + async transaction(index: number): Promise { + return new TransactionInfo( + (await dylib.symbols.MONERO_TransactionHistory_transaction( + this.#txHistoryPtr, + index, + )) as TransactionInfoPtr, + ); + } + + async refresh(): Promise { + await dylib.symbols.MONERO_TransactionHistory_refresh(this.#txHistoryPtr); + } + + async setTxNote(transactionId: string, note: string): Promise { + await dylib.symbols.MONERO_TransactionHistory_setTxNote( + this.#txHistoryPtr, + CString(transactionId), + CString(note), + ); + } +} diff --git a/impls/monero.ts/src/transaction_info.ts b/impls/monero.ts/src/transaction_info.ts new file mode 100644 index 00000000..9c697375 --- /dev/null +++ b/impls/monero.ts/src/transaction_info.ts @@ -0,0 +1,104 @@ +import { dylib } from "./bindings.ts"; +import { readCString, Sanitizer } from "./utils.ts"; + +export type TransactionInfoPtr = Deno.PointerObject<"transactionInfo">; + +export class TransactionInfo { + #txInfoPtr: TransactionInfoPtr; + sanitizer?: Sanitizer; + + constructor(txInfoPtr: TransactionInfoPtr, sanitizer?: Sanitizer) { + this.#txInfoPtr = txInfoPtr; + this.sanitizer = sanitizer; + } + + async direction(): Promise<"in" | "out"> { + switch (await dylib.symbols.MONERO_TransactionInfo_direction(this.#txInfoPtr)) { + case 0: + return "in"; + case 1: + return "out"; + default: + await this.sanitizer?.(); + throw new Error("Invalid TransactionInfo direction"); + } + } + + async isPending(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_isPending(this.#txInfoPtr); + } + + async isFailed(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_isFailed(this.#txInfoPtr); + } + + async isCoinbase(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_isCoinbase(this.#txInfoPtr); + } + + async amount(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_amount(this.#txInfoPtr); + } + + async fee(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_fee(this.#txInfoPtr); + } + + async blockHeight(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_blockHeight(this.#txInfoPtr); + } + + async description(): Promise { + const description = await dylib.symbols.MONERO_TransactionInfo_description(this.#txInfoPtr); + return readCString(description) || ""; + } + + async subaddrIndex(): Promise { + const subaddrIndex = await dylib.symbols.MONERO_TransactionInfo_subaddrIndex(this.#txInfoPtr); + return readCString(subaddrIndex) || ""; + } + + async subaddrAccount(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_subaddrAccount(this.#txInfoPtr); + } + + async label(): Promise { + const label = await dylib.symbols.MONERO_TransactionInfo_label(this.#txInfoPtr); + return readCString(label) || ""; + } + + async confirmations(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_confirmations(this.#txInfoPtr); + } + + async unlockTime(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_unlockTime(this.#txInfoPtr); + } + + async hash(): Promise { + const hash = await dylib.symbols.MONERO_TransactionInfo_hash(this.#txInfoPtr); + return readCString(hash) || ""; + } + + async timestamp(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_timestamp(this.#txInfoPtr); + } + + async paymentId(): Promise { + const paymentId = await dylib.symbols.MONERO_TransactionInfo_paymentId(this.#txInfoPtr); + return readCString(paymentId) || ""; + } + + async transfersCount(): Promise { + return await dylib.symbols.MONERO_TransactionInfo_transfers_count(this.#txInfoPtr); + } + + async transfersAmount(index: number): Promise { + return await dylib.symbols.MONERO_TransactionInfo_transfers_amount(this.#txInfoPtr, index); + } + + async transfersAddress(index: number): Promise { + const transfersAddress = await dylib.symbols.MONERO_TransactionInfo_transfers_address(this.#txInfoPtr, index); + return readCString(transfersAddress) || ""; + } +} diff --git a/impls/monero.ts/src/utils.ts b/impls/monero.ts/src/utils.ts new file mode 100644 index 00000000..715dd7b9 --- /dev/null +++ b/impls/monero.ts/src/utils.ts @@ -0,0 +1,13 @@ +export type Sanitizer = () => void | PromiseLike; + +const textEncoder = new TextEncoder(); +export function CString(string: string): Deno.PointerValue { + return Deno.UnsafePointer.of(textEncoder.encode(`${string}\x00`)); +} + +export function readCString(pointer: Deno.PointerObject): string; +export function readCString(pointer: Deno.PointerValue): string | null; +export function readCString(pointer: Deno.PointerValue): string | null { + if (!pointer) return null; + return new Deno.UnsafePointerView(pointer).getCString(); +} diff --git a/impls/monero.ts/src/wallet.ts b/impls/monero.ts/src/wallet.ts new file mode 100644 index 00000000..0906e2b3 --- /dev/null +++ b/impls/monero.ts/src/wallet.ts @@ -0,0 +1,304 @@ +import { dylib } from "./bindings.ts"; +import { CString, readCString, Sanitizer } from "./utils.ts"; +import { WalletManager, type WalletManagerPtr } from "./wallet_manager.ts"; +import { TransactionHistory, TransactionHistoryPtr } from "./transaction_history.ts"; + +import { PendingTransaction } from "./pending_transaction.ts"; +import { PendingTransactionPtr } from "./pending_transaction.ts"; + +export type WalletPtr = Deno.PointerObject<"walletManager">; + +export class Wallet { + #walletManagerPtr: WalletManagerPtr; + #walletPtr: WalletPtr; + sanitizer?: Sanitizer; + + constructor(walletManagerPtr: WalletManager, walletPtr: WalletPtr, sanitizer?: Sanitizer) { + this.#walletPtr = walletPtr; + this.#walletManagerPtr = walletManagerPtr.getPointer(); + this.sanitizer = sanitizer; + } + + async store(path = ""): Promise { + const bool = await dylib.symbols.MONERO_Wallet_store(this.#walletPtr, CString(path)); + await this.throwIfError(); + return bool; + } + + async initWallet(): Promise { + await this.init(); + await this.setTrustedDaemon(true); + await this.setDaemonAddress("http://nodex.monerujo.io:18081"); + await this.startRefresh(); + await this.refreshAsync(); + await this.throwIfError(); + } + + async setDaemonAddress(address: string): Promise { + await dylib.symbols.MONERO_WalletManager_setDaemonAddress( + this.#walletManagerPtr, + CString(address), + ); + } + + async startRefresh(): Promise { + await dylib.symbols.MONERO_Wallet_startRefresh(this.#walletPtr); + await this.throwIfError(); + } + + async refreshAsync(): Promise { + await dylib.symbols.MONERO_Wallet_refreshAsync(this.#walletPtr); + await this.throwIfError(); + } + + async init(): Promise { + const bool = await dylib.symbols.MONERO_Wallet_init( + this.#walletPtr, + CString("http://nodex.monerujo.io:18081"), + 0n, + CString(""), + CString(""), + false, + false, + CString(""), + ); + await this.throwIfError(); + return bool; + } + + async setTrustedDaemon(value: boolean): Promise { + await dylib.symbols.MONERO_Wallet_setTrustedDaemon(this.#walletPtr, value); + } + + static async create( + walletManager: WalletManager, + path: string, + password: string, + sanitizeError = true, + ): Promise { + // We assign holder of the pointer in Wallet constructor + const walletManagerPtr = walletManager.getPointer(); + + const walletPtr = await dylib.symbols.MONERO_WalletManager_createWallet( + walletManagerPtr, + CString(path), + CString(password), + CString("English"), + 0, + ); + + const wallet = new Wallet(walletManager, walletPtr as WalletPtr, walletManager.sanitizer); + await wallet.throwIfError(sanitizeError); + await wallet.initWallet(); + + return wallet; + } + + static async open( + walletManager: WalletManager, + path: string, + password: string, + sanitizeError = true, + ): Promise { + // We assign holder of the pointer in Wallet constructor + const walletManagerPtr = walletManager.getPointer(); + + const walletPtr = await dylib.symbols.MONERO_WalletManager_openWallet( + walletManagerPtr, + CString(path), + CString(password), + 0, + ); + + const wallet = new Wallet(walletManager, walletPtr as WalletPtr, walletManager.sanitizer); + await wallet.throwIfError(sanitizeError); + await wallet.initWallet(); + + return wallet; + } + + static async recover( + walletManager: WalletManager, + path: string, + password: string, + mnemonic: string, + restoreHeight: bigint, + seedOffset: string = "", + sanitizeError = true, + ): Promise { + // We assign holder of the pointer in Wallet constructor + const walletManagerPtr = walletManager.getPointer(); + + const walletPtr = await dylib.symbols.MONERO_WalletManager_recoveryWallet( + walletManagerPtr, + CString(path), + CString(password), + CString(mnemonic), + 0, + restoreHeight, + 1n, + CString(seedOffset), + ); + + const wallet = new Wallet(walletManager, walletPtr as WalletPtr, walletManager.sanitizer); + await wallet.throwIfError(sanitizeError); + await wallet.initWallet(); + + return wallet; + } + + async address(accountIndex = 0n, addressIndex = 0n): Promise { + const address = await dylib.symbols.MONERO_Wallet_address(this.#walletPtr, accountIndex, addressIndex); + if (!address) { + const error = await this.errorString(); + throw new Error(`Failed getting address from a wallet: ${error ?? ""}`); + } + return readCString(address); + } + + async balance(accountIndex = 0): Promise { + return await dylib.symbols.MONERO_Wallet_balance(this.#walletPtr, accountIndex); + } + + async unlockedBalance(accountIndex = 0): Promise { + return await dylib.symbols.MONERO_Wallet_unlockedBalance(this.#walletPtr, accountIndex); + } + + status(): Promise { + return dylib.symbols.MONERO_Wallet_status(this.#walletPtr); + } + + async errorString(): Promise { + if (!await this.status()) return null; + + const error = await dylib.symbols.MONERO_Wallet_errorString(this.#walletPtr); + if (!error) return null; + + return readCString(error) || null; + } + + async throwIfError(sanitize = true): Promise { + const maybeError = await this.errorString(); + if (maybeError) { + if (sanitize) this.sanitizer?.(); + throw new Error(maybeError); + } + } + + async synchronized(): Promise { + const synchronized = await dylib.symbols.MONERO_Wallet_synchronized(this.#walletPtr); + await this.throwIfError(); + return synchronized; + } + + async blockChainHeight(): Promise { + const height = await dylib.symbols.MONERO_Wallet_blockChainHeight(this.#walletPtr); + await this.throwIfError(); + return height; + } + + async daemonBlockChainHeight(): Promise { + const height = await dylib.symbols.MONERO_Wallet_daemonBlockChainHeight(this.#walletPtr); + await this.throwIfError(); + return height; + } + + async managerBlockChainHeight(): Promise { + const height = await dylib.symbols.MONERO_WalletManager_blockchainHeight(this.#walletManagerPtr); + await this.throwIfError(); + return height; + } + + async managerTargetBlockChainHeight(): Promise { + const height = await dylib.symbols.MONERO_WalletManager_blockchainTargetHeight(this.#walletManagerPtr); + await this.throwIfError(); + return height; + } + + async addSubaddressAccount(label: string): Promise { + await dylib.symbols.MONERO_Wallet_addSubaddressAccount( + this.#walletPtr, + CString(label), + ); + await this.throwIfError(); + } + + async numSubaddressAccounts(): Promise { + const accountsLen = await dylib.symbols.MONERO_Wallet_numSubaddressAccounts(this.#walletPtr); + await this.throwIfError(); + return accountsLen; + } + + async addSubaddress(accountIndex: number, label: string): Promise { + await dylib.symbols.MONERO_Wallet_addSubaddress( + this.#walletPtr, + accountIndex, + CString(label), + ); + await this.throwIfError(); + } + + async numSubaddresses(accountIndex: number): Promise { + const address = await dylib.symbols.MONERO_Wallet_numSubaddresses( + this.#walletPtr, + accountIndex, + ); + await this.throwIfError(); + return address; + } + + async getSubaddressLabel(accountIndex: number, addressIndex: number): Promise { + const label = await dylib.symbols.MONERO_Wallet_getSubaddressLabel(this.#walletPtr, accountIndex, addressIndex); + if (!label) { + const error = await this.errorString(); + throw new Error(`Failed getting subaddress label from a wallet: ${error ?? ""}`); + } + return readCString(label); + } + + async setSubaddressLabel(accountIndex: number, addressIndex: number, label: string): Promise { + await dylib.symbols.MONERO_Wallet_setSubaddressLabel( + this.#walletPtr, + accountIndex, + addressIndex, + CString(label), + ); + await this.throwIfError(); + } + + async getHistory(): Promise { + const transactionHistoryPointer = await dylib.symbols.MONERO_Wallet_history(this.#walletPtr); + await this.throwIfError(); + return new TransactionHistory(transactionHistoryPointer as TransactionHistoryPtr); + } + + async createTransaction( + destinationAddress: string, + amount: bigint, + pendingTransactionPriority = 0 | 1 | 2 | 3, + subaddressAccount: number, + sanitize = true, + prefferedInputs = "", + mixinCount = 0, + paymentId = "", + separator = ",", + ): Promise { + const pendingTxPtr = await dylib.symbols.MONERO_Wallet_createTransaction( + this.#walletPtr, + CString(destinationAddress), + CString(paymentId), + amount, + mixinCount, + pendingTransactionPriority, + subaddressAccount, + CString(prefferedInputs), + CString(separator), + ); + await this.throwIfError(sanitize); + return new PendingTransaction(pendingTxPtr as PendingTransactionPtr); + } + + async amountFromString(amount: string): Promise { + return await dylib.symbols.MONERO_Wallet_amountFromString(CString(amount)); + } +} diff --git a/impls/monero.ts/src/wallet_manager.ts b/impls/monero.ts/src/wallet_manager.ts new file mode 100644 index 00000000..ad9cf315 --- /dev/null +++ b/impls/monero.ts/src/wallet_manager.ts @@ -0,0 +1,27 @@ +import { dylib } from "./bindings.ts"; +import { Sanitizer } from "./utils.ts"; + +export type WalletManagerPtr = Deno.PointerObject<"walletManager">; + +export class WalletManager { + #ptr: WalletManagerPtr; + sanitizer?: Sanitizer; + + constructor(walletManagerPtr: WalletManagerPtr, sanitizer?: Sanitizer) { + this.#ptr = walletManagerPtr; + this.sanitizer = sanitizer; + } + + getPointer(): WalletManagerPtr { + return this.#ptr; + } + + static async new(sanitizer?: Sanitizer) { + const ptr = await dylib.symbols.MONERO_WalletManagerFactory_getWalletManager(); + if (!ptr) { + sanitizer?.(); + throw new Error("Failed retrieving wallet manager"); + } + return new WalletManager(ptr as WalletManagerPtr, sanitizer); + } +} diff --git a/impls/monero.ts/utils.ts b/impls/monero.ts/utils.ts deleted file mode 100644 index 0b6056f5..00000000 --- a/impls/monero.ts/utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -const textEncoder = new TextEncoder(); -export function CString(string: string): Deno.PointerValue { - return Deno.UnsafePointer.of(textEncoder.encode(`${string}\x00`)); -} diff --git a/impls/monero.ts/wallet.ts b/impls/monero.ts/wallet.ts deleted file mode 100644 index fc1e7265..00000000 --- a/impls/monero.ts/wallet.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { dylib } from "./monero_bindings.ts"; -import { CString } from "./utils.ts"; -import { WalletManager, type WalletManagerPtr } from "./wallet_manager.ts"; - -export type WalletPtr = Deno.PointerObject<"walletManager">; - -export class Wallet { - #walletManagerPtr: WalletManagerPtr; - #walletPtr: WalletPtr; - - constructor(walletManagerPtr: WalletManager, walletPtr: WalletPtr) { - this.#walletPtr = walletPtr; - this.#walletManagerPtr = walletManagerPtr.getPointer(this); - } - - async address(accountIndex = 0n, addressIndex = 0n): Promise { - const address = await dylib.symbols.MONERO_Wallet_address(this.#walletPtr, accountIndex, addressIndex); - if (!address) { - const error = await this.error(); - throw new Error(`Failed getting address from a wallet: ${error ?? ""}`); - } - return new Deno.UnsafePointerView(address).getCString(); - } - - async balance(accountIndex = 0): Promise { - return await dylib.symbols.MONERO_Wallet_balance(this.#walletPtr, accountIndex); - } - - static async create(walletManager: WalletManager, path: string, password: string): Promise { - // We assign holder of the pointer in Wallet constructor - const walletManagerPtr = walletManager.getPointer(); - - const walletPtr = await dylib.symbols.MONERO_WalletManager_createWallet( - walletManagerPtr, - CString(path), - CString(password), - CString("English"), - 0, - ); - - const wallet = new Wallet(walletManager, walletPtr as WalletPtr); - - const maybeError = await wallet.error(); - if (maybeError) throw new Error(maybeError); - - return wallet; - } - - static async open(walletManager: WalletManager, path: string, password: string): Promise { - // We assign holder of the pointer in Wallet constructor - const walletManagerPtr = walletManager.getPointer(); - - const walletPtr = await dylib.symbols.MONERO_WalletManager_openWallet( - walletManagerPtr, - CString(path), - CString(password), - 0, - ); - - const wallet = new Wallet(walletManager, walletPtr as WalletPtr); - - const maybeError = await wallet.error(); - if (maybeError) throw new Error(maybeError); - - return wallet; - } - - static async recover( - walletManager: WalletManager, - path: string, - password: string, - mnemonic: string, - restoreHeight: bigint = 0n, - seedOffset: string = "", - ): Promise { - // We assign holder of the pointer in Wallet constructor - const walletManagerPtr = walletManager.getPointer(); - - const walletPtr = await dylib.symbols.MONERO_WalletManager_recoveryWallet( - walletManagerPtr, - CString(path), - CString(password), - CString(mnemonic), - 0, - restoreHeight, - 1n, - CString(seedOffset), - ); - - const wallet = new Wallet(walletManager, walletPtr as WalletPtr); - - const maybeError = await wallet.error(); - if (maybeError) throw new Error(maybeError); - - return wallet; - } - - status(): Promise { - return dylib.symbols.MONERO_Wallet_status(this.#walletPtr); - } - - async error(): Promise { - if (!await this.status()) return null; - - const error = await dylib.symbols.MONERO_Wallet_errorString(this.#walletPtr); - if (!error) return null; - - const errorString = new Deno.UnsafePointerView(error).getCString(); - return errorString || null; - } -} diff --git a/impls/monero.ts/wallet_manager.ts b/impls/monero.ts/wallet_manager.ts deleted file mode 100644 index 61c158b6..00000000 --- a/impls/monero.ts/wallet_manager.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { dylib } from "./monero_bindings.ts"; - -export type WalletManagerPtr = Deno.PointerObject<"walletManager">; - -export class WalletManager { - #ptr: WalletManagerPtr; - // Stores data about who uses the pointer - #pointerUsers: WeakSet; - - constructor(walletManagerPtr: WalletManagerPtr) { - this.#ptr = walletManagerPtr; - this.#pointerUsers = new WeakSet(); - } - - getPointer(holder?: object) { - if (holder) this.#pointerUsers.add(holder); - return this.#ptr; - } - - static async new() { - const ptr = await dylib.symbols.MONERO_WalletManagerFactory_getWalletManager(); - if (!ptr) throw new Error("Failed retrieving wallet manager"); - return new WalletManager(ptr as WalletManagerPtr); - } -}