feat: deno ffi bindings (#40)

* initial ts commit
* monero.ts improvements
* test on latest, build on debian:bookworm
* feat: upstream changes to bindings
* chore: update checksums
* feat: allow manually loading dylib
* chore: add readme
* fix: free strings after being read to prevent potential memory leaks
* fix: load dylib
* fix: checksum checks segfaulting because of freeing const variables

---------

Co-authored-by: Mateusz Franik <47059999+Im-Beast@users.noreply.github.com>
Co-authored-by: Im-Beast <franik.mateusz@gmail.com>
This commit is contained in:
cyan
2024-09-19 10:59:07 +02:00
committed by GitHub
parent 0b91e91d19
commit d194094494
17 changed files with 1406 additions and 3 deletions
+119 -2
View File
@@ -137,7 +137,7 @@ jobs:
coin: [monero, wownero]
runs-on: ubuntu-latest
container:
image: debian:bookworm
image: debian:bullseye
steps:
- name: Install dependencies
run: |
@@ -698,6 +698,123 @@ jobs:
with:
name: ${{ matrix.coin }} xmruw apk
path: unnamed_monero_wallet/build/app/outputs/flutter-apk/*.apk
bulk_lib_release:
name: create single release file
runs-on: ubuntu-latest
needs: [
lib_mingw, lib_android, lib_linux, lib_sailfishos_aarch64, lib_sailfishos_i486, lib_darwin, lib_macos, lib_ios
]
steps:
- uses: actions/download-artifact@v4
with:
name: android monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: android wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: darwin monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: darwin wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: ios monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: ios wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: linux monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: linux wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: macos monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: macos wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: mingw monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: mingw wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: sfos aarch64 monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: sfos aarch64 wownero
path: release/wownero
- uses: actions/download-artifact@v4
with:
name: sfos_i486 monero
path: release/monero
- uses: actions/download-artifact@v4
with:
name: sfos_i486 wownero
path: release/wownero
- name: zip release dir
run: zip -r release-bundle.zip release
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: release-bundle.zip
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
- name: Upload lib
uses: actions/upload-artifact@v4
with:
name: release-bulk
path: release
deno_monerots_test_linux:
name: test ts library
runs-on: ubuntu-24.04
needs: [
lib_linux
]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
- uses: actions/download-artifact@v4
with:
name: linux monero
path: release/monero
- name: unpack and move monero_c
run: |
unxz -f -k release/*/*.xz
- uses: denoland/setup-deno@v1
with:
deno-version: vx.x.x
- name: Create symlink
run: |
cd impls/monero.ts
mkdir lib
cd lib
ln -s ../../../release/monero/x86_64-linux-gnu_libwallet2_api_c.so
mv x86_64-linux-gnu_libwallet2_api_c.so monero_libwallet2_api_c.so
cd ..
- name: Run tests
run: |
cd impls/monero.ts
deno run --unstable-ffi --allow-ffi checksum.ts
comment_pr:
name: comment on pr
@@ -752,4 +869,4 @@ jobs:
with:
issue-number: ${{steps.get_issue_number.outputs.result}}
body: |
[download artifacts #${{github.run_id}}](https://nightly.link/MrCyjaneK/monero_c/actions/runs/${{github.run_id}}) (this comment will update whenever you push)
[download artifacts #${{github.run_id}}](https://nightly.link/MrCyjaneK/monero_c/actions/runs/${{github.run_id}}) (this comment will update whenever you push)
+3
View File
@@ -0,0 +1,3 @@
{
"deno.enable": true
}
+13 -1
View File
@@ -1,6 +1,11 @@
#!/bin/bash
cd "$(realpath $(dirname $0))"
function sha256sum() { shasum -a 256 "$@" ; } && export -f sha256sum
if [[ "$(uname)" == "Darwin" ]];
then
function sha256sum() { shasum -a 256 "$@" ; } && export -f sha256sum
fi
for coin in monero wownero;
do
submodule_hash=$(git ls-tree HEAD ${coin} | xargs | awk '{ print $3 }')
@@ -22,6 +27,13 @@ EOF
const String wallet2_api_c_h_sha256 = "${COIN_wallet2_api_c_h_sha256}";
const String wallet2_api_c_cpp_sha256 = "${COIN_wallet2_api_c_cpp_sha256}";
const String wallet2_api_c_exp_sha256 = "${COIN_wallet2_api_c_exp_sha256}";
EOF
cat > impls/monero.ts/checksum_${coin}.ts << EOF
export const ${coin}Checksum = {
wallet2_api_c_h_sha256: "${COIN_wallet2_api_c_h_sha256}",
wallet2_api_c_cpp_sha256: "${COIN_wallet2_api_c_cpp_sha256}",
wallet2_api_c_exp_sha256: "${COIN_wallet2_api_c_exp_sha256}",
}
EOF
done
+2
View File
@@ -0,0 +1,2 @@
*_libwallet2_api_c.*
lib
+42
View File
@@ -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();
```
+65
View File
@@ -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;
}
}
+5
View File
@@ -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",
}
+5
View File
@@ -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",
}
+5
View File
@@ -0,0 +1,5 @@
{
"fmt": {
"lineWidth": 120
}
}
+6
View File
@@ -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";
+562
View File
@@ -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);
}
@@ -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);
}
}
@@ -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),
);
}
}
+104
View File
@@ -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) || "";
}
}
+25
View File
@@ -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;
}
+304
View File
@@ -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));
}
}
+27
View File
@@ -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);
}
}