diff options
| author | Konstantin Ullrich <konstantinullrich12@gmail.com> | 2024-10-07 12:37:30 +0200 |
|---|---|---|
| committer | Konstantin Ullrich <konstantinullrich12@gmail.com> | 2024-10-07 12:37:30 +0200 |
| commit | 98272ee381bd07081502dd426226f58c879300a6 (patch) | |
| tree | 672f6f06727dbc1c84270973ce13e9403913e481 /impls | |
| parent | 04b29d84a2c368c677cf5ec946269203622ca170 (diff) | |
| parent | 67f4baa015a4407d096e35b6e5a81d72932fb55f (diff) | |
Merge branch 'master' into ledger
# Conflicts:
# .github/workflows/full_check.yaml
# impls/monero.dart/lib/monero.dart
# impls/monero.dart/pubspec.yaml
# patches/monero/0016-add-dummy-device-for-ledger.patch
Diffstat (limited to 'impls')
| -rw-r--r-- | impls/monero.c#/README.md | 7 | ||||
| -rw-r--r-- | impls/monero.c#/monero_wrapper.cs | 184 | ||||
| -rw-r--r-- | impls/monero.dart/lib/monero.dart | 55 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/checksum_monero.dart | 2 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/checksum_wownero.dart | 2 | ||||
| -rw-r--r-- | impls/monero.dart/lib/wownero.dart | 46 | ||||
| -rw-r--r-- | impls/monero.dart/pubspec.yaml | 4 | ||||
| -rw-r--r-- | impls/monero.ts/.gitignore | 2 | ||||
| -rw-r--r-- | impls/monero.ts/README.md | 42 | ||||
| -rw-r--r-- | impls/monero.ts/checksum.ts | 65 | ||||
| -rw-r--r-- | impls/monero.ts/checksum_monero.ts | 5 | ||||
| -rw-r--r-- | impls/monero.ts/checksum_wownero.ts | 5 | ||||
| -rw-r--r-- | impls/monero.ts/deno.jsonc | 5 | ||||
| -rw-r--r-- | impls/monero.ts/mod.ts | 6 | ||||
| -rw-r--r-- | impls/monero.ts/src/bindings.ts | 562 | ||||
| -rw-r--r-- | impls/monero.ts/src/pending_transaction.ts | 81 | ||||
| -rw-r--r-- | impls/monero.ts/src/transaction_history.ts | 38 | ||||
| -rw-r--r-- | impls/monero.ts/src/transaction_info.ts | 104 | ||||
| -rw-r--r-- | impls/monero.ts/src/utils.ts | 25 | ||||
| -rw-r--r-- | impls/monero.ts/src/wallet.ts | 304 | ||||
| -rw-r--r-- | impls/monero.ts/src/wallet_manager.ts | 27 |
21 files changed, 1541 insertions, 30 deletions
diff --git a/impls/monero.c#/README.md b/impls/monero.c#/README.md new file mode 100644 index 0000000..bfbb3b7 --- /dev/null +++ b/impls/monero.c#/README.md @@ -0,0 +1,7 @@ +# monero.c# + +> C# port for monero + +This is not a ready-to-use package (as opposed to monero.dart and monero.ts), but it works. You can check the `monero_wrapper.cs`. + +Currently C# code is optimized to be used in Godot.., if you are not planning to use it there - simply do not extend Node class and remove Godot import, if you are planning to use it in production, please let me know - we can work on a better approach to using monero with proper package code in here.
\ No newline at end of file diff --git a/impls/monero.c#/monero_wrapper.cs b/impls/monero.c#/monero_wrapper.cs new file mode 100644 index 0000000..ce7749d --- /dev/null +++ b/impls/monero.c#/monero_wrapper.cs @@ -0,0 +1,184 @@ +using Godot; +using System; +using System.Runtime.InteropServices; + +public partial class monero_wrapper : Node +{ + public static IntPtr wmPtr = MONERO_WalletManagerFactory_getWalletManager(); + public static IntPtr wPtr; + public static IntPtr pendingTx; + public static IntPtr txHistory; + public string path = ""; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process(double delta) + { + } + + public static bool openWallet(string path, string password) { + MONERO_WalletManager_createWallet(wmPtr, path, password, "English", 0); + wPtr = MONERO_WalletManager_openWallet(wmPtr, path, password, 0); + return MONERO_Wallet_status(wPtr) == 0; + } + + public static void initWallet(string daemonAddress, bool useSsl, String proxyString) { + //MONERO_Wallet_init(string daemon_password, bool use_ssl, bool lightWallet, string proxy_address); + MONERO_Wallet_init(wPtr, daemonAddress, 0, "", "", useSsl, false, proxyString); + GD.Print(lastError()); + MONERO_Wallet_init3(wPtr, "", "", "/dev/shm/godot_moneroc.log", false); + GD.Print(lastError()); + MONERO_Wallet_setTrustedDaemon(wPtr, true); + GD.Print(lastError()); + MONERO_Wallet_refreshAsync(wPtr); + GD.Print(lastError()); + MONERO_Wallet_startRefresh(wPtr); + GD.Print(lastError()); + } + + public static void storeWallet() { + MONERO_Wallet_store(wPtr); + } + + public static string lastError() { + IntPtr resultPtr = MONERO_Wallet_errorString(wPtr); + string result = Marshal.PtrToStringAnsi(resultPtr); + return result; + } + + public static int lastErrorCode() { + return MONERO_Wallet_status(wPtr); + } + + public static string lastTxError() { + IntPtr resultPtr = MONERO_PendingTransaction_errorString(pendingTx); + string result = Marshal.PtrToStringAnsi(resultPtr); + return result; + } + + public static int lastTxErrorCode() { + return MONERO_PendingTransaction_status(pendingTx); + } + + public static string getAddress(ulong accountIndex, ulong addressIndex) { + IntPtr resultPtr = MONERO_Wallet_address(wPtr, accountIndex, addressIndex); + string result = Marshal.PtrToStringAnsi(resultPtr); + return result; + } + + public static ulong getBalance(uint accountIndex) { + return MONERO_Wallet_balance(wPtr, accountIndex); + } + + public static void createTransaction(string address, ulong amount) { + pendingTx = MONERO_Wallet_createTransaction(wPtr, address, "", amount, 0, 0, 0, "", ""); + MONERO_PendingTransaction_commit(pendingTx, "", false); + } + + public static int getTransactionCount() { + txHistory = MONERO_Wallet_history(wPtr); + MONERO_TransactionHistory_refresh(txHistory); + return MONERO_TransactionHistory_count(txHistory); + } + + public static int getTransactionDirection(int index) { + txHistory = MONERO_Wallet_history(wPtr); + IntPtr txPtr = MONERO_TransactionHistory_transaction(txHistory, index); + return MONERO_TransactionInfo_direction(txPtr); + } + + public static ulong getTransactionAmount(int index) { + txHistory = MONERO_Wallet_history(wPtr); + IntPtr txPtr = MONERO_TransactionHistory_transaction(txHistory, index); + return MONERO_TransactionInfo_amount(txPtr); + } + + public static ulong getTransactionTimestamp(int index) { + txHistory = MONERO_Wallet_history(wPtr); + IntPtr txPtr = MONERO_TransactionHistory_transaction(txHistory, index); + return MONERO_TransactionInfo_timestamp(txPtr); + } + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern ulong MONERO_TransactionInfo_timestamp(IntPtr txPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_TransactionHistory_transaction(IntPtr txHistory, int index); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern int MONERO_TransactionInfo_direction(IntPtr txPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern int MONERO_TransactionHistory_count(IntPtr txHistory); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern ulong MONERO_TransactionInfo_amount(IntPtr txPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_Wallet_history(IntPtr wm_ptr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_TransactionHistory_refresh(IntPtr txHistory); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_WalletManager_createWallet(IntPtr wm_ptr, string path, string password, string language, int networkType); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_WalletManager_openWallet(IntPtr wm_ptr, string path, string password, int networkType); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_WalletManagerFactory_getWalletManager(); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern int MONERO_Wallet_status(IntPtr wPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern int MONERO_PendingTransaction_status(IntPtr wPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern bool MONERO_WalletManager_walletExists(IntPtr wmPtr, string path); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_Wallet_errorString(IntPtr wPtr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_PendingTransaction_errorString(IntPtr pendingTx); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern ulong MONERO_Wallet_balance(IntPtr wPtr, uint accountIndex); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_Wallet_address(IntPtr wPtr, ulong accountIndex, ulong addressIndex); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern IntPtr MONERO_Wallet_createTransaction(IntPtr wallet_ptr, string dst_addr, string payment_id, + ulong amount, uint mixin_count, + int pendingTransactionPriority, + uint subaddr_account, + string preferredInputs, string separator); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern bool MONERO_PendingTransaction_commit(IntPtr pendingTx_ptr, string filename, bool overwrite); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern bool MONERO_Wallet_init(IntPtr wallet_ptr, string daemon_address, ulong upper_transaction_size_limit, string daemon_username, string daemon_password, bool use_ssl, bool lightWallet, string proxy_address); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern bool MONERO_Wallet_init3(IntPtr wallet_ptr, string argv0, string default_log_base_name, string log_path, bool console); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern void MONERO_Wallet_setTrustedDaemon(IntPtr wallet_ptr, bool arg); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern void MONERO_Wallet_startRefresh(IntPtr wallet_ptr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern void MONERO_Wallet_refreshAsync(IntPtr wallet_ptr); + + [DllImport("/usr/lib/monero_libwallet2_api_c.so")] + public static extern bool MONERO_Wallet_store(IntPtr wallet_ptr); +} diff --git a/impls/monero.dart/lib/monero.dart b/impls/monero.dart/lib/monero.dart index 8a0eea4..7b6eac3 100644 --- a/impls/monero.dart/lib/monero.dart +++ b/impls/monero.dart/lib/monero.dart @@ -101,14 +101,36 @@ final Stopwatch sw = Stopwatch()..start(); bool printStarts = false; void Function(String call)? debugStart = (call) { - if (printStarts) print("MONERO: $call"); - debugCallLength[call] ??= <int>[]; - debugCallLength[call]!.add(sw.elapsedMicroseconds); + try { + if (printStarts) print("MONERO: $call"); + debugCallLength[call] ??= <int>[]; + debugCallLength[call]!.add(sw.elapsedMicroseconds); + } catch (e) {} }; +void debugChores() { + for (var key in debugCallLength.keys) { + if (debugCallLength[key]!.length > 1000000) { + final elm = + debugCallLength[key]!.reduce((value, element) => value + element); + debugCallLength[key]!.clear(); + debugCallLength["${key}_1M"] ??= <int>[]; + debugCallLength["${key}_1M"]!.add(elm); + } + } +} + +int debugCount = 0; + void Function(String call)? debugEnd = (call) { - final id = debugCallLength[call]!.length - 1; - debugCallLength[call]![id] = - sw.elapsedMicroseconds - debugCallLength[call]![id]; + try { + final id = debugCallLength[call]!.length - 1; + if (++debugCount > 1000000) { + debugCount = 0; + debugChores(); + } + debugCallLength[call]![id] = + sw.elapsedMicroseconds - debugCallLength[call]![id]; + } catch (e) {} }; void Function(String call, dynamic error)? errorHandler = (call, error) { print("$call: $error"); @@ -3586,11 +3608,11 @@ bool WalletManager_verifyWalletPassword( } int WalletManager_queryWalletDevice( - WalletManager wm_ptr, { - required String keysFileName, - required String password, - required int kdfRounds, -}) { + WalletManager wm_ptr, { + required String keysFileName, + required String password, + required int kdfRounds, + }) { debugStart?.call('MONERO_WalletManager_queryWalletDevice'); lib ??= MoneroC(DynamicLibrary.open(libPath)); final keysFileName_ = keysFileName.toNativeUtf8().cast<Char>(); @@ -3603,7 +3625,8 @@ int WalletManager_queryWalletDevice( return s; } -String WalletManager_findWallets(WalletManager wm_ptr, {required String path}) { +List<String> WalletManager_findWallets(WalletManager wm_ptr, + {required String path}) { debugStart?.call('MONERO_WalletManager_findWallets'); lib ??= MoneroC(DynamicLibrary.open(libPath)); try { @@ -3613,13 +3636,15 @@ String WalletManager_findWallets(WalletManager wm_ptr, {required String path}) { .cast<Utf8>(); final str = strPtr.toDartString(); calloc.free(path_); - MONERO_free(strPtr.cast()); + if (str.isNotEmpty) { + MONERO_free(strPtr.cast()); + } debugEnd?.call('MONERO_WalletManager_findWallets'); - return str; + return str.split(";"); } catch (e) { errorHandler?.call('MONERO_WalletManager_findWallets', e); debugEnd?.call('MONERO_WalletManager_findWallets'); - return ""; + return []; } } diff --git a/impls/monero.dart/lib/src/checksum_monero.dart b/impls/monero.dart/lib/src/checksum_monero.dart index 9e553f9..4ab72a3 100644 --- a/impls/monero.dart/lib/src/checksum_monero.dart +++ b/impls/monero.dart/lib/src/checksum_monero.dart @@ -1,4 +1,4 @@ // ignore_for_file: constant_identifier_names const String wallet2_api_c_h_sha256 = "e8db0ef0324a153f5e3ecca4c0db23c54f4576e84988f04bd4f11c1142f9d7ad"; -const String wallet2_api_c_cpp_sha256 = "d1842cded0040c16b8886878681c8938005f69ec1378fa9be68a430311cc3666"; +const String wallet2_api_c_cpp_sha256 = "dca52ac9ee009fda9fb5726543a454885e61d8eb74fb33112288029ed625bec5-b089f9ee69924882c5d14dd1a6991deb05d9d1cd"; const String wallet2_api_c_exp_sha256 = "c8913ac41068f67b57c9b0a3c7dd8973e3c1273b66c2ff0aadb0003931da748c"; diff --git a/impls/monero.dart/lib/src/checksum_wownero.dart b/impls/monero.dart/lib/src/checksum_wownero.dart index f4ed1e7..7d501c6 100644 --- a/impls/monero.dart/lib/src/checksum_wownero.dart +++ b/impls/monero.dart/lib/src/checksum_wownero.dart @@ -1,4 +1,4 @@ // ignore_for_file: constant_identifier_names const String wallet2_api_c_h_sha256 = "8a8d386dd5d996c89a0586c55b295ef95ca584bf1ffa26255152b291910a0a77"; -const String wallet2_api_c_cpp_sha256 = "ed400bd9c4709383ffd42a9fbe68be37a2a47a42f92eacaf3a2dbd248c422739"; +const String wallet2_api_c_cpp_sha256 = "07d67f34a07869aaa4af6ca04e142dbad2fb1fba0e2ebdefd22bc333fd982e25-e25963cbc11ca0a0fe5eb34b9bd7c72e4f51b795"; const String wallet2_api_c_exp_sha256 = "3673e40e1a7115552276d1d541f6e4d5a0fef47c40fff7b988f49923af84c8a4"; diff --git a/impls/monero.dart/lib/wownero.dart b/impls/monero.dart/lib/wownero.dart index 5a0a288..d355005 100644 --- a/impls/monero.dart/lib/wownero.dart +++ b/impls/monero.dart/lib/wownero.dart @@ -101,14 +101,36 @@ final Stopwatch sw = Stopwatch()..start(); bool printStarts = false; void Function(String call)? debugStart = (call) { - if (printStarts) print("MONERO: $call"); - debugCallLength[call] ??= <int>[]; - debugCallLength[call]!.add(sw.elapsedMicroseconds); + try { + if (printStarts) print("MONERO: $call"); + debugCallLength[call] ??= <int>[]; + debugCallLength[call]!.add(sw.elapsedMicroseconds); + } catch (e) {} }; +void debugChores() { + for (var key in debugCallLength.keys) { + if (debugCallLength[key]!.length > 1000000) { + final elm = + debugCallLength[key]!.reduce((value, element) => value + element); + debugCallLength[key]!.clear(); + debugCallLength["${key}_1M"] ??= <int>[]; + debugCallLength["${key}_1M"]!.add(elm); + } + } +} + +int debugCount = 0; + void Function(String call)? debugEnd = (call) { - final id = debugCallLength[call]!.length - 1; - debugCallLength[call]![id] = - sw.elapsedMicroseconds - debugCallLength[call]![id]; + try { + final id = debugCallLength[call]!.length - 1; + if (++debugCount > 1000000) { + debugCount = 0; + debugChores(); + } + debugCallLength[call]![id] = + sw.elapsedMicroseconds - debugCallLength[call]![id]; + } catch (e) {} }; void Function(String call, dynamic error)? errorHandler = (call, error) { print("$call: $error"); @@ -3220,7 +3242,8 @@ bool WalletManager_verifyWalletPassword( return s; } -String WalletManager_findWallets(WalletManager wm_ptr, {required String path}) { +List<String> WalletManager_findWallets(WalletManager wm_ptr, + {required String path}) { debugStart?.call('WOWNERO_WalletManager_findWallets'); lib ??= WowneroC(DynamicLibrary.open(libPath)); try { @@ -3230,13 +3253,15 @@ String WalletManager_findWallets(WalletManager wm_ptr, {required String path}) { .cast<Utf8>(); final str = strPtr.toDartString(); calloc.free(path_); - WOWNERO_free(strPtr.cast()); + if (str.isNotEmpty) { + WOWNERO_free(strPtr.cast()); + } debugEnd?.call('WOWNERO_WalletManager_findWallets'); - return str; + return str.split(";"); } catch (e) { errorHandler?.call('WOWNERO_WalletManager_findWallets', e); debugEnd?.call('WOWNERO_WalletManager_findWallets'); - return ""; + return []; } } @@ -3606,7 +3631,6 @@ int WOWNERO_deprecated_14WordSeedHeight({ return s; } - String WOWNERO_checksum_wallet2_api_c_h() { debugStart?.call('WOWNERO_checksum_wallet2_api_c_h'); lib ??= WowneroC(DynamicLibrary.open(libPath)); diff --git a/impls/monero.dart/pubspec.yaml b/impls/monero.dart/pubspec.yaml index 67dc577..577dcbe 100644 --- a/impls/monero.dart/pubspec.yaml +++ b/impls/monero.dart/pubspec.yaml @@ -11,6 +11,6 @@ dependencies: ledger_flutter_plus: ^1.2.5 dev_dependencies: - lints: ^4.0.0 + lints: ^5.0.0 test: ^1.24.0 - ffigen: ^13.0.0 + ffigen: ^14.0.0 diff --git a/impls/monero.ts/.gitignore b/impls/monero.ts/.gitignore new file mode 100644 index 0000000..8b61d3b --- /dev/null +++ b/impls/monero.ts/.gitignore @@ -0,0 +1,2 @@ +*_libwallet2_api_c.* +lib diff --git a/impls/monero.ts/README.md b/impls/monero.ts/README.md new file mode 100644 index 0000000..e3b20f6 --- /dev/null +++ b/impls/monero.ts/README.md @@ -0,0 +1,42 @@ +# monero.ts + +`monero_c` bindings for Deno. + +## Usage + +This library does not ship with `monero_c` libraries.\ +To use these bindings you have to bring your own `monero_c` libraries.\ +There are at least two ways to do so: +- Ahead-of-time, during builds where you only ship necessary library for a given platform.\ + See [monero-tui](https://github.com/Im-Beast/monero-tui/blob/main/.github/workflows/dev-build.yml) build workflow as an example of doing so. + ```ts + import { loadDylib, Wallet, WalletManager } from "https://raw.githubusercontent.com/MrCyjaneK/monero_c/master/impls/monero.ts/mod.ts"; + + // Try to load dylib from the default lib/* path + loadDylib(); + + const wm = await WalletManager.new(); + const wallet = await Wallet.create(wm, "./my_wallet", "password"); + + console.log(await wallet.address()); + + await wallet.store(); + ``` +- Just-in-time, where you download and cache the library at runtime.\ + You can use something like [plug](https://jsr.io/@denosaurs/plug) to achieve the result. + ```ts + import { dlopen } from "jsr:@denosaurs/plug"; + // It's recommened to put the monero.ts github link into your import_map to reduce the url clutter + import { loadDylib, symbols, Wallet, WalletManager } from "https://raw.githubusercontent.com/MrCyjaneK/monero_c/master/impls/monero.ts/mod.ts"; + + // Load dylib loaded by plug + const lib = await dlopen(..., symbols); + loadDylib(lib); + + const wm = await WalletManager.new(); + const wallet = await Wallet.create(wm, "./my_wallet", "password"); + + console.log(await wallet.address()); + + await wallet.store(); + ``` diff --git a/impls/monero.ts/checksum.ts b/impls/monero.ts/checksum.ts new file mode 100644 index 0000000..22d3038 --- /dev/null +++ b/impls/monero.ts/checksum.ts @@ -0,0 +1,65 @@ +import { moneroChecksum } from "./checksum_monero.ts"; +import { readCString } from "./src/utils.ts"; +import { dylib, loadDylib } from "./src/bindings.ts"; + +loadDylib(); + +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<ChecksumError | null> { + const cppHeaderHash = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_h(), false); + 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 = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_cpp(), false); + const tsSourceHash = moneroChecksum.wallet2_api_c_cpp_sha256; + if (cppSourceHash !== tsSourceHash) { + errors.push(`ERR: CPP source file check mismatch ${cppSourceHash} == ${tsSourceHash}`); + errorCode++; + } + + const cppExportHash = await readCString(await dylib.symbols.MONERO_checksum_wallet2_api_c_exp(), false); + 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/checksum_monero.ts b/impls/monero.ts/checksum_monero.ts new file mode 100644 index 0000000..88406a0 --- /dev/null +++ b/impls/monero.ts/checksum_monero.ts @@ -0,0 +1,5 @@ +export const moneroChecksum = { + wallet2_api_c_h_sha256: "e8db0ef0324a153f5e3ecca4c0db23c54f4576e84988f04bd4f11c1142f9d7ad", + wallet2_api_c_cpp_sha256: "dca52ac9ee009fda9fb5726543a454885e61d8eb74fb33112288029ed625bec5-b089f9ee69924882c5d14dd1a6991deb05d9d1cd", + wallet2_api_c_exp_sha256: "c8913ac41068f67b57c9b0a3c7dd8973e3c1273b66c2ff0aadb0003931da748c", +} diff --git a/impls/monero.ts/checksum_wownero.ts b/impls/monero.ts/checksum_wownero.ts new file mode 100644 index 0000000..8b2899c --- /dev/null +++ b/impls/monero.ts/checksum_wownero.ts @@ -0,0 +1,5 @@ +export const wowneroChecksum = { + wallet2_api_c_h_sha256: "8a8d386dd5d996c89a0586c55b295ef95ca584bf1ffa26255152b291910a0a77", + wallet2_api_c_cpp_sha256: "07d67f34a07869aaa4af6ca04e142dbad2fb1fba0e2ebdefd22bc333fd982e25-e25963cbc11ca0a0fe5eb34b9bd7c72e4f51b795", + wallet2_api_c_exp_sha256: "3673e40e1a7115552276d1d541f6e4d5a0fef47c40fff7b988f49923af84c8a4", +} diff --git a/impls/monero.ts/deno.jsonc b/impls/monero.ts/deno.jsonc new file mode 100644 index 0000000..a7b75ec --- /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 0000000..1eca773 --- /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/src/bindings.ts b/impls/monero.ts/src/bindings.ts new file mode 100644 index 0000000..b854f7d --- /dev/null +++ b/impls/monero.ts/src/bindings.ts @@ -0,0 +1,562 @@ +export const symbols = { + "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 + + "MONERO_free": { + nonblocking: true, + // void* ptr + parameters: ["pointer"], + // void + result: "void", + }, +} as const; + +type MoneroTsDylib = Deno.DynamicLibrary<typeof symbols>; + +export let dylib: MoneroTsDylib; +export function loadDylib(newDylib?: MoneroTsDylib) { + if (newDylib) { + dylib = newDylib; + return; + } + + 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; + } + + dylib = Deno.dlopen(libPath, symbols); +} diff --git a/impls/monero.ts/src/pending_transaction.ts b/impls/monero.ts/src/pending_transaction.ts new file mode 100644 index 0000000..cf48721 --- /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<number> { + return await dylib.symbols.MONERO_PendingTransaction_status(this.#pendingTxPtr); + } + + async errorString(): Promise<string | null> { + if (!await this.status()) return null; + + const error = await dylib.symbols.MONERO_PendingTransaction_errorString(this.#pendingTxPtr); + if (!error) return null; + + return await readCString(error) || null; + } + + async throwIfError(sanitize = true): Promise<void> { + const maybeError = await this.errorString(); + if (maybeError) { + if (sanitize) this.sanitizer?.(); + throw new Error(maybeError); + } + } + + async commit(fileName: string, overwrite: boolean, sanitize = true): Promise<boolean> { + const bool = await dylib.symbols.MONERO_PendingTransaction_commit( + this.#pendingTxPtr, + CString(fileName), + overwrite, + ); + await this.throwIfError(sanitize); + return bool; + } + + async commitUR(maxFragmentLength: number): Promise<string | null> { + const result = await dylib.symbols.MONERO_PendingTransaction_commitUR( + this.#pendingTxPtr, + maxFragmentLength, + ); + if (!result) return null; + await this.throwIfError(); + return await readCString(result) || null; + } + + async amount(): Promise<bigint> { + return await dylib.symbols.MONERO_PendingTransaction_amount(this.#pendingTxPtr); + } + + async dust(): Promise<bigint> { + return await dylib.symbols.MONERO_PendingTransaction_dust(this.#pendingTxPtr); + } + + async fee(): Promise<bigint> { + return await dylib.symbols.MONERO_PendingTransaction_fee(this.#pendingTxPtr); + } + + async txid(separator: string, sanitize = true): Promise<string | null> { + const result = await dylib.symbols.MONERO_PendingTransaction_txid( + this.#pendingTxPtr, + CString(separator), + ); + if (!result) return null; + await this.throwIfError(sanitize); + return await readCString(result) || null; + } + + async txCount(): Promise<bigint> { + 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 0000000..cc76fc2 --- /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<number> { + return await dylib.symbols.MONERO_TransactionHistory_count(this.#txHistoryPtr); + } + + async transaction(index: number): Promise<TransactionInfo> { + return new TransactionInfo( + (await dylib.symbols.MONERO_TransactionHistory_transaction( + this.#txHistoryPtr, + index, + )) as TransactionInfoPtr, + ); + } + + async refresh(): Promise<void> { + await dylib.symbols.MONERO_TransactionHistory_refresh(this.#txHistoryPtr); + } + + async setTxNote(transactionId: string, note: string): Promise<void> { + 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 0000000..7db45f5 --- /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<boolean> { + return await dylib.symbols.MONERO_TransactionInfo_isPending(this.#txInfoPtr); + } + + async isFailed(): Promise<boolean> { + return await dylib.symbols.MONERO_TransactionInfo_isFailed(this.#txInfoPtr); + } + + async isCoinbase(): Promise<boolean> { + return await dylib.symbols.MONERO_TransactionInfo_isCoinbase(this.#txInfoPtr); + } + + async amount(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_amount(this.#txInfoPtr); + } + + async fee(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_fee(this.#txInfoPtr); + } + + async blockHeight(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_blockHeight(this.#txInfoPtr); + } + + async description(): Promise<string> { + const description = await dylib.symbols.MONERO_TransactionInfo_description(this.#txInfoPtr); + return await readCString(description) || ""; + } + + async subaddrIndex(): Promise<string> { + const subaddrIndex = await dylib.symbols.MONERO_TransactionInfo_subaddrIndex(this.#txInfoPtr); + return await readCString(subaddrIndex) || ""; + } + + async subaddrAccount(): Promise<number> { + return await dylib.symbols.MONERO_TransactionInfo_subaddrAccount(this.#txInfoPtr); + } + + async label(): Promise<string> { + const label = await dylib.symbols.MONERO_TransactionInfo_label(this.#txInfoPtr); + return await readCString(label) || ""; + } + + async confirmations(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_confirmations(this.#txInfoPtr); + } + + async unlockTime(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_unlockTime(this.#txInfoPtr); + } + + async hash(): Promise<string> { + const hash = await dylib.symbols.MONERO_TransactionInfo_hash(this.#txInfoPtr); + return await readCString(hash) || ""; + } + + async timestamp(): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_timestamp(this.#txInfoPtr); + } + + async paymentId(): Promise<string> { + const paymentId = await dylib.symbols.MONERO_TransactionInfo_paymentId(this.#txInfoPtr); + return await readCString(paymentId) || ""; + } + + async transfersCount(): Promise<number> { + return await dylib.symbols.MONERO_TransactionInfo_transfers_count(this.#txInfoPtr); + } + + async transfersAmount(index: number): Promise<bigint> { + return await dylib.symbols.MONERO_TransactionInfo_transfers_amount(this.#txInfoPtr, index); + } + + async transfersAddress(index: number): Promise<string> { + const transfersAddress = await dylib.symbols.MONERO_TransactionInfo_transfers_address(this.#txInfoPtr, index); + return await readCString(transfersAddress) || ""; + } +} diff --git a/impls/monero.ts/src/utils.ts b/impls/monero.ts/src/utils.ts new file mode 100644 index 0000000..6fa640f --- /dev/null +++ b/impls/monero.ts/src/utils.ts @@ -0,0 +1,25 @@ +import { dylib } from "../mod.ts"; + +export type Sanitizer = () => void | PromiseLike<void>; + +const textEncoder = new TextEncoder(); +export function CString(string: string): Deno.PointerValue<string> { + return Deno.UnsafePointer.of(textEncoder.encode(`${string}\x00`)); +} + +/** + * This method reads string from the given pointer and frees the string. + * + * SAFETY: Do not use readCString twice on the same pointer as it will cause double free\ + * If you want to read CString without freeing it set the {@linkcode free} parameter to false + */ +export async function readCString(pointer: Deno.PointerObject, free?: boolean): Promise<string>; +export async function readCString(pointer: Deno.PointerValue, free?: boolean): Promise<string | null>; +export async function readCString(pointer: Deno.PointerValue, free = true): Promise<string | null> { + if (!pointer) return null; + const string = new Deno.UnsafePointerView(pointer).getCString(); + if (free) { + await dylib.symbols.MONERO_free(pointer); + } + return string; +} diff --git a/impls/monero.ts/src/wallet.ts b/impls/monero.ts/src/wallet.ts new file mode 100644 index 0000000..07c40ce --- /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<boolean> { + const bool = await dylib.symbols.MONERO_Wallet_store(this.#walletPtr, CString(path)); + await this.throwIfError(); + return bool; + } + + async initWallet(): Promise<void> { + 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<void> { + await dylib.symbols.MONERO_WalletManager_setDaemonAddress( + this.#walletManagerPtr, + CString(address), + ); + } + + async startRefresh(): Promise<void> { + await dylib.symbols.MONERO_Wallet_startRefresh(this.#walletPtr); + await this.throwIfError(); + } + + async refreshAsync(): Promise<void> { + await dylib.symbols.MONERO_Wallet_refreshAsync(this.#walletPtr); + await this.throwIfError(); + } + + async init(): Promise<boolean> { + 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<void> { + await dylib.symbols.MONERO_Wallet_setTrustedDaemon(this.#walletPtr, value); + } + + static async create( + walletManager: WalletManager, + path: string, + password: string, + sanitizeError = true, + ): Promise<Wallet> { + // 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<Wallet> { + // 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<Wallet> { + // 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<string> { + 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 ?? "<Error unknown>"}`); + } + return await readCString(address); + } + + async balance(accountIndex = 0): Promise<bigint> { + return await dylib.symbols.MONERO_Wallet_balance(this.#walletPtr, accountIndex); + } + + async unlockedBalance(accountIndex = 0): Promise<bigint> { + return await dylib.symbols.MONERO_Wallet_unlockedBalance(this.#walletPtr, accountIndex); + } + + status(): Promise<number> { + return dylib.symbols.MONERO_Wallet_status(this.#walletPtr); + } + + async errorString(): Promise<string | null> { + if (!await this.status()) return null; + + const error = await dylib.symbols.MONERO_Wallet_errorString(this.#walletPtr); + if (!error) return null; + + return await readCString(error) || null; + } + + async throwIfError(sanitize = true): Promise<void> { + const maybeError = await this.errorString(); + if (maybeError) { + if (sanitize) this.sanitizer?.(); + throw new Error(maybeError); + } + } + + async synchronized(): Promise<boolean> { + const synchronized = await dylib.symbols.MONERO_Wallet_synchronized(this.#walletPtr); + await this.throwIfError(); + return synchronized; + } + + async blockChainHeight(): Promise<bigint> { + const height = await dylib.symbols.MONERO_Wallet_blockChainHeight(this.#walletPtr); + await this.throwIfError(); + return height; + } + + async daemonBlockChainHeight(): Promise<bigint> { + const height = await dylib.symbols.MONERO_Wallet_daemonBlockChainHeight(this.#walletPtr); + await this.throwIfError(); + return height; + } + + async managerBlockChainHeight(): Promise<bigint> { + const height = await dylib.symbols.MONERO_WalletManager_blockchainHeight(this.#walletManagerPtr); + await this.throwIfError(); + return height; + } + + async managerTargetBlockChainHeight(): Promise<bigint> { + const height = await dylib.symbols.MONERO_WalletManager_blockchainTargetHeight(this.#walletManagerPtr); + await this.throwIfError(); + return height; + } + + async addSubaddressAccount(label: string): Promise<void> { + await dylib.symbols.MONERO_Wallet_addSubaddressAccount( + this.#walletPtr, + CString(label), + ); + await this.throwIfError(); + } + + async numSubaddressAccounts(): Promise<bigint> { + const accountsLen = await dylib.symbols.MONERO_Wallet_numSubaddressAccounts(this.#walletPtr); + await this.throwIfError(); + return accountsLen; + } + + async addSubaddress(accountIndex: number, label: string): Promise<void> { + await dylib.symbols.MONERO_Wallet_addSubaddress( + this.#walletPtr, + accountIndex, + CString(label), + ); + await this.throwIfError(); + } + + async numSubaddresses(accountIndex: number): Promise<bigint> { + const address = await dylib.symbols.MONERO_Wallet_numSubaddresses( + this.#walletPtr, + accountIndex, + ); + await this.throwIfError(); + return address; + } + + async getSubaddressLabel(accountIndex: number, addressIndex: number): Promise<string> { + 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 ?? "<Error unknown>"}`); + } + return await readCString(label); + } + + async setSubaddressLabel(accountIndex: number, addressIndex: number, label: string): Promise<void> { + await dylib.symbols.MONERO_Wallet_setSubaddressLabel( + this.#walletPtr, + accountIndex, + addressIndex, + CString(label), + ); + await this.throwIfError(); + } + + async getHistory(): Promise<TransactionHistory> { + 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<PendingTransaction> { + 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<bigint> { + 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 0000000..ad9cf31 --- /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); + } +} |
