feat!: monero.ts rewrite, integration tests (#80)
* feat: move spend/view key symbols to the monero.ts implementation * feat: add integration tests for `0001-polyseed.patch` * feat(monero.ts): add support for backgroundSync and closing the wallet * feat: add integration tests for `0002-wallet-background-sync-with-just-the-view-key.patch` * feat!: require users to provide own node url BREAKING CHANGE: Requires users manual call to `Wallet.initWallet` after wallet creation with preferred node url * feat: add background sync test for `0002-wallet-background-sync-with-just-the-view-key.patch` * ci: add integration tests step * feat(monero.ts): support creating and recovering wallet from polyseed * feat: actually test polyseeds in the integration test * chore: remove legacy comments * fix: uncomment getting moneroC * feat(monero.ts): add support for reading wallet's seed * feat: add seed test for `0009-Add-recoverDeterministicWalletFromSpendKey.patch` * chore: slight refactor * feat(monero.ts): add bindings for `setOffline` and `isOffline` * feat: add integration tests for `0012-WIP-UR-functions.patch` * fix: use correct node depending on the coin * fix: prevent segfaults on wownero * feat(monero.ts): add partial bindings for `Coins` and `CoinsInfo` * feat: add integration tests for `0004-coin-control.patch` * fix coin control * clean up console.logs * chore: comment out the entire block * dev: add devcontainer config for deno * fix(monero.ts): invalid PendingTransactionPtr brand * feat(monero.ts): add bindings for retrieving keys and managing transactions * feat: improve `0012-WIP-UR-functions.patch` tests to follow the airgap doc * fix(monero.ts): make UR methods optional so wownero can load properly * remove flaky balance assertions * tests: add a little bit of delay to make 0002 patch test less flake-y * tests: run wallet transaction tests on ci * enable logging to determine why it segfaults on ci * add delay to every syncBlockchain call * its console logging time * even more console.logs * eep * eep more * dont assert that its not frozen * remove console.logs * fix(monero.ts): type typo becoming a default value * feat(monero.ts): add bindings for `createTransactionMultDest` * feat(monero.ts): support returning multiple values whenever necessary * feat(monero.ts): add missing reexports * feat(monero.ts)!: rewrite bindings BREAKING CHANGES!: - Calls to methods no longer automatically throw errors, you should take care of handling errors yourself - This means the whole sanitizer ordeal is gone, no more sanitize arguments etc. - Some misplaced methods have been moved to their "proper" place, e.g. creating Wallet is now possible using WalletManager instance methods, instead of passing WalletManager instance to Wallet's static method - Return types probably changed in places, methods were inconsitent about returning string or empty string and `string | null`, now its always `string | null` - Every available symbol should now be available in `symbols`, even for the things that are not yet implemented, so you can access them in that case * tests: adapt tests to monero.ts changes * tests: reuse dylib in tests --------- Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
@@ -23,7 +23,7 @@ There are at least two ways to do so:
|
||||
loadMoneroDylib();
|
||||
|
||||
const wm = await WalletManager.new();
|
||||
const wallet = await Wallet.create(wm, "./my_wallet", "password");
|
||||
const wallet = await wm.createWallet("./my_wallet", "password");
|
||||
|
||||
console.log(await wallet.address());
|
||||
|
||||
@@ -41,7 +41,7 @@ There are at least two ways to do so:
|
||||
loadMoneroDylib(lib);
|
||||
|
||||
const wm = await WalletManager.new();
|
||||
const wallet = await Wallet.create(wm, "./my_wallet", "password");
|
||||
const wallet = await wm.createWallet("./my_wallet", "password");
|
||||
|
||||
console.log(await wallet.address());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { moneroChecksum } from "./checksum_monero.ts";
|
||||
import { getSymbol, readCString } from "./src/utils.ts";
|
||||
import { dylib, loadMoneroDylib } from "./src/bindings.ts";
|
||||
import { readCString } from "./src/utils.ts";
|
||||
import { fns, loadMoneroDylib } from "./src/bindings.ts";
|
||||
|
||||
loadMoneroDylib();
|
||||
|
||||
@@ -21,7 +21,7 @@ export class ChecksumError extends Error {
|
||||
* @returns {ChecksumError} which contains information about why checksum failed
|
||||
*/
|
||||
export async function validateChecksum(): Promise<ChecksumError | null> {
|
||||
const cppHeaderHash = await readCString(await getSymbol("checksum_wallet2_api_c_h")!(), false);
|
||||
const cppHeaderHash = await readCString(await fns.checksum_wallet2_api_c_h!(), false);
|
||||
const tsHeaderHash = moneroChecksum.wallet2_api_c_h_sha256;
|
||||
|
||||
const errors: string[] = [];
|
||||
@@ -32,14 +32,14 @@ export async function validateChecksum(): Promise<ChecksumError | null> {
|
||||
errorCode++;
|
||||
}
|
||||
|
||||
const cppSourceHash = await readCString(await getSymbol("checksum_wallet2_api_c_cpp")!(), false);
|
||||
const cppSourceHash = await readCString(await fns.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 getSymbol("checksum_wallet2_api_c_exp")!(), false);
|
||||
const cppExportHash = await readCString(await fns.checksum_wallet2_api_c_exp!(), false);
|
||||
const tsExportHash = moneroChecksum.wallet2_api_c_exp_sha256;
|
||||
if (cppExportHash !== tsExportHash) {
|
||||
if (Deno.build.os !== "darwin") {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export * from "./src/bindings.ts";
|
||||
export * from "./src/coins.ts";
|
||||
export * from "./src/coins_info.ts";
|
||||
export * from "./src/pending_transaction.ts";
|
||||
export * from "./src/symbols.ts";
|
||||
export * from "./src/transaction_history.ts";
|
||||
export * from "./src/transaction_info.ts";
|
||||
export * from "./src/unsigned_transaction.ts";
|
||||
export * from "./src/utils.ts";
|
||||
export * from "./src/wallet.ts";
|
||||
export * from "./src/wallet_manager.ts";
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { type Dylib, moneroSymbols, type MoneroTsDylib, wowneroSymbols, type WowneroTsDylib } from "./symbols.ts";
|
||||
import { type MoneroSymbols, moneroSymbols, type SymbolName, type WowneroSymbols, wowneroSymbols } from "./symbols.ts";
|
||||
|
||||
export type MoneroDylib = Deno.DynamicLibrary<MoneroSymbols>;
|
||||
export type WowneroDylib = Deno.DynamicLibrary<WowneroSymbols>;
|
||||
export type Dylib = MoneroDylib | WowneroDylib;
|
||||
|
||||
export let dylib: Dylib;
|
||||
|
||||
export function loadMoneroDylib(newDylib?: MoneroTsDylib) {
|
||||
let dylibPrefix = "MONERO";
|
||||
export const fns = new Proxy({} as { [K in SymbolName]: MoneroDylib["symbols"][`MONERO_${K}`] }, {
|
||||
get(_, symbolName: SymbolName) {
|
||||
return dylib.symbols[`${dylibPrefix}_${symbolName}` as keyof Dylib["symbols"]];
|
||||
},
|
||||
});
|
||||
|
||||
export function loadMoneroDylib(newDylib?: MoneroDylib) {
|
||||
dylibPrefix = "MONERO";
|
||||
|
||||
if (newDylib) {
|
||||
dylib = newDylib;
|
||||
return;
|
||||
@@ -27,7 +40,9 @@ export function loadMoneroDylib(newDylib?: MoneroTsDylib) {
|
||||
dylib = Deno.dlopen(libPath, moneroSymbols);
|
||||
}
|
||||
|
||||
export function loadWowneroDylib(newDylib?: WowneroTsDylib) {
|
||||
export function loadWowneroDylib(newDylib?: WowneroDylib) {
|
||||
dylibPrefix = "WOWNERO";
|
||||
|
||||
if (newDylib) {
|
||||
dylib = newDylib;
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { CoinsInfo, type CoinsInfoPtr } from "./coins_info.ts";
|
||||
import { fns } from "./bindings.ts";
|
||||
|
||||
export type CoinsPtr = Deno.PointerObject<"coins">;
|
||||
|
||||
export class Coins {
|
||||
#ptr: CoinsPtr;
|
||||
|
||||
#coins: CoinsInfo[] = [];
|
||||
|
||||
constructor(ptr: CoinsPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
async count(): Promise<number> {
|
||||
return await fns.Coins_count(this.#ptr);
|
||||
}
|
||||
|
||||
async coin(index: number): Promise<CoinsInfo | null> {
|
||||
if (this.#coins[index]) {
|
||||
return this.#coins[index];
|
||||
}
|
||||
|
||||
const coinPtr = await fns.Coins_coin(this.#ptr, index);
|
||||
if (!coinPtr) return null;
|
||||
|
||||
return CoinsInfo.new(coinPtr as CoinsInfoPtr);
|
||||
}
|
||||
|
||||
async setFrozen(index: number) {
|
||||
return await fns.Coins_setFrozen(this.#ptr, index);
|
||||
}
|
||||
|
||||
async thaw(index: number) {
|
||||
return await fns.Coins_thaw(this.#ptr, index);
|
||||
}
|
||||
|
||||
async getAllSize(): Promise<number> {
|
||||
return await fns.Coins_getAll_size(this.#ptr);
|
||||
}
|
||||
|
||||
async getAllByIndex(index: number): Promise<unknown> {
|
||||
return await fns.Coins_getAll_byIndex(this.#ptr, index);
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
await fns.Coins_refresh(this.#ptr);
|
||||
|
||||
for (const coin of this.#coins) {
|
||||
coin.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { fns } from "./bindings.ts";
|
||||
import { readCString } from "./utils.ts";
|
||||
|
||||
export type CoinsInfoPtr = Deno.PointerObject<"coinsInfo">;
|
||||
|
||||
export class CoinsInfo {
|
||||
#ptr: CoinsInfoPtr;
|
||||
|
||||
#hash!: string | null;
|
||||
#keyImage!: string | null;
|
||||
#blockHeight!: bigint;
|
||||
#amount!: bigint;
|
||||
#spent!: boolean;
|
||||
#spentHeight!: bigint;
|
||||
#frozen!: boolean;
|
||||
#unlocked!: boolean;
|
||||
|
||||
constructor(ptr: CoinsInfoPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
getPointer(): CoinsInfoPtr {
|
||||
return this.#ptr;
|
||||
}
|
||||
|
||||
static async new(ptr: CoinsInfoPtr): Promise<CoinsInfo> {
|
||||
const instance = new CoinsInfo(ptr);
|
||||
await instance.refresh();
|
||||
return instance;
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
const [hash, keyImage, blockHeight, amount, spent, spentHeight, frozen, unlocked] = await Promise.all([
|
||||
fns.CoinsInfo_hash(this.#ptr).then(readCString),
|
||||
fns.CoinsInfo_keyImage(this.#ptr).then(readCString),
|
||||
fns.CoinsInfo_blockHeight(this.#ptr),
|
||||
fns.CoinsInfo_amount(this.#ptr),
|
||||
fns.CoinsInfo_spent(this.#ptr),
|
||||
fns.CoinsInfo_spentHeight(this.#ptr),
|
||||
fns.CoinsInfo_frozen(this.#ptr),
|
||||
fns.CoinsInfo_unlocked(this.#ptr),
|
||||
]);
|
||||
|
||||
this.#hash = hash;
|
||||
this.#keyImage = keyImage;
|
||||
this.#blockHeight = blockHeight;
|
||||
this.#amount = amount;
|
||||
this.#spent = spent;
|
||||
this.#spentHeight = spentHeight;
|
||||
this.#frozen = frozen;
|
||||
this.#unlocked = unlocked;
|
||||
}
|
||||
|
||||
get hash(): string | null {
|
||||
return this.#hash;
|
||||
}
|
||||
|
||||
get keyImage(): string | null {
|
||||
return this.#keyImage;
|
||||
}
|
||||
|
||||
get blockHeight(): bigint {
|
||||
return this.#blockHeight;
|
||||
}
|
||||
|
||||
get amount(): bigint {
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
get spent(): boolean {
|
||||
return this.#spent;
|
||||
}
|
||||
|
||||
get spentHeight(): bigint {
|
||||
return this.#spentHeight;
|
||||
}
|
||||
|
||||
get frozen(): boolean {
|
||||
return this.#frozen;
|
||||
}
|
||||
|
||||
get unlocked(): boolean {
|
||||
return this.#unlocked;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +1,84 @@
|
||||
import { CString, getSymbol, readCString, type Sanitizer } from "./utils.ts";
|
||||
import { fns } from "./bindings.ts";
|
||||
import { C_SEPARATOR, CString, maybeMultipleStrings, readCString } from "./utils.ts";
|
||||
|
||||
export type PendingTransactionPtr = Deno.PointerObject<"transactionInfo">;
|
||||
export type PendingTransactionPtr = Deno.PointerObject<"pendingTransaction">;
|
||||
|
||||
export class PendingTransaction {
|
||||
#pendingTxPtr: PendingTransactionPtr;
|
||||
sanitizer?: Sanitizer;
|
||||
export class PendingTransaction<MultDest extends boolean = false> {
|
||||
#ptr: PendingTransactionPtr;
|
||||
|
||||
constructor(pendingTxPtr: PendingTransactionPtr, sanitizer?: Sanitizer) {
|
||||
this.sanitizer = sanitizer;
|
||||
this.#pendingTxPtr = pendingTxPtr;
|
||||
#amount!: bigint;
|
||||
#dust!: bigint;
|
||||
#fee!: bigint;
|
||||
#txid!: string | string[] | null;
|
||||
#txCount!: bigint;
|
||||
|
||||
constructor(ptr: PendingTransactionPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
static async new(ptr: PendingTransactionPtr): Promise<PendingTransaction> {
|
||||
const instance = new PendingTransaction(ptr);
|
||||
|
||||
const [amount, dust, fee, txCount, txid] = await Promise.all([
|
||||
fns.PendingTransaction_amount(ptr),
|
||||
fns.PendingTransaction_dust(ptr),
|
||||
fns.PendingTransaction_fee(ptr),
|
||||
fns.PendingTransaction_txCount(ptr),
|
||||
fns.PendingTransaction_txid(ptr, C_SEPARATOR),
|
||||
]);
|
||||
|
||||
instance.#amount = amount;
|
||||
instance.#dust = dust;
|
||||
instance.#fee = fee;
|
||||
instance.#txCount = txCount;
|
||||
instance.#txid = maybeMultipleStrings(await readCString(txid));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
get amount(): bigint {
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
get dust(): bigint {
|
||||
return this.#dust;
|
||||
}
|
||||
|
||||
get fee(): bigint {
|
||||
return this.#fee;
|
||||
}
|
||||
|
||||
get txCount(): bigint {
|
||||
return this.#txCount;
|
||||
}
|
||||
|
||||
async commit(fileName: string, overwrite: boolean): Promise<boolean> {
|
||||
return await fns.PendingTransaction_commit(this.#ptr, CString(fileName), overwrite);
|
||||
}
|
||||
|
||||
async commitUR(maxFragmentLength: number): Promise<string | null> {
|
||||
const commitUR = fns.PendingTransaction_commitUR;
|
||||
if (!commitUR) return null;
|
||||
|
||||
return await readCString(
|
||||
await commitUR(this.#ptr, maxFragmentLength),
|
||||
);
|
||||
}
|
||||
|
||||
async status(): Promise<number> {
|
||||
return await getSymbol("PendingTransaction_status")(this.#pendingTxPtr);
|
||||
return await fns.PendingTransaction_status(this.#ptr);
|
||||
}
|
||||
|
||||
async errorString(): Promise<string | null> {
|
||||
if (!await this.status()) return null;
|
||||
|
||||
const error = await getSymbol("PendingTransaction_errorString")(this.#pendingTxPtr);
|
||||
if (!error) return null;
|
||||
|
||||
return await readCString(error) || null;
|
||||
const error = await fns.PendingTransaction_errorString(this.#ptr);
|
||||
return await readCString(error);
|
||||
}
|
||||
|
||||
async throwIfError(sanitize = true): Promise<void> {
|
||||
async throwIfError(): 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 getSymbol("PendingTransaction_commit")(
|
||||
this.#pendingTxPtr,
|
||||
CString(fileName),
|
||||
overwrite,
|
||||
);
|
||||
await this.throwIfError(sanitize);
|
||||
return bool;
|
||||
}
|
||||
|
||||
async commitUR(maxFragmentLength: number): Promise<string | null> {
|
||||
const commitUR = getSymbol("PendingTransaction_commitUR");
|
||||
|
||||
if (!commitUR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await commitUR(
|
||||
this.#pendingTxPtr,
|
||||
maxFragmentLength,
|
||||
);
|
||||
|
||||
if (!result) return null;
|
||||
await this.throwIfError();
|
||||
return await readCString(result) || null;
|
||||
}
|
||||
|
||||
async amount(): Promise<bigint> {
|
||||
return await getSymbol("PendingTransaction_amount")(this.#pendingTxPtr);
|
||||
}
|
||||
|
||||
async dust(): Promise<bigint> {
|
||||
return await getSymbol("PendingTransaction_dust")(this.#pendingTxPtr);
|
||||
}
|
||||
|
||||
async fee(): Promise<bigint> {
|
||||
return await getSymbol("PendingTransaction_fee")(this.#pendingTxPtr);
|
||||
}
|
||||
|
||||
async txid(separator: string, sanitize = true): Promise<string | null> {
|
||||
const result = await getSymbol("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 getSymbol("PendingTransaction_txCount")(this.#pendingTxPtr);
|
||||
}
|
||||
}
|
||||
|
||||
+2349
-445
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,41 @@
|
||||
import { fns } from "./bindings.ts";
|
||||
import { TransactionInfo, TransactionInfoPtr } from "./transaction_info.ts";
|
||||
import { CString, getSymbol } from "./utils.ts";
|
||||
import { CString } from "./utils.ts";
|
||||
|
||||
export type TransactionHistoryPtr = Deno.PointerObject<"transactionHistory">;
|
||||
|
||||
export class TransactionHistory {
|
||||
#txHistoryPtr: TransactionHistoryPtr;
|
||||
#ptr: TransactionHistoryPtr;
|
||||
|
||||
constructor(txHistoryPtr: TransactionHistoryPtr) {
|
||||
this.#txHistoryPtr = txHistoryPtr;
|
||||
#count!: number;
|
||||
|
||||
constructor(ptr: TransactionHistoryPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
async count(): Promise<number> {
|
||||
return await getSymbol("TransactionHistory_count")(this.#txHistoryPtr);
|
||||
static async new(ptr: TransactionHistoryPtr) {
|
||||
const instance = new TransactionHistory(ptr);
|
||||
instance.#count = await fns.TransactionHistory_count(ptr);
|
||||
return instance;
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
return this.#count;
|
||||
}
|
||||
|
||||
async transaction(index: number): Promise<TransactionInfo> {
|
||||
return new TransactionInfo(
|
||||
(
|
||||
await getSymbol("TransactionHistory_transaction")(this.#txHistoryPtr, index)
|
||||
) as TransactionInfoPtr,
|
||||
return TransactionInfo.new(
|
||||
await fns.TransactionHistory_transaction(this.#ptr, index) as TransactionInfoPtr,
|
||||
);
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
await getSymbol("TransactionHistory_refresh")(this.#txHistoryPtr);
|
||||
await fns.TransactionHistory_refresh(this.#ptr);
|
||||
}
|
||||
|
||||
async setTxNote(transactionId: string, note: string): Promise<void> {
|
||||
await getSymbol("TransactionHistory_setTxNote")(
|
||||
this.#txHistoryPtr,
|
||||
await fns.TransactionHistory_setTxNote(
|
||||
this.#ptr,
|
||||
CString(transactionId),
|
||||
CString(note),
|
||||
);
|
||||
|
||||
@@ -1,104 +1,147 @@
|
||||
import { dylib } from "./bindings.ts";
|
||||
import { getSymbol, readCString, Sanitizer } from "./utils.ts";
|
||||
import { fns } from "./bindings.ts";
|
||||
import { C_SEPARATOR, CString, maybeMultipleStrings, readCString, SEPARATOR } from "./utils.ts";
|
||||
|
||||
export type TransactionInfoPtr = Deno.PointerObject<"transactionInfo">;
|
||||
export type TransactionInfoPtr = Deno.PointerObject<"pendingTransaction">;
|
||||
|
||||
export class TransactionInfo {
|
||||
#txInfoPtr: TransactionInfoPtr;
|
||||
sanitizer?: Sanitizer;
|
||||
export interface TransferData {
|
||||
address: string | null;
|
||||
amount: bigint;
|
||||
}
|
||||
|
||||
constructor(txInfoPtr: TransactionInfoPtr, sanitizer?: Sanitizer) {
|
||||
this.#txInfoPtr = txInfoPtr;
|
||||
this.sanitizer = sanitizer;
|
||||
export class TransactionInfo<MultDest extends boolean = boolean> {
|
||||
#ptr: TransactionInfoPtr;
|
||||
|
||||
#amount!: bigint;
|
||||
#fee!: bigint;
|
||||
#timestamp!: bigint;
|
||||
#transfersCount!: number;
|
||||
#paymentId!: string | null;
|
||||
#hash!: string | null;
|
||||
|
||||
#subaddrAccount!: number;
|
||||
#subaddrIndex!: string | null;
|
||||
|
||||
#transfers!: readonly TransferData[];
|
||||
|
||||
constructor(ptr: TransactionInfoPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
static async new(ptr: TransactionInfoPtr): Promise<TransactionInfo> {
|
||||
const instance = new TransactionInfo(ptr);
|
||||
|
||||
const [amount, paymentId, fee, hash, subaddrIndex, subaddrAccount, timestamp, transfersCount] = await Promise.all([
|
||||
fns.TransactionInfo_amount(ptr),
|
||||
fns.TransactionInfo_paymentId(ptr).then(readCString),
|
||||
fns.TransactionInfo_fee(ptr),
|
||||
fns.TransactionInfo_hash(ptr).then(readCString),
|
||||
fns.TransactionInfo_subaddrIndex(ptr, C_SEPARATOR).then(readCString),
|
||||
fns.TransactionInfo_subaddrAccount(ptr),
|
||||
fns.TransactionInfo_timestamp(ptr),
|
||||
fns.TransactionInfo_transfers_count(ptr),
|
||||
]);
|
||||
|
||||
instance.#amount = amount;
|
||||
instance.#fee = fee;
|
||||
instance.#timestamp = timestamp;
|
||||
instance.#transfersCount = transfersCount;
|
||||
instance.#paymentId = paymentId;
|
||||
instance.#hash = hash;
|
||||
|
||||
instance.#subaddrAccount = subaddrAccount;
|
||||
instance.#subaddrIndex = subaddrIndex;
|
||||
|
||||
const transfers = [];
|
||||
for (let i = 0; i < transfersCount; ++i) {
|
||||
const [amount, address] = await Promise.all([
|
||||
fns.TransactionInfo_transfers_amount(ptr, i),
|
||||
fns.TransactionInfo_transfers_address(ptr, i).then(readCString),
|
||||
]);
|
||||
|
||||
transfers.push({ amount, address });
|
||||
}
|
||||
Object.freeze(transfers);
|
||||
instance.#transfers = transfers;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
get amount(): bigint {
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
get fee(): bigint {
|
||||
return this.#fee;
|
||||
}
|
||||
|
||||
get timestamp(): bigint {
|
||||
return this.#timestamp;
|
||||
}
|
||||
|
||||
get transfersCount(): number {
|
||||
return this.#transfersCount;
|
||||
}
|
||||
|
||||
get paymentId(): string | null {
|
||||
return this.#paymentId;
|
||||
}
|
||||
|
||||
get hash(): string | null {
|
||||
return this.#hash;
|
||||
}
|
||||
|
||||
get subaddrAccount(): number {
|
||||
return this.#subaddrAccount;
|
||||
}
|
||||
|
||||
get subaddrIndex(): string | null {
|
||||
return this.#subaddrIndex;
|
||||
}
|
||||
|
||||
get transfers(): readonly TransferData[] {
|
||||
return this.#transfers;
|
||||
}
|
||||
|
||||
async direction(): Promise<"in" | "out"> {
|
||||
switch (await getSymbol("TransactionInfo_direction")(this.#txInfoPtr)) {
|
||||
switch (await fns.TransactionInfo_direction(this.#ptr)) {
|
||||
case 0:
|
||||
return "in";
|
||||
case 1:
|
||||
return "out";
|
||||
default:
|
||||
await this.sanitizer?.();
|
||||
throw new Error("Invalid TransactionInfo direction");
|
||||
}
|
||||
}
|
||||
|
||||
async isPending(): Promise<boolean> {
|
||||
return await getSymbol("TransactionInfo_isPending")(this.#txInfoPtr);
|
||||
async description(): Promise<string | null> {
|
||||
return await readCString(
|
||||
await fns.TransactionInfo_description(this.#ptr),
|
||||
);
|
||||
}
|
||||
|
||||
async isFailed(): Promise<boolean> {
|
||||
return await getSymbol("TransactionInfo_isFailed")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async isCoinbase(): Promise<boolean> {
|
||||
return await getSymbol("TransactionInfo_isCoinbase")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async amount(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_amount")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async fee(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_fee")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async blockHeight(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_blockHeight")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async description(): Promise<string> {
|
||||
const description = await getSymbol("TransactionInfo_description")(this.#txInfoPtr);
|
||||
return await readCString(description) || "";
|
||||
}
|
||||
|
||||
async subaddrIndex(): Promise<string> {
|
||||
const subaddrIndex = await getSymbol("TransactionInfo_subaddrIndex")(this.#txInfoPtr);
|
||||
return await readCString(subaddrIndex) || "";
|
||||
}
|
||||
|
||||
async subaddrAccount(): Promise<number> {
|
||||
return await getSymbol("TransactionInfo_subaddrAccount")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async label(): Promise<string> {
|
||||
const label = await getSymbol("TransactionInfo_label")(this.#txInfoPtr);
|
||||
return await readCString(label) || "";
|
||||
async label(): Promise<string | null> {
|
||||
return await readCString(
|
||||
await fns.TransactionInfo_label(this.#ptr),
|
||||
);
|
||||
}
|
||||
|
||||
async confirmations(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_confirmations")(this.#txInfoPtr);
|
||||
return await fns.TransactionInfo_confirmations(this.#ptr);
|
||||
}
|
||||
|
||||
async unlockTime(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_unlockTime")(this.#txInfoPtr);
|
||||
return await fns.TransactionInfo_unlockTime(this.#ptr);
|
||||
}
|
||||
|
||||
async hash(): Promise<string> {
|
||||
const hash = await getSymbol("TransactionInfo_hash")(this.#txInfoPtr);
|
||||
return await readCString(hash) || "";
|
||||
async isPending(): Promise<boolean> {
|
||||
return await fns.TransactionInfo_isPending(this.#ptr);
|
||||
}
|
||||
|
||||
async timestamp(): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_timestamp")(this.#txInfoPtr);
|
||||
async isFailed(): Promise<boolean> {
|
||||
return await fns.TransactionInfo_isFailed(this.#ptr);
|
||||
}
|
||||
|
||||
async paymentId(): Promise<string> {
|
||||
const paymentId = await getSymbol("TransactionInfo_paymentId")(this.#txInfoPtr);
|
||||
return await readCString(paymentId) || "";
|
||||
}
|
||||
|
||||
async transfersCount(): Promise<number> {
|
||||
return await getSymbol("TransactionInfo_transfers_count")(this.#txInfoPtr);
|
||||
}
|
||||
|
||||
async transfersAmount(index: number): Promise<bigint> {
|
||||
return await getSymbol("TransactionInfo_transfers_amount")(this.#txInfoPtr, index);
|
||||
}
|
||||
|
||||
async transfersAddress(index: number): Promise<string> {
|
||||
const transfersAddress = await getSymbol("TransactionInfo_transfers_address")(this.#txInfoPtr, index);
|
||||
return await readCString(transfersAddress) || "";
|
||||
async isCoinbase(): Promise<boolean> {
|
||||
return await fns.TransactionInfo_isCoinbase(this.#ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { fns } from "./bindings.ts";
|
||||
import { C_SEPARATOR, CString, maybeMultipleStrings, readCString } from "./utils.ts";
|
||||
|
||||
export type UnsignedTransactionPtr = Deno.PointerObject<"pendingTransaction">;
|
||||
|
||||
export class UnsignedTransaction<MultDest extends boolean = false> {
|
||||
#ptr: UnsignedTransactionPtr;
|
||||
|
||||
#amount!: string | string[] | null;
|
||||
#fee!: string | string[] | null;
|
||||
#txCount!: bigint;
|
||||
#paymentId!: string | null;
|
||||
#recipientAddress!: string | string[] | null;
|
||||
|
||||
constructor(ptr: UnsignedTransactionPtr) {
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
async status(): Promise<number> {
|
||||
return await fns.UnsignedTransaction_status(this.#ptr);
|
||||
}
|
||||
|
||||
async errorString(): Promise<string | null> {
|
||||
return await readCString(await fns.UnsignedTransaction_errorString(this.#ptr));
|
||||
}
|
||||
|
||||
static async new(ptr: UnsignedTransactionPtr): Promise<UnsignedTransaction> {
|
||||
const instance = new UnsignedTransaction(ptr);
|
||||
|
||||
const [amount, paymentId, fee, txCount, recipientAddress] = await Promise.all([
|
||||
fns.UnsignedTransaction_amount(ptr, C_SEPARATOR).then(readCString),
|
||||
fns.UnsignedTransaction_paymentId(ptr, C_SEPARATOR).then(readCString),
|
||||
fns.UnsignedTransaction_fee(ptr, C_SEPARATOR).then(readCString),
|
||||
fns.UnsignedTransaction_txCount(ptr),
|
||||
fns.UnsignedTransaction_recipientAddress(ptr, C_SEPARATOR).then(readCString),
|
||||
]);
|
||||
|
||||
instance.#amount = maybeMultipleStrings(amount);
|
||||
instance.#fee = maybeMultipleStrings(fee);
|
||||
instance.#recipientAddress = maybeMultipleStrings(recipientAddress);
|
||||
instance.#txCount = txCount;
|
||||
instance.#paymentId = paymentId;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
get amount(): string | string[] | null {
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
get fee(): string | string[] | null {
|
||||
return this.#fee;
|
||||
}
|
||||
|
||||
get txCount(): bigint {
|
||||
return this.#txCount;
|
||||
}
|
||||
|
||||
get paymentId(): string | null {
|
||||
return this.#paymentId;
|
||||
}
|
||||
|
||||
get recipientAddress(): string | string[] | null {
|
||||
return this.#recipientAddress;
|
||||
}
|
||||
|
||||
async sign(signedFileName: string): Promise<boolean> {
|
||||
return await fns.UnsignedTransaction_sign(this.#ptr, CString(signedFileName));
|
||||
}
|
||||
|
||||
async signUR(maxFragmentLength: number): Promise<string | null> {
|
||||
const signUR = fns.UnsignedTransaction_signUR;
|
||||
if (!signUR) return null;
|
||||
|
||||
return await readCString(
|
||||
await signUR(this.#ptr, maxFragmentLength),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,19 @@
|
||||
import { dylib } from "../mod.ts";
|
||||
import type { moneroSymbols, MoneroTsDylib, WowneroTsDylib } from "./symbols.ts";
|
||||
|
||||
export type Sanitizer = () => void | PromiseLike<void>;
|
||||
import { fns } from "./bindings.ts";
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
export function CString(string: string): Deno.PointerValue<string> {
|
||||
return Deno.UnsafePointer.of(textEncoder.encode(`${string}\x00`));
|
||||
export const SEPARATOR = ",";
|
||||
export const C_SEPARATOR = CString(SEPARATOR);
|
||||
|
||||
export function maybeMultipleStrings(input: string): string | string[];
|
||||
export function maybeMultipleStrings(input: null | string): null | string | string[];
|
||||
export function maybeMultipleStrings(input: null | string): null | string | string[] {
|
||||
if (!input) return null;
|
||||
const multiple = input.split(SEPARATOR);
|
||||
return multiple.length === 1 ? multiple[0] : multiple;
|
||||
}
|
||||
|
||||
type SymbolWithoutPrefix = keyof typeof moneroSymbols extends `MONERO_${infer DylibSymbol}` ? DylibSymbol : never;
|
||||
export function getSymbol<S extends SymbolWithoutPrefix>(
|
||||
symbol: S,
|
||||
): MoneroTsDylib["symbols"][`MONERO_${S}`] | WowneroTsDylib["symbols"][`WOWNERO_${S}`] {
|
||||
if ("MONERO_free" in dylib.symbols) {
|
||||
return dylib.symbols[`MONERO_${symbol}` as const];
|
||||
} else {
|
||||
return dylib.symbols[`WOWNERO_${symbol}` as const];
|
||||
}
|
||||
export function CString(string: string): Deno.PointerValue<string> {
|
||||
return Deno.UnsafePointer.of(textEncoder.encode(`${string}\x00`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,9 +26,8 @@ export async function readCString(pointer: Deno.PointerObject, free?: boolean):
|
||||
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 getSymbol("free")(pointer);
|
||||
}
|
||||
if (string && free) await fns.free(pointer);
|
||||
return string;
|
||||
}
|
||||
|
||||
+241
-219
@@ -1,294 +1,268 @@
|
||||
import { dylib } from "./bindings.ts";
|
||||
import { CString, getSymbol, readCString, Sanitizer } from "./utils.ts";
|
||||
import { WalletManager } from "./wallet_manager.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";
|
||||
import { C_SEPARATOR, CString, readCString, SEPARATOR } from "./utils.ts";
|
||||
import { PendingTransaction, PendingTransactionPtr } from "./pending_transaction.ts";
|
||||
import { UnsignedTransaction, UnsignedTransactionPtr } from "./unsigned_transaction.ts";
|
||||
import { Coins, CoinsPtr } from "./coins.ts";
|
||||
import { fns } from "./bindings.ts";
|
||||
|
||||
export type WalletPtr = Deno.PointerObject<"walletManager">;
|
||||
|
||||
interface DaemonInfo {
|
||||
address?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
lightWallet?: boolean;
|
||||
proxyAddress?: string;
|
||||
}
|
||||
|
||||
export class Wallet {
|
||||
#walletManagerPtr: WalletManagerPtr;
|
||||
#walletPtr: WalletPtr;
|
||||
sanitizer?: Sanitizer;
|
||||
#walletManager: WalletManager;
|
||||
#ptr: WalletPtr;
|
||||
|
||||
constructor(walletManagerPtr: WalletManager, walletPtr: WalletPtr, sanitizer?: Sanitizer) {
|
||||
this.#walletPtr = walletPtr;
|
||||
this.#walletManagerPtr = walletManagerPtr.getPointer();
|
||||
this.sanitizer = sanitizer;
|
||||
constructor(walletManager: WalletManager, ptr: WalletPtr) {
|
||||
this.#walletManager = walletManager;
|
||||
this.#ptr = ptr;
|
||||
}
|
||||
|
||||
getPointer(): WalletPtr {
|
||||
return this.#walletPtr;
|
||||
getPointer() {
|
||||
return this.#ptr;
|
||||
}
|
||||
|
||||
async store(path = ""): Promise<boolean> {
|
||||
const bool = await getSymbol("Wallet_store")(this.#walletPtr, CString(path));
|
||||
await this.throwIfError();
|
||||
return bool;
|
||||
}
|
||||
async init(daemonInfo: DaemonInfo, log = false): Promise<boolean> {
|
||||
const success = await fns.Wallet_init(
|
||||
this.#ptr,
|
||||
CString(daemonInfo.address ?? ""),
|
||||
0n,
|
||||
CString(daemonInfo.username ?? ""),
|
||||
CString(daemonInfo.password ?? ""),
|
||||
false,
|
||||
daemonInfo.lightWallet ?? false,
|
||||
CString(daemonInfo.proxyAddress ?? ""),
|
||||
);
|
||||
|
||||
if (log) {
|
||||
await fns.Wallet_init3(
|
||||
this.#ptr,
|
||||
CString(""),
|
||||
CString(""),
|
||||
CString(""),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
async initWallet(daemonAddress = "http://nodex.monerujo.io:18081"): Promise<void> {
|
||||
await this.init();
|
||||
await this.setTrustedDaemon(true);
|
||||
await this.setDaemonAddress(daemonAddress);
|
||||
await this.startRefresh();
|
||||
await this.refreshAsync();
|
||||
await this.throwIfError();
|
||||
}
|
||||
|
||||
async setDaemonAddress(address: string): Promise<void> {
|
||||
await getSymbol("WalletManager_setDaemonAddress")(
|
||||
this.#walletManagerPtr,
|
||||
CString(address),
|
||||
);
|
||||
}
|
||||
|
||||
async startRefresh(): Promise<void> {
|
||||
await getSymbol("Wallet_startRefresh")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
}
|
||||
|
||||
async refreshAsync(): Promise<void> {
|
||||
await getSymbol("Wallet_refreshAsync")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
}
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
const bool = await getSymbol("Wallet_init")(
|
||||
this.#walletPtr,
|
||||
CString("http://nodex.monerujo.io:18081"),
|
||||
0n,
|
||||
CString(""),
|
||||
CString(""),
|
||||
false,
|
||||
false,
|
||||
CString(""),
|
||||
);
|
||||
await this.throwIfError();
|
||||
return bool;
|
||||
return success;
|
||||
}
|
||||
|
||||
async setTrustedDaemon(value: boolean): Promise<void> {
|
||||
await getSymbol("Wallet_setTrustedDaemon")(this.#walletPtr, value);
|
||||
return await fns.Wallet_setTrustedDaemon(this.#ptr, 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();
|
||||
async startRefresh(): Promise<void> {
|
||||
return await fns.Wallet_startRefresh(this.#ptr);
|
||||
}
|
||||
|
||||
const walletPtr = await getSymbol("WalletManager_createWallet")(
|
||||
walletManagerPtr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
CString("English"),
|
||||
0,
|
||||
async refreshAsync(): Promise<void> {
|
||||
return await fns.Wallet_refreshAsync(this.#ptr);
|
||||
}
|
||||
|
||||
async setupBackgroundSync(
|
||||
backgroundSyncType: number,
|
||||
walletPassword: string,
|
||||
backgroundCachePassword: string,
|
||||
): Promise<boolean> {
|
||||
return await fns.Wallet_setupBackgroundSync(
|
||||
this.#ptr,
|
||||
backgroundSyncType,
|
||||
CString(walletPassword),
|
||||
CString(backgroundCachePassword),
|
||||
);
|
||||
|
||||
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();
|
||||
async startBackgroundSync(): Promise<boolean> {
|
||||
return await fns.Wallet_startBackgroundSync(this.#ptr);
|
||||
}
|
||||
|
||||
const walletPtr = await getSymbol("WalletManager_openWallet")(
|
||||
walletManagerPtr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
0,
|
||||
async stopBackgroundSync(walletPassword: string): Promise<boolean> {
|
||||
return await fns.Wallet_stopBackgroundSync(this.#ptr, CString(walletPassword));
|
||||
}
|
||||
|
||||
async store(path = ""): Promise<boolean> {
|
||||
return await fns.Wallet_store(this.#ptr, CString(path));
|
||||
}
|
||||
|
||||
async close(store: boolean): Promise<boolean> {
|
||||
return await fns.WalletManager_closeWallet(this.#walletManager.getPointer(), this.#ptr, store);
|
||||
}
|
||||
|
||||
async seed(offset = ""): Promise<string | null> {
|
||||
return await readCString(
|
||||
await fns.Wallet_seed(this.#ptr, CString(offset)),
|
||||
);
|
||||
|
||||
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 getSymbol("WalletManager_recoveryWallet")(
|
||||
walletManagerPtr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
CString(mnemonic),
|
||||
0,
|
||||
restoreHeight,
|
||||
1n,
|
||||
CString(seedOffset),
|
||||
async address(accountIndex = 0n, addressIndex = 0n): Promise<string | null> {
|
||||
return await readCString(
|
||||
await fns.Wallet_address(this.#ptr, accountIndex, addressIndex),
|
||||
);
|
||||
|
||||
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 getSymbol("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 getSymbol("Wallet_balance")(this.#walletPtr, accountIndex);
|
||||
return await fns.Wallet_balance(this.#ptr, accountIndex);
|
||||
}
|
||||
|
||||
async unlockedBalance(accountIndex = 0): Promise<bigint> {
|
||||
return await getSymbol("Wallet_unlockedBalance")(this.#walletPtr, accountIndex);
|
||||
}
|
||||
|
||||
status(): Promise<number> {
|
||||
return getSymbol("Wallet_status")(this.#walletPtr);
|
||||
}
|
||||
|
||||
async errorString(): Promise<string | null> {
|
||||
if (!await this.status()) return null;
|
||||
|
||||
const error = await getSymbol("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);
|
||||
}
|
||||
return await fns.Wallet_unlockedBalance(this.#ptr, accountIndex);
|
||||
}
|
||||
|
||||
async synchronized(): Promise<boolean> {
|
||||
const synchronized = await getSymbol("Wallet_synchronized")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
return synchronized;
|
||||
return await fns.Wallet_synchronized(this.#ptr);
|
||||
}
|
||||
|
||||
async blockChainHeight(): Promise<bigint> {
|
||||
const height = await getSymbol("Wallet_blockChainHeight")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
return height;
|
||||
return await fns.Wallet_blockChainHeight(this.#ptr);
|
||||
}
|
||||
|
||||
async daemonBlockChainHeight(): Promise<bigint> {
|
||||
const height = await getSymbol("Wallet_daemonBlockChainHeight")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
return height;
|
||||
}
|
||||
|
||||
async managerBlockChainHeight(): Promise<bigint> {
|
||||
const height = await getSymbol("WalletManager_blockchainHeight")(this.#walletManagerPtr);
|
||||
await this.throwIfError();
|
||||
return height;
|
||||
}
|
||||
|
||||
async managerTargetBlockChainHeight(): Promise<bigint> {
|
||||
const height = await getSymbol("WalletManager_blockchainTargetHeight")(this.#walletManagerPtr);
|
||||
await this.throwIfError();
|
||||
return height;
|
||||
return await fns.Wallet_daemonBlockChainHeight(this.#ptr);
|
||||
}
|
||||
|
||||
async addSubaddressAccount(label: string): Promise<void> {
|
||||
await getSymbol("Wallet_addSubaddressAccount")(
|
||||
this.#walletPtr,
|
||||
CString(label),
|
||||
);
|
||||
await this.throwIfError();
|
||||
return await fns.Wallet_addSubaddressAccount(this.#ptr, CString(label));
|
||||
}
|
||||
|
||||
async numSubaddressAccounts(): Promise<bigint> {
|
||||
const accountsLen = await getSymbol("Wallet_numSubaddressAccounts")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
return accountsLen;
|
||||
return await fns.Wallet_numSubaddressAccounts(this.#ptr);
|
||||
}
|
||||
|
||||
async addSubaddress(accountIndex: number, label: string): Promise<void> {
|
||||
await getSymbol("Wallet_addSubaddress")(
|
||||
this.#walletPtr,
|
||||
return await fns.Wallet_addSubaddress(
|
||||
this.#ptr,
|
||||
accountIndex,
|
||||
CString(label),
|
||||
);
|
||||
await this.throwIfError();
|
||||
}
|
||||
|
||||
async numSubaddresses(accountIndex: number): Promise<bigint> {
|
||||
const address = await getSymbol("Wallet_numSubaddresses")(
|
||||
this.#walletPtr,
|
||||
return await fns.Wallet_numSubaddresses(
|
||||
this.#ptr,
|
||||
accountIndex,
|
||||
);
|
||||
await this.throwIfError();
|
||||
return address;
|
||||
}
|
||||
|
||||
async getSubaddressLabel(accountIndex: number, addressIndex: number): Promise<string> {
|
||||
const label = await getSymbol("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 getSubaddressLabel(accountIndex: number, addressIndex: number): Promise<string | null> {
|
||||
return await readCString(
|
||||
await fns.Wallet_getSubaddressLabel(this.#ptr, accountIndex, addressIndex),
|
||||
);
|
||||
}
|
||||
|
||||
async setSubaddressLabel(accountIndex: number, addressIndex: number, label: string): Promise<void> {
|
||||
await getSymbol("Wallet_setSubaddressLabel")(
|
||||
this.#walletPtr,
|
||||
accountIndex,
|
||||
addressIndex,
|
||||
CString(label),
|
||||
);
|
||||
await this.throwIfError();
|
||||
return await fns.Wallet_setSubaddressLabel(this.#ptr, accountIndex, addressIndex, CString(label));
|
||||
}
|
||||
|
||||
async getHistory(): Promise<TransactionHistory> {
|
||||
const transactionHistoryPointer = await getSymbol("Wallet_history")(this.#walletPtr);
|
||||
await this.throwIfError();
|
||||
return new TransactionHistory(transactionHistoryPointer as TransactionHistoryPtr);
|
||||
async isOffline(): Promise<boolean> {
|
||||
return await fns.Wallet_isOffline(this.#ptr);
|
||||
}
|
||||
|
||||
async setOffline(offline: boolean): Promise<void> {
|
||||
return await fns.Wallet_setOffline(this.#ptr, offline);
|
||||
}
|
||||
|
||||
async publicViewKey(): Promise<string | null> {
|
||||
return await readCString(await fns.Wallet_publicViewKey(this.#ptr));
|
||||
}
|
||||
|
||||
async secretViewKey(): Promise<string | null> {
|
||||
return await readCString(await fns.Wallet_secretViewKey(this.#ptr));
|
||||
}
|
||||
|
||||
async publicSpendKey(): Promise<string | null> {
|
||||
return await readCString(await fns.Wallet_publicSpendKey(this.#ptr));
|
||||
}
|
||||
|
||||
async secretSpendKey(): Promise<string | null> {
|
||||
return await readCString(await fns.Wallet_secretSpendKey(this.#ptr));
|
||||
}
|
||||
|
||||
async exportOutputs(fileName: string, all: boolean): Promise<boolean> {
|
||||
return await fns.Wallet_exportOutputs(this.#ptr, CString(fileName), all);
|
||||
}
|
||||
|
||||
async exportOutputsUR(maxFragmentLength: bigint, all: boolean): Promise<string | null> {
|
||||
const exportOutputsUR = fns.Wallet_exportOutputsUR;
|
||||
if (!exportOutputsUR) return null;
|
||||
|
||||
return await readCString(
|
||||
await exportOutputsUR(this.#ptr, maxFragmentLength, all),
|
||||
);
|
||||
}
|
||||
|
||||
async importOutputs(fileName: string): Promise<boolean> {
|
||||
return await fns.Wallet_importOutputs(this.#ptr, CString(fileName));
|
||||
}
|
||||
|
||||
async importOutputsUR(input: string): Promise<boolean | null> {
|
||||
const importOutputsUR = fns.Wallet_importOutputsUR;
|
||||
if (!importOutputsUR) return null;
|
||||
|
||||
return await importOutputsUR(this.#ptr, CString(input));
|
||||
}
|
||||
|
||||
async exportKeyImages(fileName: string, all: boolean): Promise<boolean> {
|
||||
return await fns.Wallet_exportKeyImages(this.#ptr, CString(fileName), all);
|
||||
}
|
||||
|
||||
async exportKeyImagesUR(maxFragmentLength: bigint, all: boolean): Promise<string | null> {
|
||||
const exportKeyImagesUR = fns.Wallet_exportKeyImagesUR;
|
||||
if (!exportKeyImagesUR) return null;
|
||||
|
||||
return await readCString(
|
||||
await exportKeyImagesUR(this.#ptr, maxFragmentLength, all),
|
||||
);
|
||||
}
|
||||
|
||||
async importKeyImages(fileName: string): Promise<boolean> {
|
||||
return await fns.Wallet_importKeyImages(this.#ptr, CString(fileName));
|
||||
}
|
||||
|
||||
async importKeyImagesUR(input: string): Promise<boolean | null> {
|
||||
const importKeyImagesUR = fns.Wallet_importKeyImagesUR;
|
||||
if (!importKeyImagesUR) return null;
|
||||
|
||||
return await importKeyImagesUR(this.#ptr, CString(input));
|
||||
}
|
||||
|
||||
async loadUnsignedTx(fileName: string): Promise<UnsignedTransaction> {
|
||||
const pendingTxPtr = await fns.Wallet_loadUnsignedTx(this.#ptr, CString(fileName));
|
||||
return UnsignedTransaction.new(pendingTxPtr as UnsignedTransactionPtr);
|
||||
}
|
||||
|
||||
async loadUnsignedTxUR(input: string): Promise<UnsignedTransaction | null> {
|
||||
const loadUnsignedTxUR = fns.Wallet_loadUnsignedTxUR;
|
||||
if (!loadUnsignedTxUR) return null;
|
||||
|
||||
const pendingTxPtr = await loadUnsignedTxUR(this.#ptr, CString(input));
|
||||
if (await this.status()) {
|
||||
throw this.errorString();
|
||||
}
|
||||
return UnsignedTransaction.new(pendingTxPtr as UnsignedTransactionPtr);
|
||||
}
|
||||
|
||||
async createTransaction(
|
||||
destinationAddress: string,
|
||||
amount: bigint,
|
||||
pendingTransactionPriority = 0 | 1 | 2 | 3,
|
||||
pendingTransactionPriority: 0 | 1 | 2 | 3,
|
||||
subaddressAccount: number,
|
||||
sanitize = true,
|
||||
prefferedInputs = "",
|
||||
mixinCount = 0,
|
||||
paymentId = "",
|
||||
separator = ",",
|
||||
): Promise<PendingTransaction> {
|
||||
const pendingTxPtr = await getSymbol("Wallet_createTransaction")(
|
||||
this.#walletPtr,
|
||||
): Promise<PendingTransaction | null> {
|
||||
const pendingTxPtr = await fns.Wallet_createTransaction(
|
||||
this.#ptr,
|
||||
CString(destinationAddress),
|
||||
CString(paymentId),
|
||||
amount,
|
||||
@@ -296,13 +270,61 @@ export class Wallet {
|
||||
pendingTransactionPriority,
|
||||
subaddressAccount,
|
||||
CString(prefferedInputs),
|
||||
CString(separator),
|
||||
C_SEPARATOR,
|
||||
);
|
||||
await this.throwIfError(sanitize);
|
||||
return new PendingTransaction(pendingTxPtr as PendingTransactionPtr);
|
||||
|
||||
if (!pendingTxPtr) return null;
|
||||
return PendingTransaction.new(pendingTxPtr as PendingTransactionPtr);
|
||||
}
|
||||
|
||||
async amountFromString(amount: string): Promise<bigint> {
|
||||
return await getSymbol("Wallet_amountFromString")(CString(amount));
|
||||
async createTransactionMultDest(
|
||||
destinationAddresses: string[],
|
||||
amounts: bigint[],
|
||||
amountSweepAll: boolean,
|
||||
pendingTransactionPriority: 0 | 1 | 2 | 3,
|
||||
subaddressAccount: number,
|
||||
preferredInputs: string[] = [],
|
||||
mixinCount = 0,
|
||||
paymentId = "",
|
||||
): Promise<PendingTransaction> {
|
||||
const pendingTxPtr = await fns.Wallet_createTransactionMultDest(
|
||||
this.#ptr,
|
||||
CString(destinationAddresses.join(SEPARATOR)),
|
||||
C_SEPARATOR,
|
||||
CString(paymentId),
|
||||
amountSweepAll,
|
||||
CString(amounts.join(SEPARATOR)),
|
||||
C_SEPARATOR,
|
||||
mixinCount,
|
||||
pendingTransactionPriority,
|
||||
subaddressAccount,
|
||||
CString(preferredInputs.join(SEPARATOR)),
|
||||
C_SEPARATOR,
|
||||
);
|
||||
return PendingTransaction.new(pendingTxPtr as PendingTransactionPtr);
|
||||
}
|
||||
|
||||
async coins(): Promise<Coins | null> {
|
||||
const coinsPtr = await fns.Wallet_coins(this.#ptr);
|
||||
if (!coinsPtr) return null;
|
||||
|
||||
return new Coins(coinsPtr as CoinsPtr);
|
||||
}
|
||||
|
||||
async status(): Promise<number> {
|
||||
return await fns.Wallet_status(this.#ptr);
|
||||
}
|
||||
|
||||
async errorString(): Promise<string | null> {
|
||||
if (!await this.status()) return null;
|
||||
const error = await fns.Wallet_errorString(this.#ptr);
|
||||
return await readCString(error);
|
||||
}
|
||||
|
||||
async throwIfError(): Promise<void> {
|
||||
const maybeError = await this.errorString();
|
||||
if (maybeError) {
|
||||
throw new Error(maybeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,148 @@
|
||||
import { getSymbol, Sanitizer } from "./utils.ts";
|
||||
import { fns } from "./bindings.ts";
|
||||
import { CString } from "./utils.ts";
|
||||
import { Wallet, WalletPtr } from "./wallet.ts";
|
||||
|
||||
export type WalletManagerPtr = Deno.PointerObject<"walletManager">;
|
||||
|
||||
export class WalletManager {
|
||||
#ptr: WalletManagerPtr;
|
||||
sanitizer?: Sanitizer;
|
||||
|
||||
constructor(walletManagerPtr: WalletManagerPtr, sanitizer?: Sanitizer) {
|
||||
constructor(walletManagerPtr: WalletManagerPtr) {
|
||||
this.#ptr = walletManagerPtr;
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
getPointer(): WalletManagerPtr {
|
||||
return this.#ptr;
|
||||
}
|
||||
|
||||
static async new(sanitizer?: Sanitizer) {
|
||||
const ptr = await getSymbol("WalletManagerFactory_getWalletManager")();
|
||||
static async new() {
|
||||
const ptr = await fns.WalletManagerFactory_getWalletManager();
|
||||
if (!ptr) {
|
||||
sanitizer?.();
|
||||
throw new Error("Failed retrieving wallet manager");
|
||||
}
|
||||
return new WalletManager(ptr as WalletManagerPtr, sanitizer);
|
||||
|
||||
return new WalletManager(ptr as WalletManagerPtr);
|
||||
}
|
||||
|
||||
async setDaemonAddress(address: string): Promise<void> {
|
||||
return await fns.WalletManager_setDaemonAddress(this.#ptr, CString(address));
|
||||
}
|
||||
|
||||
async createWallet(path: string, password: string): Promise<Wallet> {
|
||||
const walletPtr = await fns.WalletManager_createWallet(
|
||||
this.#ptr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
CString("English"),
|
||||
0,
|
||||
);
|
||||
|
||||
const wallet = new Wallet(this, walletPtr as WalletPtr);
|
||||
await wallet.throwIfError();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
async openWallet(path: string, password: string): Promise<Wallet> {
|
||||
const walletPtr = await fns.WalletManager_openWallet(
|
||||
this.#ptr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
0,
|
||||
);
|
||||
|
||||
const wallet = new Wallet(this, walletPtr as WalletPtr);
|
||||
await wallet.throwIfError();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
async recoverWallet(
|
||||
path: string,
|
||||
password: string,
|
||||
mnemonic: string,
|
||||
restoreHeight: bigint,
|
||||
seedOffset: string = "",
|
||||
): Promise<Wallet> {
|
||||
const walletPtr = await fns.WalletManager_recoveryWallet(
|
||||
this.#ptr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
CString(mnemonic),
|
||||
0,
|
||||
restoreHeight,
|
||||
1n,
|
||||
CString(seedOffset),
|
||||
);
|
||||
|
||||
const wallet = new Wallet(this, walletPtr as WalletPtr);
|
||||
await wallet.throwIfError();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
async recoverFromPolyseed(
|
||||
path: string,
|
||||
password: string,
|
||||
mnemonic: string,
|
||||
restoreHeight: bigint,
|
||||
passphrase = "",
|
||||
): Promise<Wallet> {
|
||||
return await this.createFromPolyseed(
|
||||
path,
|
||||
password,
|
||||
mnemonic,
|
||||
restoreHeight,
|
||||
passphrase,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async createFromPolyseed(
|
||||
path: string,
|
||||
password: string,
|
||||
mnemonic: string,
|
||||
restoreHeight: bigint,
|
||||
passphrase = "",
|
||||
newWallet = true,
|
||||
): Promise<Wallet> {
|
||||
const walletPtr = await fns.WalletManager_createWalletFromPolyseed(
|
||||
this.#ptr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
0,
|
||||
CString(mnemonic),
|
||||
CString(passphrase),
|
||||
newWallet,
|
||||
restoreHeight,
|
||||
1n,
|
||||
);
|
||||
|
||||
const wallet = new Wallet(this, walletPtr as WalletPtr);
|
||||
await wallet.throwIfError();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
async recoverFromKeys(
|
||||
path: string,
|
||||
password: string,
|
||||
restoreHeight: bigint,
|
||||
address: string,
|
||||
viewKey: string,
|
||||
spendKey: string,
|
||||
): Promise<Wallet> {
|
||||
const walletPtr = await fns.WalletManager_createWalletFromKeys(
|
||||
this.#ptr,
|
||||
CString(path),
|
||||
CString(password),
|
||||
CString("English"),
|
||||
0,
|
||||
restoreHeight,
|
||||
CString(address),
|
||||
CString(viewKey),
|
||||
CString(spendKey),
|
||||
0n,
|
||||
);
|
||||
|
||||
const wallet = new Wallet(this, walletPtr as WalletPtr);
|
||||
await wallet.throwIfError();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user