summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/.DS_Storebin0 -> 6148 bytes
-rwxr-xr-xtests/compare.ts75
-rw-r--r--[-rwxr-xr-x]tests/deno.lock32
-rw-r--r--tests/download_deps.ts278
-rw-r--r--tests/integration.test.ts616
-rwxr-xr-xtests/regression.test.ts9
-rwxr-xr-xtests/utils.ts164
7 files changed, 1036 insertions, 138 deletions
diff --git a/tests/.DS_Store b/tests/.DS_Store
new file mode 100644
index 0000000..6914114
--- /dev/null
+++ b/tests/.DS_Store
Binary files differ
diff --git a/tests/compare.ts b/tests/compare.ts
index d09bdd9..8c13fc5 100755
--- a/tests/compare.ts
+++ b/tests/compare.ts
@@ -1,80 +1,23 @@
-import { moneroSymbols as symbols, type MoneroTsDylib, type WowneroTsDylib } from "../impls/monero.ts/src/symbols.ts";
-import { loadMoneroDylib, loadWowneroDylib } from "../impls/monero.ts/src/bindings.ts";
-import { Wallet, WalletManager } from "../impls/monero.ts/mod.ts";
-import { readCString } from "../impls/monero.ts/src/utils.ts";
import { assertEquals } from "jsr:@std/assert";
+import { WalletManager } from "../impls/monero.ts/mod.ts";
+import { loadDylib } from "./utils.ts";
+
const coin = Deno.args[0] as "monero" | "wownero";
const version = Deno.args[1];
const walletInfo = JSON.parse(Deno.args[2]);
-const moneroSymbols = {
- ...symbols,
-
- "MONERO_Wallet_secretViewKey": {
- nonblocking: true,
- // void* wallet_ptr
- parameters: ["pointer"],
- // const char*
- result: "pointer",
- },
- "MONERO_Wallet_publicViewKey": {
- nonblocking: true,
- // void* wallet_ptr
- parameters: ["pointer"],
- // const char*
- result: "pointer",
- },
-
- "MONERO_Wallet_secretSpendKey": {
- nonblocking: true,
- // void* wallet_ptr
- parameters: ["pointer"],
- // const char*
- result: "pointer",
- },
- "MONERO_Wallet_publicSpendKey": {
- nonblocking: true,
- // void* wallet_ptr
- parameters: ["pointer"],
- // const char*
- result: "pointer",
- },
-} as const;
-
-type ReplaceMonero<T extends string> = T extends `MONERO${infer Y}` ? `WOWNERO${Y}` : never;
-type WowneroSymbols = { [Key in keyof typeof moneroSymbols as ReplaceMonero<Key>]: (typeof moneroSymbols)[Key] };
-const wowneroSymbols = Object.fromEntries(
- Object.entries(moneroSymbols).map(([key, value]) => [key.replace("MONERO", "WOWNERO"), value]),
-) as WowneroSymbols;
-
-let getKey: (wallet: Wallet, type: `${"secret" | "public"}${"Spend" | "View"}Key`) => Promise<string | null>;
-
-if (coin === "monero") {
- const dylib = Deno.dlopen(`tests/libs/${version}/monero_libwallet2_api_c.so`, moneroSymbols);
- loadMoneroDylib(dylib as MoneroTsDylib);
-
- getKey = async (wallet, type) =>
- await readCString(await dylib.symbols[`MONERO_Wallet_${type}` as const](wallet.getPointer()));
-} else {
- const dylib = Deno.dlopen(`tests/libs/${version}/wownero_libwallet2_api_c.so`, wowneroSymbols);
- loadWowneroDylib(dylib as WowneroTsDylib);
-
- getKey = async (wallet, type) =>
- await readCString(
- await dylib.symbols[`WOWNERO_Wallet_${type}` as const](wallet.getPointer()),
- );
-}
+loadDylib(coin, version);
const walletManager = await WalletManager.new();
-const wallet = await Wallet.open(walletManager, walletInfo.path, walletInfo.password);
+const wallet = await walletManager.openWallet(walletInfo.path, walletInfo.password);
assertEquals(await wallet.address(), walletInfo.address);
-assertEquals(await getKey(wallet, "publicSpendKey"), walletInfo.publicSpendKey);
-assertEquals(await getKey(wallet, "secretSpendKey"), walletInfo.secretSpendKey);
+assertEquals(await wallet.publicSpendKey(), walletInfo.publicSpendKey);
+assertEquals(await wallet.secretSpendKey(), walletInfo.secretSpendKey);
-assertEquals(await getKey(wallet, "publicViewKey"), walletInfo.publicViewKey);
-assertEquals(await getKey(wallet, "secretViewKey"), walletInfo.secretViewKey);
+assertEquals(await wallet.publicViewKey(), walletInfo.publicViewKey);
+assertEquals(await wallet.secretViewKey(), walletInfo.secretViewKey);
await wallet.store(walletInfo.path);
diff --git a/tests/deno.lock b/tests/deno.lock
index 02d189f..5ed1a7a 100755..100644
--- a/tests/deno.lock
+++ b/tests/deno.lock
@@ -9,13 +9,12 @@
"jsr:@std/bytes@0.221": "0.221.0",
"jsr:@std/fmt@0.221": "0.221.0",
"jsr:@std/fmt@1": "1.0.2",
- "jsr:@std/fs@1": "1.0.4",
+ "jsr:@std/fs@1": "1.0.5",
"jsr:@std/io@0.221": "0.221.0",
- "jsr:@std/path@1": "1.0.6",
- "jsr:@std/path@^1.0.6": "1.0.6",
- "jsr:@std/streams@0.221": "0.221.0",
- "jsr:@std/streams@^1.0.7": "1.0.7",
- "jsr:@std/tar@*": "0.1.2"
+ "jsr:@std/path@*": "1.0.8",
+ "jsr:@std/path@1": "1.0.8",
+ "jsr:@std/path@^1.0.7": "1.0.8",
+ "jsr:@std/streams@0.221": "0.221.0"
},
"jsr": {
"@david/dax@0.42.0": {
@@ -27,7 +26,7 @@
"jsr:@std/fs",
"jsr:@std/io",
"jsr:@std/path@1",
- "jsr:@std/streams@0.221"
+ "jsr:@std/streams"
]
},
"@david/path@0.2.0": {
@@ -55,10 +54,10 @@
"@std/fmt@1.0.2": {
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
},
- "@std/fs@1.0.4": {
- "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
+ "@std/fs@1.0.5": {
+ "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e",
"dependencies": [
- "jsr:@std/path@^1.0.6"
+ "jsr:@std/path@^1.0.7"
]
},
"@std/io@0.221.0": {
@@ -68,23 +67,14 @@
"jsr:@std/bytes"
]
},
- "@std/path@1.0.6": {
- "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
+ "@std/path@1.0.8": {
+ "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
},
"@std/streams@0.221.0": {
"integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61",
"dependencies": [
"jsr:@std/io"
]
- },
- "@std/streams@1.0.7": {
- "integrity": "1a93917ca0c58c01b2bfb93647189229b1702677f169b6fb61ad6241cd2e499b"
- },
- "@std/tar@0.1.2": {
- "integrity": "98183102395decd6268253996177804f818580ef547a25b81da0e7cc334db708",
- "dependencies": [
- "jsr:@std/streams@^1.0.7"
- ]
}
}
}
diff --git a/tests/download_deps.ts b/tests/download_deps.ts
new file mode 100644
index 0000000..8871fa5
--- /dev/null
+++ b/tests/download_deps.ts
@@ -0,0 +1,278 @@
+import { join, resolve } from "jsr:@std/path";
+import { Coin, getMoneroCTags } from "./utils.ts";
+
+export type Target = `${typeof Deno["build"]["os"]}_${typeof Deno["build"]["arch"]}`;
+
+interface FileInfo {
+ // List of mirrors that override DownloadInfo mirrors for this FileInfo
+ overrideMirrors?: [mainFilePath: string, ...fallbacks: string[]];
+ // List of file names that fallback if previous failed
+ // If first name failed and got fallbacked by another, it will get renamed to the first entry
+ names: string[];
+ sha256?: string;
+}
+
+interface DownloadInfo {
+ mirrors: string[];
+ file:
+ | FileInfo
+ | { [os in Target]?: FileInfo };
+ outDir?: string;
+}
+
+export function getFileInfo(
+ downloadInfo: DownloadInfo,
+ target: Target = `${Deno.build.os}_${Deno.build.arch}`,
+): FileInfo {
+ const fileInfo = "names" in downloadInfo.file ? downloadInfo.file : downloadInfo.file[target];
+ if (!fileInfo) {
+ throw new Error(`No fileInfo set for target: ${target}`);
+ }
+ return fileInfo;
+}
+
+async function sha256(buffer: Uint8Array): Promise<string> {
+ const hashed = new Uint8Array(await crypto.subtle.digest("SHA-256", buffer));
+ return Array.from(hashed).map((i) => i.toString(16).padStart(2, "0")).join("");
+}
+
+export async function downloadDependencies(...infos: DownloadInfo[]): Promise<void> {
+ return await downloadFiles("./tests/dependencies", `${Deno.build.os}_${Deno.build.arch}`, ...infos);
+}
+
+async function tryToDownloadFile(
+ mirror: string,
+ fileInfo: FileInfo,
+ fileName: string,
+): Promise<Uint8Array | undefined> {
+ const url = `${mirror}/${fileName}`;
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ console.warn(`Could not reach file ${fileName} on mirror: ${mirror}`);
+ await response.body?.cancel();
+ return;
+ }
+
+ const responseBuffer = await response.bytes();
+
+ if (fileInfo.sha256) {
+ const responseChecksum = await sha256(responseBuffer);
+ if (responseChecksum !== fileInfo.sha256) {
+ console.warn(
+ `Checksum mismatch on file ${fileName} on mirror: ${mirror} (${responseChecksum} != ${fileInfo.sha256})`,
+ );
+ return;
+ }
+ }
+
+ return responseBuffer;
+}
+
+async function validFileExists(filePath: string, fileInfo: FileInfo): Promise<boolean> {
+ const [mainFileName] = fileInfo.names;
+
+ let fileBuffer: Uint8Array;
+ try {
+ fileBuffer = await Deno.readFile(filePath);
+ } catch {
+ return false;
+ }
+
+ // File exists, make sure checksum matches
+ if (fileInfo.sha256) {
+ const fileChecksum = await sha256(fileBuffer);
+ if (fileChecksum !== fileInfo.sha256) {
+ console.log(
+ `File ${mainFileName} already exists, but checksum is mismatched (${fileChecksum} != ${fileInfo.sha256}), redownloading`,
+ );
+ await Deno.remove(filePath);
+ return false;
+ }
+ }
+
+ console.log(`File ${mainFileName} already exists, skipping`);
+ return true;
+}
+
+export async function downloadFiles(outDir: string, target: Target, ...infos: DownloadInfo[]): Promise<void> {
+ try {
+ await Deno.mkdir(outDir, { recursive: true });
+ } catch (error) {
+ if (!(error instanceof Deno.errors.AlreadyExists)) {
+ throw error;
+ }
+ }
+
+ for (const info of infos) {
+ const fileInfo = getFileInfo(info, target);
+ const [mainFileName] = fileInfo.names;
+
+ if (
+ await validFileExists(
+ join(outDir, info.outDir || ""),
+ fileInfo,
+ )
+ ) {
+ continue;
+ }
+
+ let buffer: Uint8Array | undefined;
+ outer: for (const mirror of fileInfo.overrideMirrors ?? info.mirrors) {
+ for (const fileName of fileInfo.names) {
+ buffer = await tryToDownloadFile(mirror, fileInfo, fileName);
+
+ if (buffer) {
+ break outer;
+ }
+ }
+ }
+
+ if (!buffer) {
+ throw new Error(`None of the mirrors for ${fileInfo.names} are available`);
+ }
+
+ const filePath = join(outDir, info.outDir ?? "", mainFileName);
+
+ await Deno.mkdir(resolve(filePath, ".."), {
+ recursive: true,
+ }).catch(() => {});
+
+ await Deno.writeFile(filePath, buffer);
+ console.info("Downloaded file", filePath);
+ }
+}
+
+export const wowneroCliInfo: DownloadInfo = {
+ mirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://codeberg.org/wownero/wownero/releases/download/v0.11.2.0/",
+ ],
+ file: {
+ linux_aarch64: {
+ names: ["wownero-aarch64-linux-gnu-59db3fe8d.tar.bz2"],
+ sha256: "07ce678302c07a6e79d90be65cbda243d843d414fbadb30f972d6c226575cfa7",
+ },
+ linux_x86_64: {
+ names: ["wownero-x86_64-linux-gnu-59db3fe8d.tar.bz2"],
+ sha256: "03880967c70cc86558d962b8a281868c3934238ea457a36174ba72b99d70107e",
+ },
+
+ darwin_aarch64: {
+ names: ["wownero-aarch64-apple-darwin11-59db3fe8d.tar.bz2"],
+ sha256: "25ff454a92b1cf036df5f28cdd2c63dcaf4b03da7da9403087371f868827c957",
+ },
+ darwin_x86_64: {
+ names: ["wownero-x86_64-apple-darwin11-59db3fe8d.tar.bz2"],
+ sha256: "7e9b6a84a560ed7a9ed7117c6f07fb228d77a06afac863d0ea1dbf833c4eddf6",
+ },
+
+ windows_x86_64: {
+ names: ["wownero-x86_64-w64-mingw32-59db3fe8d.zip"],
+ sha256: "7e0ed84afa51e3b403d635c706042859094eb6850de21c9e82cb0a104425510e",
+ },
+
+ android_aarch64: {
+ overrideMirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://codeberg.org/wownero/wownero/releases/download/v0.11.1.0/",
+ ],
+ names: ["wownero-aarch64-linux-android-v0.11.1.0.tar.bz2"],
+ sha256: "236188f8d8e7fad2ff35973f8c2417afffa8855d1a57b4c682fff5b199ea40f5",
+ },
+ },
+};
+
+export const moneroCliInfo: DownloadInfo = {
+ mirrors: [
+ "https://static.mrcyjanek.net/download_mirror/",
+ "https://downloads.getmonero.org/cli/",
+ ],
+ file: {
+ linux_aarch64: {
+ names: ["monero-linux-armv8-v0.18.3.4.tar.bz2"],
+ sha256: "33ca2f0055529d225b61314c56370e35606b40edad61c91c859f873ed67a1ea7",
+ },
+ linux_x86_64: {
+ names: ["monero-linux-x64-v0.18.3.4.tar.bz2"],
+ sha256: "51ba03928d189c1c11b5379cab17dd9ae8d2230056dc05c872d0f8dba4a87f1d",
+ },
+
+ darwin_aarch64: {
+ names: ["monero-mac-armv8-v0.18.3.4.tar.bz2"],
+ sha256: "44520cb3a05c2518ca9aeae1b2e3080fe2bba1e3596d014ceff1090dfcba8ab4",
+ },
+ darwin_x86_64: {
+ names: ["monero-mac-x64-v0.18.3.4.tar.bz2"],
+ sha256: "32c449f562216d3d83154e708471236d07db7477d6b67f1936a0a85a5005f2b8",
+ },
+
+ windows_x86_64: {
+ names: ["monero-win-x64-v0.18.3.4.zip"],
+ sha256: "54a66db6c892b2a0999754841f4ca68511741b88ea3ab20c7cd504a027f465f5",
+ },
+
+ android_aarch64: {
+ names: ["monero-android-armv8-v0.18.3.4.tar.bz2"],
+ sha256: "d9c9249d1408822ce36b346c6b9fb6b896cda16714d62117fb1c588a5201763c",
+ },
+ },
+};
+
+export const dylibInfos: Record<Coin, DownloadInfo[]> = {
+ monero: [],
+ wownero: [],
+};
+
+for (const tag of await getMoneroCTags()) {
+ for (const coin of ["monero", "wownero"] as const) {
+ dylibInfos[coin].push({
+ mirrors: [
+ `https://static.mrcyjanek.net/download_mirror/libs/${tag}/`,
+ `https://github.com/MrCyjaneK/monero_c/releases/download/${tag}/`,
+ ],
+ file: {
+ linux_aarch64: { names: [`${coin}_aarch64-linux-gnu_libwallet2_api_c.so.xz`] },
+ linux_x86_64: { names: [`${coin}_x86_64-linux-gnu_libwallet2_api_c.so.xz`] },
+ darwin_aarch64: {
+ names: [
+ `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib.xz`,
+ `${coin}_aarch64-apple-darwin_libwallet2_api_c.dylib.xz`,
+ ],
+ },
+ darwin_x86_64: {
+ names: [
+ `${coin}_x86_64-apple-darwin11_libwallet2_api_c.dylib.xz`,
+ `${coin}_x86_64-apple-darwin_libwallet2_api_c.dylib.xz`,
+ ],
+ },
+ windows_x86_64: { names: [`${coin}_x86_64-w64-mingw32_libwallet2_api_c.dll.xz`] },
+ android_aarch64: { names: [`${coin}_aarch64-linux-android_libwallet2_api_c.so.xz`] },
+ },
+ outDir: `libs/${tag}`,
+ });
+ }
+}
+
+// Download files to the download_mirror folder
+// (used on mirror to keep files up to date)
+if (import.meta.main) {
+ const supportedTargets: Target[] = [
+ "linux_x86_64",
+ "linux_aarch64",
+ "darwin_x86_64",
+ "darwin_aarch64",
+ "windows_x86_64",
+ "android_aarch64",
+ ];
+
+ for (const target of supportedTargets) {
+ await downloadFiles(
+ "./download_mirror",
+ target,
+ moneroCliInfo,
+ wowneroCliInfo,
+ ...Object.values(dylibInfos).flat(),
+ );
+ }
+}
diff --git a/tests/integration.test.ts b/tests/integration.test.ts
new file mode 100644
index 0000000..2570874
--- /dev/null
+++ b/tests/integration.test.ts
@@ -0,0 +1,616 @@
+import { CoinsInfo, Wallet, WalletManager } from "../impls/monero.ts/mod.ts";
+
+import { assert, assertEquals } from "jsr:@std/assert";
+import { $, loadDylib, prepareCli, prepareMoneroC } from "./utils.ts";
+
+const coin = Deno.env.get("COIN");
+if (coin !== "monero" && coin !== "wownero") {
+ throw new Error("COIN env var invalid or missing");
+}
+
+async function syncBlockchain(wallet: Wallet): Promise<bigint> {
+ // Wait for blockchain to sync
+ const blockHeight = await new Promise<bigint>((resolve) => {
+ let timeout: number;
+
+ const poll = async () => {
+ const blockChainHeight = await wallet.blockChainHeight();
+ const daemonBlockchainHeight = await wallet.daemonBlockChainHeight();
+ // console.log("Blockchain height:", blockChainHeight, "Daemon blockchain height:", daemonBlockchainHeight, "Remains:", daemonBlockchainHeight - blockChainHeight);
+ if (blockChainHeight === daemonBlockchainHeight) {
+ clearTimeout(timeout);
+ resolve(blockChainHeight);
+ } else {
+ setTimeout(poll, 500);
+ }
+ };
+
+ poll();
+ });
+ await new Promise((r) => setTimeout(r, 1500)); // wait for it to sync
+ return blockHeight;
+}
+
+// TODO: Change for custom address on CI
+const WOWNERO_NODE_URL = "https://node3.monerodevs.org:34568";
+const MONERO_NODE_URL = "https://nodes.hashvault.pro:18081";
+const NODE_URL = coin === "monero" ? MONERO_NODE_URL : WOWNERO_NODE_URL;
+
+const WOWNERO_DESTINATION_ADDRESS =
+ "WW3Zetw4Gg5Rk88ViCm8H8Ft8BqgAQ5DbTLZC1whv8GNFJPSoGfLViW3dAAb4Bcqpz2M1y31pZykd4ZKd8GH1UyF1fwEFg5mS";
+const MONERO_DESTINATION_ADDRESS =
+ "89BoVWjqdGVe68wdxbYurXR8sXaEb96eWKYRPxdT6wSCfZYK6XSHoj5ZRXQLtd7GzL2B2PD7Lb7GSKupkXMWjQVFAEb1CK8";
+const DESTINATION_ADDRESS = coin === "monero" ? MONERO_DESTINATION_ADDRESS : WOWNERO_DESTINATION_ADDRESS;
+
+const BILLION = 10n ** 9n;
+
+await prepareMoneroC(coin, "next");
+
+interface WalletInfo {
+ name: string;
+ password: string;
+ seed: string;
+ offset?: string;
+ address: string;
+ restoreHeight: bigint;
+
+ publicSpendKey: string;
+ secretSpendKey: string;
+ publicViewKey: string;
+ secretViewKey: string;
+}
+
+async function clearWallets() {
+ await Deno.remove("tests/wallets/", { recursive: true }).catch(() => {});
+ await Deno.mkdir("tests/wallets/");
+}
+
+loadDylib(coin, "next");
+
+Deno.test("0001-polyseed.patch", async (t) => {
+ const WALLETS: Record<"monero" | "wownero", WalletInfo[]> = {
+ monero: [
+ //#region Cake wallet, no offset
+ {
+ name: "English Wallet",
+ password: "englishwallet",
+ seed:
+ "tortoise winter play argue laptop diary tell library travel cupboard view river embark rubber plunge student",
+ restoreHeight: 3254619n,
+ address: "49PL6qHMkc4Hw3dWT5wy5NbbVd2xda8zw3tLx2BoQsNZUWDQYYpwMEKjB9BLbEKSo3S3z34bncFw6ijToTwfiEJJ5m8aefx",
+ publicSpendKey: "ccd6846ab69fdd653a8d092d89590dced40aa2862f3c24113fedfcd6469162a4",
+ secretSpendKey: "37fd2e3e933c8468beb407e5350789e23bed5df33eeeb35d3b119401988e6709",
+ publicViewKey: "6f0de7385aafd4fc259cbd0abb069295c5d3824b7e1b81f97ffcf8cccde6c72a",
+ secretViewKey: "b8095208d61fc22e4ee3a79347a889e4872cdcf1cceff991542834cef5375907",
+ },
+ {
+ name: "Chinese Traditional Wallet",
+ password: "chinesetraditionalwallet",
+ seed: "旗 铁 革 酯 紧 毅 饱 应 第 兄 植 隙 点 吐 童 赞",
+ restoreHeight: 3254619n,
+ address: "4AR8YW51Ga3DR4a47F5J8rXaqyBa8pnnF557pTyt52ZqNMFa3gfxvi13R7sbt5zHfjbF5aKsLFZQrRod3qcr5MQj4f91rLh",
+ publicSpendKey: "e80bab7b3e2d384a393b825cf4f2abb6d8d08f1742d87c1856c064a609a0647f",
+ secretSpendKey: "c7de78f9819db6755e14d2e1411f1591c2d0b3a6ee19049c30e270f81eb50401",
+ publicViewKey: "a366527c3a6d160e717ebaac11d08eccb95586991a4c87944ad750331adac020",
+ secretViewKey: "aab2ea0dc6fa2745c8c7113399242c03300664a21cac9202c315ad789b67d004",
+ },
+ {
+ name: "Chinese Simplified Wallet",
+ password: "chinesesimplifiedwallet",
+ seed: "纹 触 集 驶 朋 辨 你 版 是 益 驳 修 偏 汽 录 吨",
+ restoreHeight: 3254619n,
+ address: "47E7p1mFGNj59QNfjpXcopP1YZuGdn7NhYJv25xbPKdicnThww5DUv2aNMH7oPWsKZjQQmXMkBzUze2T6gAaXafLAF9E4Dz",
+ publicSpendKey: "93dec0155d30b818c7d64a0b0c3a678395e3e28bdd736abb2ef7c360b38449d5",
+ secretSpendKey: "60084b4ff3d99d6ca38876078721c13692635429c74ef5d71f03310cf2e0690b",
+ publicViewKey: "eff74761051a2fc77eac4d7ed1b564fd83c0e3e924199bdd5ba66b4bf4ecf351",
+ secretViewKey: "3bde0d3a1bd2877f75fc9f6ea331c976d26a62e469db1d11ddaef2cce9405d02",
+ },
+ {
+ name: "Japanese Wallet",
+ password: "japanesewallet",
+ seed:
+ "きほん ことば そうび きどう なまえ ひさしぶり ごうけい ふひょう ぎゅうにく しはらい きびしい はんとし ととのえる たかい とかい るりがわら",
+ restoreHeight: 3254619n,
+ address: "45YYsW5do7NjGSaRZPAkPQ9mi4HQsMv3w86HFddtkZi4cCbqtFiVoqJjdFobCtCwpBPZWSnUtmrU2G9fLpEE7vsQU3aZyeE",
+ publicSpendKey: "677ac034b7c3e9fcb178370e3df067346ff5703db8273e2a65010862a85935d2",
+ secretSpendKey: "1fe9110cc46c58ed0f5cb6c6e189e246f6c3cfbf459de2ed1565838b6e08780e",
+ publicViewKey: "7276d8537e0cd9fed6b9327177ed9086e159f7d63b9fb35a92693cdbf322ffef",
+ secretViewKey: "f3c8f4121cdf07616453f005e7f2cf0474cf3608ec632f4e81165ec1c5b4aa0c",
+ },
+ {
+ name: "Korean Wallet",
+ password: "koreanwallet",
+ seed: "단골 운전 일대 제작 구역 보자기 대한민국 답장 쇼핑 논문 편견 대전 충돌 강당 형제 볼펜",
+ restoreHeight: 3254619n,
+ address: "42DNYppLMXki8j3urYuXaTU1S9EBrSJ71aVNbzr4fKfnXryMwJ2rFt6Y5eCP9vpej9AdrZqNFXDFB1VmkjzjX5e5URJ9q8c",
+ publicSpendKey: "0f96caeac7cd4ff5eb55810b4eb87ca1779637e949d138c837fe9a776b3c51b8",
+ secretSpendKey: "d86c40f13694e7511499cdb22db4e96ee2990cfffde52274248bb8818a36c406",
+ publicViewKey: "826438a35b0f63b9d0b877269a0468399b3f60513e079602f73c4b0f7cd0e6f3",
+ secretViewKey: "3773b61c0cac3a2e15d03b989461704c6e90916561c6b98d035bd3d2caf88c05",
+ },
+ {
+ name: "Portugese Wallet",
+ password: "portugesewallet",
+ seed:
+ "inscrito raposa vermelho medusa apetite bacharel quantia usado poupar pilotar sigilo ideia robalo ignorado desgaste intimar",
+ restoreHeight: 3254619n,
+ address: "43xw29tpLnU5VaPZ8Nuzz7DqrnUip5tmWBn3aukkZAyzgHTscJHvESy6pmYutLebQAB9TJgGhoCAWhPK6WJ39CpiD29fbwj",
+ publicSpendKey: "3dcbbde593acc11adc291eeeec8bc44cc7943192d54fb1406de435b4d7b73dea",
+ secretSpendKey: "9b94f57038b07b8280f27cedbed6e53472b48fe4683de15cc9e034fdddfcbb05",
+ publicViewKey: "dce02ef2ca595e22d1242a53b5235f3ca8508e22386d03f171bc77ef856e1b6a",
+ secretViewKey: "27bdd486a74f9b7c7a79c39c8ff7438a3fc862bb1ec3f6ef035a8acedf876705",
+ },
+ {
+ name: "Czech Wallet",
+ password: "czechwallet",
+ seed:
+ "ulice louskat odmlka parodie dominant slanina sukno vodivost zimnice vykonat sundat kalibr dobro moucha kometa legenda",
+ restoreHeight: 3254619n,
+ address: "499LrJgGPkFA1BPvF4xqr5bwZfKyCZah1C2CEhvqpW7YGQddwWYyR2L1F1TJhqyxxwa4TXKYZM4bb8ukq3kein1cPMNLLi4",
+ publicSpendKey: "c6796799947e0c35d3720af264d6a6d0e5a574cea1cde441e343b828a00c875c",
+ secretSpendKey: "61f9c86a71744a101c36b5628c1c4324e49e84861f136cbd2d0f1477ba5ace0b",
+ publicViewKey: "1d75ed535e3dfd0171a4a714aef368c5a6862aaf9972422f49cd76d232f88fc6",
+ secretViewKey: "47fd8eadc1848a09b343815a74185835a0b2cc8567e61022322dd52f6aaf2004",
+ },
+ {
+ name: "Spanish Wallet",
+ password: "spanishwallet",
+ seed:
+ "rehén torpedo remo existir fuente dama culpa riqueza cebolla supremo vereda odio novio sumar espía margen",
+ restoreHeight: 3254619n,
+ address: "41xeBwVJEpVVrJntrvUXafJR4mk9x3tfW3zXxamh8G951SM9BbBLgJSgzwdCywLRrbZvipLL2Azu9jKbu4Y9Hey3MUF6f19",
+ publicSpendKey: "08e31c09ad35beac7bcb9e4a689e40681df1b42cd2686511e344d02606bfc802",
+ secretSpendKey: "19ae689bdd594fde5d06b66e4c7a89d9783c02694ae615e33928b38f0c52120c",
+ publicViewKey: "9cdf4adfd9c7c7ef236c9084a0ceb4c4da6017b9fd1b6cfd04e02527ea6f06b5",
+ secretViewKey: "40ae98266cadfbf546e861cac2b2e93ce921f9db2e02f072d86aea274a182006",
+ },
+ {
+ name: "Italian Wallet",
+ password: "italianwallet",
+ seed:
+ "fuso rinvenire astenuto camicia erboso icona bollito esito spettrale abisso dogma appunto prefisso gracile podismo araldica",
+ restoreHeight: 3254619n,
+ address: "46Kkbh8jLorHRKJEV7C4hNczZknHeB6w4GHpLWkpfThMdPLeeU8MFRmDfMqioYyacCTAG9wZ9y9UHZDNhehEssDVHePj3Ja",
+ publicSpendKey: "7c0b928d9d8349622a07673a9721e9d72f60ccfc8e83935b699c379999104cd9",
+ secretSpendKey: "673b61bdb369cc61a022e0eab47e5e41cf86e99a80979f75ebcda1219ee0ab01",
+ publicViewKey: "8858e908f756c44bb286d5b657824d9c660386c7db0b0ec0974d268a7432bc93",
+ secretViewKey: "5012dbfa9e1cd2a30c2a18654dc9c8bee128af8fe7246c46e5f4def76705fe0f",
+ },
+ //#endregion
+ //#region Feather wallet, offset
+ {
+ name: "English Wallet (offset)",
+ password: "englishwallet",
+ seed: "loud fix cattle broken right main web rather write aunt left nation broken ship program ten",
+ offset: "englishoffset",
+ restoreHeight: 3263855n,
+ address: "4B2QGWy9as7bwwLNq2DQ26Q3woahpTLbR7d8vJE1uKL5gobU9iMydFqbVrYa9ixfrnAvnuwT9BXpkBx1APocbJfb2drFuQi",
+ publicSpendKey: "f817ca86625d1ed0ef81ccb8a4e82b89cfc3345512c7b82798ba2f5982b7daed",
+ secretSpendKey: "39ae15e92e08a0903652b4b0f187d740d2a5bf08e77879babb345b9a78ca6504",
+ publicViewKey: "f7fb585b9a288cce3f3b1a5f0ca6873b5a2fef8afb3bd94174ac31cbed53620e",
+ secretViewKey: "d5676e49438b0cd38c6a699ab783c11f21e1a7ebc1c9174121e37456a97f380d",
+ },
+ //#endregion
+ ],
+ wownero: [
+ {
+ name: "English Wowlet",
+ password: "englishwallet",
+ seed: "fragile proud oven shove trend visit oxygen dove pledge entire pencil exist throw type large chase",
+ restoreHeight: 0n,
+ address: "Wo4ExnCfajHZcVY9Q4XgjTYHu9GwyT9E7dZQuoFhY7HNWr2X6iD2wuB1asHQ1DVEtNYSLjqiCzJVDg5ZKeWnbKDe1LD9Wwy91",
+ publicSpendKey: "88c53568fd38c2f957f229a7d4dabb142e4fcfd7c128da922b63e4f4df25b26e",
+ secretSpendKey: "b131442ee0aecd410947a74bf622e2833397f372ee843fc3d291cb16a343e308",
+ publicViewKey: "ea7a909bc832037db14c6a537357bbf2eedee84b7d00e9a2b1d718d92fe52693",
+ secretViewKey: "96e03a70a4956656be6cc1fa2252f159ae0b2d2fdc21f761fc7e8d0316931708",
+ },
+ // TODO: Add other localized wallets for Wownero
+ ],
+ };
+
+ await clearWallets();
+
+ for (const walletInfo of WALLETS[coin]) {
+ await t.step(walletInfo.name, async () => {
+ const walletManager = await WalletManager.new();
+ const path = `tests/wallets/${walletInfo.name}`;
+
+ const wallet = await walletManager.recoverFromPolyseed(
+ path,
+ walletInfo.password,
+ walletInfo.seed,
+ walletInfo.restoreHeight,
+ walletInfo.offset,
+ );
+
+ await wallet.init({}); // empty daemon address for offline test
+
+ assertEquals(await wallet.address(), walletInfo.address);
+
+ assertEquals(await wallet.publicSpendKey(), walletInfo.publicSpendKey);
+ assertEquals(await wallet.secretSpendKey(), walletInfo.secretSpendKey);
+
+ assertEquals(await wallet.publicViewKey(), walletInfo.publicViewKey);
+ assertEquals(await wallet.secretViewKey(), walletInfo.secretViewKey);
+
+ await wallet.close(true);
+ });
+ }
+});
+
+Deno.test("0002-wallet-background-sync-with-just-the-view-key.patch", async () => {
+ await clearWallets();
+
+ const walletManager = await WalletManager.new();
+ const wallet = await walletManager.createWallet("tests/wallets/squirrel", "belka");
+ await wallet.init({
+ address: NODE_URL,
+ });
+
+ const walletInfo = {
+ address: await wallet.address(),
+ publicSpendKey: await wallet.publicSpendKey(),
+ secretSpendKey: await wallet.secretSpendKey(),
+ publicViewKey: await wallet.publicViewKey(),
+ secretViewKey: await wallet.secretViewKey(),
+ };
+
+ await wallet.setupBackgroundSync(2, "belka", "background-belka");
+ await wallet.startBackgroundSync();
+ await wallet.close(true);
+
+ const backgroundWallet = await walletManager.openWallet(
+ "tests/wallets/squirrel.background",
+ "background-belka",
+ );
+ await backgroundWallet.init({ address: NODE_URL });
+
+ const blockChainHeight = await syncBlockchain(backgroundWallet);
+ await backgroundWallet.refreshAsync();
+
+ await backgroundWallet.close(true);
+
+ const reopenedWallet = await walletManager.openWallet("tests/wallets/squirrel", "belka");
+ await reopenedWallet.throwIfError();
+ await reopenedWallet.refreshAsync();
+
+ assertEquals(await reopenedWallet.blockChainHeight(), blockChainHeight);
+ assertEquals(
+ walletInfo,
+ {
+ address: await reopenedWallet.address(),
+ publicSpendKey: await reopenedWallet.publicSpendKey(),
+ secretSpendKey: await reopenedWallet.secretSpendKey(),
+ publicViewKey: await reopenedWallet.publicViewKey(),
+ secretViewKey: await reopenedWallet.secretViewKey(),
+ },
+ );
+
+ await reopenedWallet.close(true);
+});
+
+Deno.test("0004-coin-control.patch", {
+ ignore: coin === "wownero" || !(
+ Deno.env.get("SECRET_WALLET_PASSWORD") &&
+ Deno.env.get("SECRET_WALLET_MNEMONIC") &&
+ Deno.env.get("SECRET_WALLET_RESTORE_HEIGHT")
+ ),
+}, async (t) => {
+ await clearWallets();
+
+ const walletManager = await WalletManager.new();
+ const wallet = await walletManager.recoverFromPolyseed(
+ "tests/wallets/secret-wallet",
+ Deno.env.get("SECRET_WALLET_PASSWORD")!,
+ Deno.env.get("SECRET_WALLET_MNEMONIC")!,
+ BigInt(Deno.env.get("SECRET_WALLET_RESTORE_HEIGHT")!),
+ );
+
+ assertEquals(
+ await wallet.address(),
+ "434dZdLzhymcoNyGSBUJAqhDCLtBECN6698CGRMYByuEAYtpxXdbiibQb3t4qX3SiZi9vDWkxeiEF8kmDGmEoEZ4VMG8Nvh",
+ );
+
+ await wallet.init({ address: NODE_URL });
+ await wallet.refreshAsync();
+
+ // Wait for blockchain to sync
+ await syncBlockchain(wallet);
+
+ await wallet.refreshAsync();
+ await wallet.store();
+ await wallet.refreshAsync();
+
+ const coins = (await wallet.coins())!;
+ await coins.refresh();
+
+ // COINS:
+ // 5x 0.001XMR 1x 0.005XMR (in no particular order)
+ await t.step("preffered_inputs", async (t) => {
+ const coinsCount = await coins.count();
+
+ const availableCoinsData: Record<string, {
+ index: number;
+ coin: CoinsInfo;
+ keyImage: string | null;
+ amount: bigint;
+ }[]> = {
+ ["0.001"]: [],
+ ["0.005"]: [],
+ };
+
+ const freezeAll = async () => {
+ for (const [_, coinsData] of Object.entries(availableCoinsData)) {
+ for (const coinData of coinsData) {
+ await coins.setFrozen(coinData.index);
+ }
+ }
+ await coins.refresh();
+ };
+
+ const thawAll = async () => {
+ for (const [_, coinsData] of Object.entries(availableCoinsData)) {
+ for (const coinData of coinsData) {
+ await coins.thaw(coinData.index);
+ }
+ }
+ await coins.refresh();
+ };
+
+ let availableCoinsCount = 0;
+ let totalAvailableAmount = 0n;
+ for (let i = 0; i < coinsCount; ++i) {
+ const coin = (await coins.coin(i))!;
+ if (coin.spent) {
+ continue;
+ }
+
+ let humanReadableAmount: string;
+ if (coin.amount === BILLION) {
+ humanReadableAmount = "0.001";
+ } else if (coin.amount === 5n * BILLION) {
+ humanReadableAmount = "0.005";
+ } else {
+ throw new Error("Invalid coin amount! Only 5x0.01XMR coins and 1x0.05XMR coin should be available");
+ }
+
+ availableCoinsData[humanReadableAmount].push({
+ index: i,
+ coin,
+ keyImage: coin.keyImage,
+ amount: coin.amount,
+ });
+
+ totalAvailableAmount += coin.amount;
+ availableCoinsCount += 1;
+
+ await coins.thaw(i);
+ }
+
+ await coins.refresh();
+
+ assertEquals(availableCoinsCount, 6);
+ assertEquals(totalAvailableAmount, 10n * BILLION);
+
+ await t.step("Try to spend 0.002XMR by using only one 0.001XMR coin", async () => {
+ const transaction = await wallet.createTransaction(
+ DESTINATION_ADDRESS,
+ 2n * BILLION,
+ 0,
+ 0,
+ availableCoinsData["0.001"][0].keyImage!,
+ );
+
+ if (!transaction) {
+ throw new Error("Failed creating a transaction");
+ }
+
+ assertEquals(await transaction.status(), 1);
+ });
+
+ await t.step("Try to spend 0.002XMR with only 0.001XMR unlocked balance", async () => {
+ await freezeAll();
+ await coins.thaw(availableCoinsData["0.001"][0].index);
+
+ const transaction = await wallet.createTransaction(DESTINATION_ADDRESS, 2n * BILLION, 0, 0);
+
+ if (!transaction) {
+ throw new Error("Failed creating a transaction: " + await wallet.errorString());
+ }
+
+ assertEquals(await transaction.status(), 1);
+ assert((await transaction.errorString())?.includes("not enough money to transfer"));
+
+ await thawAll();
+ });
+
+ await t.step("Try to spend 0.002XMR + fee with only 0.002XMR unlocked balance", async () => {
+ await freezeAll();
+ await coins.thaw(availableCoinsData["0.001"][0].index);
+ await coins.thaw(availableCoinsData["0.001"][1].index);
+
+ const transaction = await wallet.createTransaction(
+ DESTINATION_ADDRESS,
+ 2n * BILLION,
+ 0,
+ 0,
+ availableCoinsData["0.001"][0].keyImage!,
+ );
+
+ if (!transaction) {
+ throw new Error("Failed creating a transaction: " + await wallet.errorString());
+ }
+
+ assertEquals(await transaction.status(), 1);
+ assertEquals(
+ (await transaction.errorString())?.split("\n")[0],
+ "not enough money to transfer, overall balance only 0.002000000000, sent amount 0.002000000000",
+ );
+ });
+
+ await thawAll();
+ });
+
+ await t.step("spend more than unfrozen balance", async () => {
+ const unlockedBalance = await wallet.unlockedBalance();
+ const transaction = await wallet.createTransaction(DESTINATION_ADDRESS, unlockedBalance + 1n, 0, 0);
+
+ if (!transaction) {
+ throw new Error("Failed creating a transaction: " + await wallet.errorString());
+ }
+
+ assertEquals(await transaction.status(), 1);
+ assert(
+ await transaction.errorString(),
+ "not enough money to transfer, overall balance only 0.001000000000, sent amount 0.001000000001",
+ );
+ });
+
+ await wallet.close(true);
+});
+
+Deno.test("0009-Add-recoverDeterministicWalletFromSpendKey.patch", async () => {
+ await Promise.all([
+ prepareCli(coin),
+ clearWallets(),
+ ]);
+
+ const walletManager = await WalletManager.new();
+ const wallet = await walletManager.createWallet("tests/wallets/stoat", "gornostay");
+ const moneroCSeed = await wallet.seed();
+ await wallet.close(true);
+
+ await Deno.remove("./tests/wallets/stoat");
+
+ const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`;
+ const moneroCliSeed = (await $.raw`${cliPath} --wallet-file ./tests/wallets/stoat --password gornostay --command seed`
+ .stdinText(`gornostay\n`)
+ .lines()).slice(-3).join(" ");
+
+ assertEquals(moneroCSeed, moneroCliSeed);
+});
+
+Deno.test("0012-WIP-UR-functions.patch", {
+ ignore: coin === "wownero" || !(
+ Deno.env.get("SECRET_WALLET_PASSWORD") &&
+ Deno.env.get("SECRET_WALLET_MNEMONIC") &&
+ Deno.env.get("SECRET_WALLET_RESTORE_HEIGHT")
+ ),
+}, async (t) => {
+ for (const method of ["UR", "file"] as const) {
+ await clearWallets();
+
+ const walletManager = await WalletManager.new();
+
+ const airgap = await walletManager.recoverFromPolyseed(
+ "tests/wallets/secret-wallet",
+ Deno.env.get("SECRET_WALLET_PASSWORD")!,
+ Deno.env.get("SECRET_WALLET_MNEMONIC")!,
+ BigInt(Deno.env.get("SECRET_WALLET_RESTORE_HEIGHT")!),
+ );
+ await airgap.init({ address: "" });
+
+ const online = await walletManager.recoverFromKeys(
+ "tests/wallets/horse-online",
+ "loshad-online",
+ BigInt(Deno.env.get("SECRET_WALLET_RESTORE_HEIGHT")!) - 2000n,
+ (await airgap.address())!,
+ (await airgap.secretViewKey())!,
+ "",
+ );
+ await online.init({ address: NODE_URL });
+ await online.refreshAsync();
+
+ await syncBlockchain(online);
+
+ await online.refreshAsync();
+ await online.store();
+ await online.refreshAsync();
+
+ if (method === "UR") {
+ await t.step({
+ name: "Sync wallets (UR)",
+ ignore: coin === "wownero", // Wownero doesn't have UR methods
+ fn: async () => {
+ try {
+ const outputs = await online.exportOutputsUR(130n, false);
+ await airgap.importOutputsUR(outputs!);
+
+ const keyImages = await airgap.exportKeyImagesUR(130n, false);
+ await online.importKeyImagesUR(keyImages!);
+ } catch {
+ const outputs = await online.exportOutputsUR(130n, true);
+ await airgap.importOutputsUR(outputs!);
+
+ const keyImages = await airgap.exportKeyImagesUR(130n, true);
+ await online.importKeyImagesUR(keyImages!);
+ }
+ },
+ });
+
+ await t.step({
+ name: "Transaction (UR)",
+ ignore: coin === "wownero",
+ fn: async () => {
+ const transaction = await online.createTransaction(DESTINATION_ADDRESS, 1n * BILLION, 0, 0);
+ if (!transaction) {
+ throw new Error("Failed creating online transaction: " + await online.errorString());
+ }
+
+ const input = await transaction.commitUR(130);
+
+ const unsignedTx = (await airgap.loadUnsignedTxUR(input!))!;
+ if (!unsignedTx) {
+ throw new Error("Failed creating unsigned transaction: " + await online.errorString());
+ }
+
+ assertEquals(await unsignedTx.status(), 0);
+ assertEquals(unsignedTx.recipientAddress, DESTINATION_ADDRESS);
+ assert(!isNaN(Number(unsignedTx.fee)));
+ assertEquals(unsignedTx.amount, "1000000000");
+
+ await unsignedTx.signUR(130);
+ assertEquals(await unsignedTx.status(), 0, (await unsignedTx.errorString())!);
+ },
+ });
+ } else {
+ await t.step("Sync wallets (File)", async () => {
+ try {
+ await online.exportOutputs("tests/wallets/outputs", false);
+ await airgap.importOutputs("tests/wallets/outputs");
+
+ await airgap.exportKeyImages("tests/wallets/keyImages", false);
+ await online.importKeyImages("tests/wallets/keyImages");
+ } catch {
+ await online.exportOutputs("tests/wallets/outputs", true);
+ await airgap.importOutputs("tests/wallets/outputs");
+
+ await airgap.exportKeyImages("tests/wallets/keyImages", true);
+ await online.importKeyImages("tests/wallets/keyImages");
+ }
+ });
+
+ await t.step("Transaction (File)", async () => {
+ const transaction = await online.createTransaction(DESTINATION_ADDRESS, 1n * BILLION, 0, 0);
+ if (!transaction) {
+ throw new Error("Failed creating online transaction: " + await online.errorString());
+ }
+
+ await transaction.commit("tests/wallets/transaction", false);
+
+ const unsignedTx = await airgap.loadUnsignedTx("tests/wallets/transaction");
+ if (!unsignedTx) {
+ throw new Error("Failed creating unsigned transaction: " + await online.errorString());
+ }
+
+ assertEquals(await unsignedTx.status(), 0);
+ assertEquals(unsignedTx.amount, "1000000000");
+ assertEquals(unsignedTx.recipientAddress, DESTINATION_ADDRESS);
+ assert(!isNaN(Number(unsignedTx.fee)));
+
+ await unsignedTx.sign("tests/wallets/signed-transaction");
+ assertEquals(await unsignedTx.status(), 0);
+ });
+ }
+ }
+});
diff --git a/tests/regression.test.ts b/tests/regression.test.ts
index 82a9f95..797720f 100755
--- a/tests/regression.test.ts
+++ b/tests/regression.test.ts
@@ -1,4 +1,4 @@
-import { $, createWalletViaCli, downloadCli, getMoneroC, getMoneroCTags } from "./utils.ts";
+import { $, createWalletViaCli, getMoneroCTags, prepareCli, prepareMoneroC } from "./utils.ts";
const coin = Deno.env.get("COIN");
if (coin !== "monero" && coin !== "wownero") {
@@ -11,7 +11,7 @@ Deno.test(`Regression tests (${coin})`, async (t) => {
const tags = await getMoneroCTags();
const latestTag = tags[0];
- await Promise.all([getMoneroC(coin, "next"), await getMoneroC(coin, latestTag), downloadCli(coin)]);
+ await Promise.all([prepareMoneroC(coin, "next"), await prepareMoneroC(coin, latestTag), prepareCli(coin)]);
await t.step("Simple (next, latest, next)", async () => {
const walletInfo = await createWalletViaCli(coin, "dog", "sobaka");
@@ -27,13 +27,10 @@ Deno.test(`Regression tests (${coin})`, async (t) => {
const walletInfo = await createWalletViaCli(coin, "cat", "koshka");
for (const version of tags.toReversed()) {
- if (version !== "next" && version !== tags[0]) await getMoneroC(coin, version);
+ if (version !== "next" && version !== tags[0]) await prepareMoneroC(coin, version);
await $`deno run -A ./tests/compare.ts ${coin} ${version} ${JSON.stringify(walletInfo)}`;
}
-
- await Deno.remove("./tests/wallets", { recursive: true }).catch(() => {});
});
await Deno.remove("./tests/wallets", { recursive: true }).catch(() => {});
- await Deno.remove("./tests/libs", { recursive: true }).catch(() => {});
});
diff --git a/tests/utils.ts b/tests/utils.ts
index 028e0ff..9408f54 100755
--- a/tests/utils.ts
+++ b/tests/utils.ts
@@ -1,4 +1,18 @@
import { build$, CommandBuilder } from "jsr:@david/dax";
+import { dirname, join } from "jsr:@std/path";
+import {
+ downloadDependencies,
+ dylibInfos,
+ getFileInfo,
+ moneroCliInfo,
+ Target,
+ wowneroCliInfo,
+} from "./download_deps.ts";
+import { loadMoneroDylib, loadWowneroDylib, moneroSymbols, wowneroSymbols } from "../impls/monero.ts/mod.ts";
+
+export type Coin = "monero" | "wownero";
+
+const target = `${Deno.build.os}_${Deno.build.arch}` as const;
export const $ = build$({
commandBuilder: new CommandBuilder()
@@ -8,34 +22,83 @@ export const $ = build$({
.stderr("inherit"),
});
-type Coin = "monero" | "wownero";
+export const dylibNames = (coin: Coin, version: MoneroCVersion) => ({
+ linux_x86_64: `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`,
+ darwin_aarch64: version === "next"
+ ? `${coin}_aarch64-apple-darwin_libwallet2_api_c.dylib`
+ : `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`,
+ windows_x86_64: `${coin}_x86_64-w64-mingw32_libwallet2_api_c.dll`,
+} as Partial<Record<Target, string>>);
+
+export const moneroTsDylibNames = (coin: Coin) => ({
+ linux_x86_64: `${coin}_libwallet2_api_c.so`,
+ darwin_aarch64: `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`,
+ windows_x86_64: `${coin}_libwallet2_api_c.dll`,
+} as Partial<Record<Target, string>>);
+
+export function loadDylib(coin: Coin, version: MoneroCVersion) {
+ const dylibName = moneroTsDylibNames(coin)[target]!;
+
+ if (coin === "monero") {
+ const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, moneroSymbols);
+ loadMoneroDylib(dylib);
+ return dylib;
+ } else {
+ const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, wowneroSymbols);
+ loadWowneroDylib(dylib);
+ return dylib;
+ }
+}
-export async function downloadMoneroCli() {
- const MONERO_CLI_FILE_NAME = "monero-linux-x64-v0.18.3.4";
- const MONERO_WALLET_CLI_URL = `https://downloads.getmonero.org/cli/${MONERO_CLI_FILE_NAME}.tar.bz2`;
+async function exists(path: string): Promise<boolean> {
+ try {
+ await Deno.stat(path);
+ return true;
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return false;
+ }
+ throw error;
+ }
+}
- await $`wget ${MONERO_WALLET_CLI_URL}`;
- await $
- .raw`tar -xvf ${MONERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=monero-cli --strip-components=1 -C tests`;
- await $.raw`rm ${MONERO_CLI_FILE_NAME}.tar.bz2`;
+export async function extract(path: string, out: string) {
+ const outDir = out.endsWith("/") ? out : dirname(out);
+ await Deno.mkdir(outDir, { recursive: true });
+
+ if (path.endsWith(".tar.bz2")) {
+ let args = `-C ${dirname(out)}`;
+ if (outDir === out) {
+ args = `-C ${out} --strip-components=1`;
+ }
+ await $.raw`tar -xf ${path} ${args}`;
+ } else if (path.endsWith(".zip")) {
+ await $.raw`unzip ${path} -nu -d ${outDir}`;
+ } else if (path.endsWith(".xz")) {
+ await $.raw`xz -kd ${path}`;
+ await Deno.rename(path.slice(0, -3), out);
+ } else {
+ throw new Error("Unsupported archive file for:" + path);
+ }
}
-export async function downloadWowneroCli() {
- const WOWNERO_CLI_FILE_NAME = "wownero-x86_64-linux-gnu-59db3fe8d";
- const WOWNERO_WALLET_CLI_URL =
- `https://codeberg.org/wownero/wownero/releases/download/v0.11.2.0/wownero-x86_64-linux-gnu-59db3fe8d.tar.bz2`;
+export async function prepareMoneroCli() {
+ await downloadDependencies(moneroCliInfo);
+ const path = join("./tests/dependencies", moneroCliInfo.outDir ?? "", getFileInfo(moneroCliInfo).names[0]);
+ await extract(path, "./tests/dependencies/monero-cli/");
+}
- await $`wget ${WOWNERO_WALLET_CLI_URL}`;
- await $
- .raw`tar -xvf ${WOWNERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=wownero-cli --strip-components=1 -C tests`;
- await $.raw`rm ${WOWNERO_CLI_FILE_NAME}.tar.bz2`;
+export async function prepareWowneroCli() {
+ await downloadDependencies(wowneroCliInfo);
+ const path = join("./tests/dependencies", wowneroCliInfo.outDir ?? "", getFileInfo(wowneroCliInfo).names[0]);
+ await extract(path, "./tests/dependencies/wownero-cli/");
}
-export function downloadCli(coin: Coin) {
+export function prepareCli(coin: Coin) {
if (coin === "wownero") {
- return downloadWowneroCli();
+ return prepareWowneroCli();
}
- return downloadMoneroCli();
+ return prepareMoneroCli();
}
interface WalletInfo {
@@ -54,7 +117,7 @@ export async function createWalletViaCli(
password: string,
): Promise<WalletInfo> {
const path = `./tests/wallets/${name}`;
- const cliPath = `./tests/${coin}-cli/${coin}-wallet-cli`;
+ const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`;
await $
.raw`${cliPath} --generate-new-wallet ${path} --password ${password} --mnemonic-language English --command exit`
@@ -97,35 +160,46 @@ export async function createWalletViaCli(
export type MoneroCVersion = "next" | (string & {});
export async function getMoneroCTags(): Promise<string[]> {
- return ((
- await (await fetch(
- "https://api.github.com/repos/MrCyjanek/monero_c/releases",
- )).json()
- ) as { tag_name: string }[])
- .map(({ tag_name }) => tag_name);
+ const response = await fetch("https://static.mrcyjanek.net/monero_c/release.php");
+
+ if (!response.ok) {
+ throw new Error(`Could not receive monero_c release tags: ${await response.text()}`);
+ }
+
+ const json = await response.json() as { tag_name: string }[];
+ return json.map(({ tag_name }) => tag_name);
}
-export async function getMoneroC(coin: Coin, version: MoneroCVersion) {
- const dylibName = `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`;
- const endpointDylibName = `${coin}_libwallet2_api_c.so`;
+
+export async function prepareMoneroC(coin: Coin, version: MoneroCVersion) {
+ const dylibName = dylibNames(coin, version)[target];
+ const moneroTsDylibName = moneroTsDylibNames(coin)[target];
+
+ if (!dylibName || !moneroTsDylibName) {
+ throw new Error(`Missing dylib name value for target: ${target}`);
+ }
+
const releaseDylibName = dylibName.slice(`${coin}_`.length);
if (version === "next") {
- await $.raw`xz -kd release/${coin}/${releaseDylibName}.xz`;
- await $`mkdir -p tests/libs/next`;
- await $`mv release/${coin}/${releaseDylibName} tests/libs/next/${endpointDylibName}`;
+ const outFileDir = `./tests/dependencies/libs/${version}/${moneroTsDylibName}`;
+
+ if (await exists(outFileDir)) {
+ return;
+ }
+
+ await extract(`./release/${coin}/${releaseDylibName}.xz`, outFileDir);
} else {
- const downloadUrl = `https://github.com/MrCyjaneK/monero_c/releases/download/${version}/${dylibName}.xz`;
-
- const file = await Deno.open(`./tests/${dylibName}.xz`, {
- create: true,
- write: true,
- });
- file.write(await (await fetch(downloadUrl)).bytes());
- file.close();
-
- await $.raw`xz -d ./tests/${dylibName}.xz`;
- await $.raw`mkdir -p ./tests/libs/${version}`;
- await $
- .raw`mv ./tests/${dylibName} ./tests/libs/${version}/${endpointDylibName}`;
+ const outFileDir = `./tests/dependencies/libs/${version}/${moneroTsDylibName}`;
+
+ if (await exists(outFileDir)) {
+ return;
+ }
+
+ const downloadInfo = dylibInfos[coin].find((info) => info.outDir?.endsWith(version));
+ if (downloadInfo) {
+ await downloadDependencies(downloadInfo);
+ }
+
+ await extract(`./tests/dependencies/libs/${version}/${dylibName}.xz`, outFileDir);
}
}