rebrand(lethean): update branding, ports, and config for Lethean blockchain
Some checks failed
Deploy LetheanCompanion Test Suit to Github Pages / Build and Deploy (push) Failing after 4s

- Coin: Zano → Lethean, ticker: ZAN/ZANO → LTHN
- Ports: 11211 → 36941 (mainnet RPC), 46941 (testnet RPC)
- Wallet: 11212 → 36944/46944
- Address prefix: iTHN
- URLs: zano.org → lethean.io
- Explorer links: explorer.lthn.io

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-01 22:24:16 +01:00
parent fcffea241d
commit e25e773fc9
No known key found for this signature in database
GPG key ID: AF404715446AEB41
28 changed files with 144 additions and 119 deletions

View file

@ -1,4 +1,4 @@
name: "Deploy ZanoCompanion Test Suit to Github Pages" name: "Deploy LetheanCompanion Test Suit to Github Pages"
on: on:
push: push:

25
Dockerfile Normal file
View file

@ -0,0 +1,25 @@
# Build + serve: compiles the React/Vite test harness and serves on port 5173
FROM node:22-alpine AS builder
# pnpm is the declared package manager
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
# Serve the static dist with a minimal HTTP server
FROM node:22-alpine
RUN npm install -g serve
WORKDIR /app
COPY --from=builder /app/dist ./dist
EXPOSE 5173
CMD ["serve", "-s", "dist", "-l", "5173"]

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.png" sizes="96x96" /> <link rel="icon" type="image/png" href="/favicon.png" sizes="96x96" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>zano-companion-testsuit</title> <title>lethean-companion-testsuit</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View file

@ -1,5 +1,5 @@
{ {
"name": "zano-companion-testsuit", "name": "lethean-companion-testsuit",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",

View file

@ -1,11 +1,11 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { ASSETS_WHITELIST_ADD_RESPONSE } from "./companion"; import type { ASSETS_WHITELIST_ADD_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
import { particlesToValue } from "./utils"; import { particlesToValue } from "./utils";
export const AddWhitelist = () => { export const AddWhitelist = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<ASSETS_WHITELIST_ADD_RESPONSE | string | null>(null); const [response, setResponse] = useState<ASSETS_WHITELIST_ADD_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const asset_id = prompt("What asset do you want to add?"); const asset_id = prompt("What asset do you want to add?");

View file

@ -1,10 +1,10 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { GET_ALIAS_DETAILS_RESPONSE } from "./companion"; import type { GET_ALIAS_DETAILS_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const AliasDetails = () => { export const AliasDetails = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [details, setDetails] = useState<GET_ALIAS_DETAILS_RESPONSE | string | null>(null); const [details, setDetails] = useState<GET_ALIAS_DETAILS_RESPONSE | string | null>(null);
const fetchDetails = useCallback(async () => { const fetchDetails = useCallback(async () => {
let alias = prompt("What alias are you searching for?"); let alias = prompt("What alias are you searching for?");

View file

@ -1,7 +1,7 @@
import "./app.css"; import "./app.css";
import { AliasDetails } from "./AliasDetails"; import { AliasDetails } from "./AliasDetails";
import { ZanoCompanionProvider } from "./companion"; import { LetheanCompanionProvider } from "./companion";
import { Connector } from "./Connector"; import { Connector } from "./Connector";
import { CreateAlias } from "./CreateAlias"; import { CreateAlias } from "./CreateAlias";
import { Credentials } from "./Credentials"; import { Credentials } from "./Credentials";
@ -16,17 +16,17 @@ import { Transfer } from "./Transfer";
import { WalletBalance } from "./WalletBalance"; import { WalletBalance } from "./WalletBalance";
import { WalletData } from "./WalletData"; import { WalletData } from "./WalletData";
import { Whitelist } from "./Whitelist"; import { Whitelist } from "./Whitelist";
import ZanoLogo from "./zano.svg"; import LetheanLogo from "./lethean.svg";
export const App = () => { export const App = () => {
return ( return (
<div id="screen"> <div id="screen">
<div id="header"> <div id="header">
<img src={ZanoLogo} className="logo" alt="Zano logo" /> <img src={LetheanLogo} className="logo" alt="Lethean logo" />
<span>ZanoCompanion Test Suits</span> <span>LetheanCompanion Test Suits</span>
</div> </div>
<div id="content"> <div id="content">
<ZanoCompanionProvider disableServerRequest verbose> <LetheanCompanionProvider disableServerRequest verbose>
<div>Links</div> <div>Links</div>
<DeepLinkTransfer /> <DeepLinkTransfer />
<DeepLinkTransferLegacy /> <DeepLinkTransferLegacy />
@ -45,7 +45,7 @@ export const App = () => {
<RequestSign /> <RequestSign />
<AliasDetails /> <AliasDetails />
<CreateAlias /> <CreateAlias />
</ZanoCompanionProvider> </LetheanCompanionProvider>
</div> </div>
</div> </div>
); );

View file

@ -1,7 +1,7 @@
import { useZanoCompanionConnect } from "./companion"; import { useLetheanCompanionConnect } from "./companion";
export const Connector = () => { export const Connector = () => {
const [status, connect, disconnect] = useZanoCompanionConnect(); const [status, connect, disconnect] = useLetheanCompanionConnect();
return ( return (
<button onClick={status === "disconnected" ? connect : status === "connected" ? disconnect : undefined} disabled={status === "pending"}> <button onClick={status === "disconnected" ? connect : status === "connected" ? disconnect : undefined} disabled={status === "pending"}>
connection state: {status} connection state: {status}

View file

@ -1,10 +1,10 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { CREATE_ALIAS_RESPONSE } from "./companion"; import type { CREATE_ALIAS_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const CreateAlias = () => { export const CreateAlias = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<CREATE_ALIAS_RESPONSE | string | null>(null); const [response, setResponse] = useState<CREATE_ALIAS_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
let alias = prompt("What alias do you want to create?"); let alias = prompt("What alias do you want to create?");

View file

@ -1,8 +1,8 @@
import { useZanoCompanionCredentials } from "./companion"; import { useLetheanCompanionCredentials } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const Credentials = () => { export const Credentials = () => {
const credentials = useZanoCompanionCredentials(); const credentials = useLetheanCompanionCredentials();
if (!credentials) return null; if (!credentials) return null;
return ( return (
<Group> <Group>

View file

@ -10,7 +10,7 @@ export const DeepLinkTransfer = () => {
const amount = BigNumber(prompt("How many?") ?? NaN); const amount = BigNumber(prompt("How many?") ?? NaN);
const comment = prompt("Comment:") ?? undefined; const comment = prompt("Comment:") ?? undefined;
setLink( setLink(
`zano://transfer/?address=${address}${assetId ? `&asset_id=${assetId}` : ""}${amount.isNaN() ? "" : `&amount=${amount.toString(10)}`}${comment ? `&comment=${comment}` : ""}`, `lthn://transfer/?address=${address}${assetId ? `&asset_id=${assetId}` : ""}${amount.isNaN() ? "" : `&amount=${amount.toString(10)}`}${comment ? `&comment=${comment}` : ""}`,
); );
}, []); }, []);
return ( return (

View file

@ -10,7 +10,7 @@ export const DeepLinkTransferLegacy = () => {
const amount = BigNumber(prompt("How many?") ?? NaN); const amount = BigNumber(prompt("How many?") ?? NaN);
const comment = prompt("Comment:") ?? undefined; const comment = prompt("Comment:") ?? undefined;
setLink( setLink(
`zano:action=send&address=${address}${assetId ? `&asset_id=${assetId}` : ""}${amount.isNaN() ? "" : `&amount=${amount.toString(10)}`}${comment ? `&comment=${comment}` : ""}`, `lthn://transfer/?action=send&address=${address}${assetId ? `&asset_id=${assetId}` : ""}${amount.isNaN() ? "" : `&amount=${amount.toString(10)}`}${comment ? `&comment=${comment}` : ""}`,
); );
}, []); }, []);
return ( return (

View file

@ -1,11 +1,11 @@
import { BigNumber } from "bignumber.js"; import { BigNumber } from "bignumber.js";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { IONIC_SWAP_RESPONSE } from "./companion"; import type { IONIC_SWAP_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const IonicSwap = () => { export const IonicSwap = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<IONIC_SWAP_RESPONSE | string | null>(null); const [response, setResponse] = useState<IONIC_SWAP_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const destinationAddress = prompt("With whom do you swaping?"); const destinationAddress = prompt("With whom do you swaping?");

View file

@ -1,10 +1,10 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { IONIC_SWAP_ACCEPT_RESPONSE } from "./companion"; import type { IONIC_SWAP_ACCEPT_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const IonicSwapAccept = () => { export const IonicSwapAccept = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<IONIC_SWAP_ACCEPT_RESPONSE | string | null>(null); const [response, setResponse] = useState<IONIC_SWAP_ACCEPT_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const hex_raw_proposal = prompt("What proposal you want to accept?"); const hex_raw_proposal = prompt("What proposal you want to accept?");

View file

@ -1,11 +1,11 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE } from "./companion"; import type { GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
import { particlesToValue } from "./utils"; import { particlesToValue } from "./utils";
export const IonicSwapInfo = () => { export const IonicSwapInfo = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE | string | null>(null); const [response, setResponse] = useState<GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const hex_raw_proposal = prompt("What proposal you want to inspect?"); const hex_raw_proposal = prompt("What proposal you want to inspect?");
@ -48,7 +48,7 @@ export const IonicSwapInfo = () => {
))} ))}
<span>Additional fields:</span> <span>Additional fields:</span>
<Group> <Group>
<Group.Item label="Fee:" value={`${particlesToValue(response.proposal.fee_paid_by_a, 12)} ZANO`} /> <Group.Item label="Fee:" value={`${particlesToValue(response.proposal.fee_paid_by_a, 12)} LTHN`} />
</Group> </Group>
</> </>
) : null} ) : null}

View file

@ -1,11 +1,11 @@
import { BigNumber } from "bignumber.js"; import { BigNumber } from "bignumber.js";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { TRANSFER_RESPONSE } from "./companion"; import type { TRANSFER_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const MultiTransfer = () => { export const MultiTransfer = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<TRANSFER_RESPONSE | string | null>(null); const [response, setResponse] = useState<TRANSFER_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const destinations: { address: string; amount: string; assetId?: string }[] = []; const destinations: { address: string; amount: string; assetId?: string }[] = [];

View file

@ -1,10 +1,10 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { REQUEST_MESSAGE_SIGN_RESPONSE } from "./companion"; import type { REQUEST_MESSAGE_SIGN_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const RequestSign = () => { export const RequestSign = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<REQUEST_MESSAGE_SIGN_RESPONSE | string | null>(null); const [response, setResponse] = useState<REQUEST_MESSAGE_SIGN_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const message = prompt("What message are you trying to sign?"); const message = prompt("What message are you trying to sign?");

View file

@ -1,11 +1,11 @@
import { BigNumber } from "bignumber.js"; import { BigNumber } from "bignumber.js";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import type { TRANSFER_RESPONSE } from "./companion"; import type { TRANSFER_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
export const Transfer = () => { export const Transfer = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const [response, setResponse] = useState<TRANSFER_RESPONSE | string | null>(null); const [response, setResponse] = useState<TRANSFER_RESPONSE | string | null>(null);
const call = useCallback(async () => { const call = useCallback(async () => {
const destination = prompt("Whom do you send it?"); const destination = prompt("Whom do you send it?");

View file

@ -1,11 +1,11 @@
import { useCallback, useRef, useState, useSyncExternalStore } from "react"; import { useCallback, useRef, useState, useSyncExternalStore } from "react";
import type { GET_WALLET_BALANCE_RESPONSE } from "./companion"; import type { GET_WALLET_BALANCE_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
import { particlesToValue } from "./utils"; import { particlesToValue } from "./utils";
const WalletBalanceWatcher = () => { const WalletBalanceWatcher = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const snapshot = useRef<GET_WALLET_BALANCE_RESPONSE | null>(null); const snapshot = useRef<GET_WALLET_BALANCE_RESPONSE | null>(null);
const response = useSyncExternalStore( const response = useSyncExternalStore(
useCallback( useCallback(
@ -25,8 +25,8 @@ const WalletBalanceWatcher = () => {
return ( return (
<> <>
<Group> <Group>
<Group.Item label="Balance:" value={`${particlesToValue(response?.balance ?? 0, 12)} ZANO`} /> <Group.Item label="Balance:" value={`${particlesToValue(response?.balance ?? 0, 12)} LTHN`} />
<Group.Item label="Unlocked Balance:" value={`${particlesToValue(response?.unlocked_balance ?? 0, 12)} ZANO`} /> <Group.Item label="Unlocked Balance:" value={`${particlesToValue(response?.unlocked_balance ?? 0, 12)} LTHN`} />
</Group> </Group>
<Group> <Group>
{response?.balances.map((balance) => ( {response?.balances.map((balance) => (

View file

@ -1,10 +1,10 @@
import { useCallback, useRef, useState, useSyncExternalStore } from "react"; import { useCallback, useRef, useState, useSyncExternalStore } from "react";
import type { GET_WALLET_DATA_RESPONSE } from "./companion"; import type { GET_WALLET_DATA_RESPONSE } from "./companion";
import { useZanoCompanion } from "./companion"; import { useLetheanCompanion } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
const WalletDataWatcher = () => { const WalletDataWatcher = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const snapshot = useRef<GET_WALLET_DATA_RESPONSE | null>(null); const snapshot = useRef<GET_WALLET_DATA_RESPONSE | null>(null);
const walletData = useSyncExternalStore( const walletData = useSyncExternalStore(
useCallback( useCallback(
@ -22,7 +22,7 @@ const WalletDataWatcher = () => {
return ( return (
<Group> <Group>
{walletData?.alias ? <Group.Item label="Alias:" value={`@${walletData.alias}`} /> : null} {walletData?.alias ? <Group.Item label="Alias:" value={`@${walletData.alias}`} /> : null}
<Group.Item label="Balance:" value={`${walletData?.balance} ZANO`} /> <Group.Item label="Balance:" value={`${walletData?.balance} LTHN`} />
</Group> </Group>
); );
}; };

View file

@ -1,9 +1,9 @@
import { useCallback, useRef, useState, useSyncExternalStore } from "react"; import { useCallback, useRef, useState, useSyncExternalStore } from "react";
import { useZanoCompanion, type GET_WHITELIST_RESPONSE } from "./companion"; import { useLetheanCompanion, type GET_WHITELIST_RESPONSE } from "./companion";
import { Group } from "./Group"; import { Group } from "./Group";
const WhitelistWatcher = () => { const WhitelistWatcher = () => {
const companion = useZanoCompanion(); const companion = useLetheanCompanion();
const snapshot = useRef<GET_WHITELIST_RESPONSE | null>(null); const snapshot = useRef<GET_WHITELIST_RESPONSE | null>(null);
const response = useSyncExternalStore( const response = useSyncExternalStore(
useCallback( useCallback(

View file

@ -1,20 +1,20 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { ZanoCredentials, type ZanoCredentialsParams } from "./credentials"; import { LetheanCredentials, type LetheanCredentialsParams } from "./credentials";
import type { ZanoCompanionMethodParams, ZanoCompanionMethodResult, ZanoCompanionMethods } from "./types"; import type { LetheanCompanionMethodParams, LetheanCompanionMethodResult, LetheanCompanionMethods } from "./types";
declare global { declare global {
interface ZanoCompanionApi { interface LetheanCompanionApi {
request<Method extends keyof ZanoCompanionMethods>( request<Method extends keyof LetheanCompanionMethods>(
method: Method, method: Method,
...params: keyof ZanoCompanionMethodParams<Method> extends undefined ...params: keyof LetheanCompanionMethodParams<Method> extends undefined
? [params?: ZanoCompanionMethodParams<Method>, timeoutMs?: number | null] ? [params?: LetheanCompanionMethodParams<Method>, timeoutMs?: number | null]
: [params: ZanoCompanionMethodParams<Method>, timeoutMs?: number | null] : [params: LetheanCompanionMethodParams<Method>, timeoutMs?: number | null]
): Promise<{ data: ZanoCompanionMethodResult<Method> } | undefined>; ): Promise<{ data: LetheanCompanionMethodResult<Method> } | undefined>;
} }
var zano: ZanoCompanionApi | undefined; var lethean: LetheanCompanionApi | undefined;
} }
export type ZanoCompanionServerData = { export type LetheanCompanionServerData = {
alias: string | undefined; alias: string | undefined;
address: string; address: string;
signature: string; signature: string;
@ -22,40 +22,40 @@ export type ZanoCompanionServerData = {
message: string; message: string;
isSavedData: boolean | undefined; isSavedData: boolean | undefined;
}; };
export type ZanoCompanionParams = ZanoCredentialsParams & { export type LetheanCompanionParams = LetheanCredentialsParams & {
aliasRequired?: boolean; aliasRequired?: boolean;
customNonce?: string; customNonce?: string;
customServerPath?: string; customServerPath?: string;
disableServerRequest?: boolean; disableServerRequest?: boolean;
onConnectStart?: () => void; onConnectStart?: () => void;
onConnectEnd?: (data: ZanoCompanionServerData & { token: string }) => void; onConnectEnd?: (data: LetheanCompanionServerData & { token: string }) => void;
onLocalConnectEnd?: (data: ZanoCompanionServerData) => void; onLocalConnectEnd?: (data: LetheanCompanionServerData) => void;
verbose?: boolean; verbose?: boolean;
}; };
type AuthServerResponse = { success: true; data: { token: string } } | { success: false; error: string }; type AuthServerResponse = { success: true; data: { token: string } } | { success: false; error: string };
type UnwrappedZanoCompanionMethodResult<Method extends keyof ZanoCompanionMethods> = Exclude<ZanoCompanionMethodResult<Method>, { error: unknown }>; type UnwrappedLetheanCompanionMethodResult<Method extends keyof LetheanCompanionMethods> = Exclude<LetheanCompanionMethodResult<Method>, { error: unknown }>;
export class ZanoCompanion { export class LetheanCompanion {
#params: ZanoCompanionParams; #params: LetheanCompanionParams;
readonly methods: { readonly methods: {
[Method in keyof ZanoCompanionMethods]: ( [Method in keyof LetheanCompanionMethods]: (
...params: keyof ZanoCompanionMethodParams<Method> extends undefined ...params: keyof LetheanCompanionMethodParams<Method> extends undefined
? [params?: ZanoCompanionMethodParams<Method>, timeoutMs?: number | null] ? [params?: LetheanCompanionMethodParams<Method>, timeoutMs?: number | null]
: [params: ZanoCompanionMethodParams<Method>, timeoutMs?: number | null] : [params: LetheanCompanionMethodParams<Method>, timeoutMs?: number | null]
) => Promise<UnwrappedZanoCompanionMethodResult<Method>>; ) => Promise<UnwrappedLetheanCompanionMethodResult<Method>>;
}; };
readonly credentials: ZanoCredentials; readonly credentials: LetheanCredentials;
constructor(params: ZanoCompanionParams) { constructor(params: LetheanCompanionParams) {
if (typeof window === "undefined") { if (typeof window === "undefined") {
throw new Error("ZanoWallet can only be used in the browser"); throw new Error("LetheanWallet can only be used in the browser");
} }
if (!window.zano) { if (!window.lethean) {
console.error("ZanoWallet requires the ZanoWallet extension to be installed"); console.error("LetheanWallet requires the LetheanWallet extension to be installed");
} }
this.#params = Object.freeze(params); this.#params = Object.freeze(params);
@ -64,21 +64,21 @@ export class ZanoCompanion {
const credentials = this.credentials.get(); const credentials = this.credentials.get();
if (credentials && result.address !== credentials?.address) this.credentials.clear(); if (credentials && result.address !== credentials?.address) this.credentials.clear();
}, },
} as { [Method in keyof ZanoCompanionMethods]?: (result: UnwrappedZanoCompanionMethodResult<Method>) => void }; } as { [Method in keyof LetheanCompanionMethods]?: (result: UnwrappedLetheanCompanionMethodResult<Method>) => void };
this.methods = new Proxy( this.methods = new Proxy(
{}, {},
{ {
get: (cache, prop) => { get: (cache, prop) => {
const method = prop as keyof ZanoCompanionMethods; const method = prop as keyof LetheanCompanionMethods;
// @ts-expect-error - untyped // @ts-expect-error - untyped
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
if (cache[method]) return cache[method]; if (cache[method]) return cache[method];
// @ts-expect-error - untyped // @ts-expect-error - untyped
cache[method] = async (params: Parameters<ZanoCompanionMethods[typeof method]>[0], timeoutMs?: number | null) => { cache[method] = async (params: Parameters<LetheanCompanionMethods[typeof method]>[0], timeoutMs?: number | null) => {
if (!window.zano) throw new Error("ZanoWallet requires the ZanoWallet extension to be installed"); if (!window.lethean) throw new Error("LetheanWallet requires the LetheanWallet extension to be installed");
if (this.#params.verbose) console.info(`> call ${method} with`, params); if (this.#params.verbose) console.info(`> call ${method} with`, params);
const response = await window.zano.request(method, params, timeoutMs).catch((reason) => { const response = await window.lethean.request(method, params, timeoutMs).catch((reason) => {
if (this.#params.verbose) console.info(`> ${method} throws`, reason); if (this.#params.verbose) console.info(`> ${method} throws`, reason);
throw reason; throw reason;
}); });
@ -98,7 +98,7 @@ export class ZanoCompanion {
}, },
) as never; ) as never;
this.credentials = new ZanoCredentials(params); this.credentials = new LetheanCredentials(params);
} }
async connect(signal?: AbortSignal) { async connect(signal?: AbortSignal) {
@ -132,7 +132,7 @@ export class ZanoCompanion {
publicKey = signResult.result.pkey; publicKey = signResult.result.pkey;
} }
const serverData: ZanoCompanionServerData = { const serverData: LetheanCompanionServerData = {
alias: walletData.alias, alias: walletData.alias,
address: walletData.address, address: walletData.address,
signature, signature,

View file

@ -1,31 +1,31 @@
export interface IZanoCredentials { export interface ILetheanCredentials {
nonce: string; nonce: string;
signature: string; signature: string;
publicKey: string; publicKey: string;
address: string; address: string;
} }
export type ZanoCredentialsParams = { export type LetheanCredentialsParams = {
useLocalStorage?: boolean; useLocalStorage?: boolean;
customLocalStorageKey?: string; customLocalStorageKey?: string;
}; };
export class ZanoCredentials { export class LetheanCredentials {
static readonly #DEFAULT_LOCAL_STORAGE_KEY = "wallet"; static readonly #DEFAULT_LOCAL_STORAGE_KEY = "wallet";
#localStorageKey: string | false; #localStorageKey: string | false;
constructor({ useLocalStorage = true, customLocalStorageKey }: ZanoCredentialsParams = {}) { constructor({ useLocalStorage = true, customLocalStorageKey }: LetheanCredentialsParams = {}) {
this.#localStorageKey = useLocalStorage ? (customLocalStorageKey ?? ZanoCredentials.#DEFAULT_LOCAL_STORAGE_KEY) : false; this.#localStorageKey = useLocalStorage ? (customLocalStorageKey ?? LetheanCredentials.#DEFAULT_LOCAL_STORAGE_KEY) : false;
} }
#stored: IZanoCredentials | null = null; #stored: ILetheanCredentials | null = null;
restore(address?: string) { restore(address?: string) {
let next = (() => { let next = (() => {
if (!this.#localStorageKey) return null; if (!this.#localStorageKey) return null;
const json = localStorage.getItem(this.#localStorageKey); const json = localStorage.getItem(this.#localStorageKey);
if (json === null) return null; if (json === null) return null;
try { try {
const parsed = JSON.parse(json) as IZanoCredentials; const parsed = JSON.parse(json) as ILetheanCredentials;
return parsed; return parsed;
} catch { } catch {
return null; return null;
@ -46,7 +46,7 @@ export class ZanoCredentials {
if (this.#localStorageKey) localStorage.removeItem(this.#localStorageKey); if (this.#localStorageKey) localStorage.removeItem(this.#localStorageKey);
void this.emit(null); void this.emit(null);
} }
set(credentials: IZanoCredentials | null) { set(credentials: ILetheanCredentials | null) {
this.#stored = credentials; this.#stored = credentials;
if (this.#localStorageKey) { if (this.#localStorageKey) {
if (credentials) localStorage.setItem(this.#localStorageKey, JSON.stringify(credentials)); if (credentials) localStorage.setItem(this.#localStorageKey, JSON.stringify(credentials));
@ -55,18 +55,18 @@ export class ZanoCredentials {
void this.emit(credentials); void this.emit(credentials);
} }
private listeners = new Set<(credentials: IZanoCredentials | null) => void>(); private listeners = new Set<(credentials: ILetheanCredentials | null) => void>();
private async emit(credentials: IZanoCredentials | null) { private async emit(credentials: ILetheanCredentials | null) {
await Promise.resolve(); await Promise.resolve();
this.listeners.forEach((listener) => listener(credentials)); this.listeners.forEach((listener) => listener(credentials));
} }
addListener(listener: (credentials: IZanoCredentials | null) => void) { addListener(listener: (credentials: ILetheanCredentials | null) => void) {
this.listeners.add(listener); this.listeners.add(listener);
return () => { return () => {
this.listeners.delete(listener); this.listeners.delete(listener);
}; };
} }
removeListener(listener: (credentials: IZanoCredentials | null) => void) { removeListener(listener: (credentials: ILetheanCredentials | null) => void) {
this.listeners.delete(listener); this.listeners.delete(listener);
} }
} }

View file

@ -1,8 +1,8 @@
import { createContext, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from "react"; import { createContext, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from "react";
import { assert } from "../utils"; import { assert } from "../utils";
import { ZanoCompanion, type ZanoCompanionParams } from "./companion"; import { LetheanCompanion, type LetheanCompanionParams } from "./companion";
export const useZanoCompanionInstance = ({ onConnectStart, onConnectEnd, onLocalConnectEnd, ...params }: ZanoCompanionParams = {}) => { export const useLetheanCompanionInstance = ({ onConnectStart, onConnectEnd, onLocalConnectEnd, ...params }: LetheanCompanionParams = {}) => {
const onConnectStartRef = useRef(onConnectStart); const onConnectStartRef = useRef(onConnectStart);
onConnectStartRef.current = onConnectStart; onConnectStartRef.current = onConnectStart;
const onConnectEndRef = useRef(onConnectEnd); const onConnectEndRef = useRef(onConnectEnd);
@ -10,9 +10,9 @@ export const useZanoCompanionInstance = ({ onConnectStart, onConnectEnd, onLocal
const onLocalConnectEndRef = useRef(onLocalConnectEnd); const onLocalConnectEndRef = useRef(onLocalConnectEnd);
onLocalConnectEndRef.current = onLocalConnectEnd; onLocalConnectEndRef.current = onLocalConnectEnd;
const instance = useRef<ZanoCompanion | null>(null); const instance = useRef<LetheanCompanion | null>(null);
if (!instance.current) { if (!instance.current) {
instance.current = new ZanoCompanion({ instance.current = new LetheanCompanion({
...params, ...params,
onConnectStart: (...params) => onConnectStartRef.current?.(...params), onConnectStart: (...params) => onConnectStartRef.current?.(...params),
onConnectEnd: (...params) => onConnectEndRef.current?.(...params), onConnectEnd: (...params) => onConnectEndRef.current?.(...params),
@ -23,20 +23,20 @@ export const useZanoCompanionInstance = ({ onConnectStart, onConnectEnd, onLocal
return instance.current; return instance.current;
}; };
const ZanoCompanionContext = createContext<ZanoCompanion | undefined>(undefined); const LetheanCompanionContext = createContext<LetheanCompanion | undefined>(undefined);
export const ZanoCompanionProvider = ({ children, ...params }: { children?: ReactNode } & ZanoCompanionParams) => { export const LetheanCompanionProvider = ({ children, ...params }: { children?: ReactNode } & LetheanCompanionParams) => {
const companion = useZanoCompanionInstance(params); const companion = useLetheanCompanionInstance(params);
return <ZanoCompanionContext.Provider value={companion}>{children}</ZanoCompanionContext.Provider>; return <LetheanCompanionContext.Provider value={companion}>{children}</LetheanCompanionContext.Provider>;
}; };
export const useZanoCompanion = (companion?: ZanoCompanion) => { export const useLetheanCompanion = (companion?: LetheanCompanion) => {
const context = useContext(ZanoCompanionContext); const context = useContext(LetheanCompanionContext);
if (!companion) companion = context; if (!companion) companion = context;
assert(companion, "component must be wrapped in <ZanoCompanionProvider />"); assert(companion, "component must be wrapped in <LetheanCompanionProvider />");
return companion; return companion;
}; };
export const useZanoCompanionCredentials = (companion?: ZanoCompanion) => { export const useLetheanCompanionCredentials = (companion?: LetheanCompanion) => {
companion = useZanoCompanion(companion); companion = useLetheanCompanion(companion);
const credentials = useSyncExternalStore( const credentials = useSyncExternalStore(
useCallback((listener) => companion.credentials.addListener(listener), [companion.credentials]), useCallback((listener) => companion.credentials.addListener(listener), [companion.credentials]),
() => companion.credentials.get(), () => companion.credentials.get(),
@ -44,8 +44,8 @@ export const useZanoCompanionCredentials = (companion?: ZanoCompanion) => {
return credentials; return credentials;
}; };
export const useZanoCompanionConnect = (companion?: ZanoCompanion) => { export const useLetheanCompanionConnect = (companion?: LetheanCompanion) => {
companion = useZanoCompanion(companion); companion = useLetheanCompanion(companion);
const [status, setStatus] = useState<"disconnected" | "pending" | "connected">("disconnected"); const [status, setStatus] = useState<"disconnected" | "pending" | "connected">("disconnected");
const statusRef = useRef(status); const statusRef = useRef(status);
statusRef.current = status; statusRef.current = status;
@ -81,9 +81,9 @@ export const useZanoCompanionConnect = (companion?: ZanoCompanion) => {
return [status, connect, disconnect] as const; return [status, connect, disconnect] as const;
}; };
export const useZanoCompanionConnectEffect = (companion?: ZanoCompanion) => { export const useLetheanCompanionConnectEffect = (companion?: LetheanCompanion) => {
const [state, connect, disconnect] = useZanoCompanionConnect(companion); const [state, connect, disconnect] = useLetheanCompanionConnect(companion);
const credentials = useZanoCompanionCredentials(companion); const credentials = useLetheanCompanionCredentials(companion);
useEffect(() => { useEffect(() => {
const abort = connect(); const abort = connect();
return () => { return () => {

View file

@ -1,4 +1,4 @@
export * from "./companion"; export * from "./companion";
export type { IZanoCredentials } from "./credentials"; export type { ILetheanCredentials } from "./credentials";
export * from "./hooks"; export * from "./hooks";
export * from "./types"; export * from "./types";

View file

@ -128,9 +128,9 @@ export type ASSETS_WHITELIST_ADD_RESPONSE = {
asset_descriptor: asset_descriptor_with_id; asset_descriptor: asset_descriptor_with_id;
}; };
export type ZanoCompanionWrappedMethod<Result> = { result: Result; error?: undefined } | { result?: undefined; error: unknown }; export type LetheanCompanionWrappedMethod<Result> = { result: Result; error?: undefined } | { result?: undefined; error: unknown };
export type ZanoCompanionMethods = { export type LetheanCompanionMethods = {
GET_WALLET_BALANCE(params?: Record<string, never>): ZanoCompanionWrappedMethod<GET_WALLET_BALANCE_RESPONSE>; GET_WALLET_BALANCE(params?: Record<string, never>): LetheanCompanionWrappedMethod<GET_WALLET_BALANCE_RESPONSE>;
GET_WALLET_DATA(params?: Record<string, never>): GET_WALLET_DATA_RESPONSE; GET_WALLET_DATA(params?: Record<string, never>): GET_WALLET_DATA_RESPONSE;
IONIC_SWAP(params: { IONIC_SWAP(params: {
destinationAddress: string; destinationAddress: string;
@ -138,20 +138,20 @@ export type ZanoCompanionMethods = {
destinationAssetAmount: number | string; destinationAssetAmount: number | string;
currentAssetID: string; currentAssetID: string;
currentAssetAmount: number | string; currentAssetAmount: number | string;
}): ZanoCompanionWrappedMethod<IONIC_SWAP_RESPONSE>; }): LetheanCompanionWrappedMethod<IONIC_SWAP_RESPONSE>;
IONIC_SWAP_ACCEPT(params: { hex_raw_proposal: string }): ZanoCompanionWrappedMethod<IONIC_SWAP_ACCEPT_RESPONSE>; IONIC_SWAP_ACCEPT(params: { hex_raw_proposal: string }): LetheanCompanionWrappedMethod<IONIC_SWAP_ACCEPT_RESPONSE>;
GET_IONIC_SWAP_PROPOSAL_INFO(params: { hex_raw_proposal: string }): GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE; GET_IONIC_SWAP_PROPOSAL_INFO(params: { hex_raw_proposal: string }): GET_IONIC_SWAP_PROPOSAL_INFO_RESPONSE;
TRANSFER( TRANSFER(
params: ExclusiveUnion< params: ExclusiveUnion<
{ assetId: string; destination: string; amount: string }, { assetId: string; destination: string; amount: string },
{ assetId?: string; destinations: { address: string; amount: string; assetId?: string }[] } { assetId?: string; destinations: { address: string; amount: string; assetId?: string }[] }
> & { comment?: string }, > & { comment?: string },
): ZanoCompanionWrappedMethod<TRANSFER_RESPONSE>; ): LetheanCompanionWrappedMethod<TRANSFER_RESPONSE>;
REQUEST_MESSAGE_SIGN(params: { message: string }): ZanoCompanionWrappedMethod<REQUEST_MESSAGE_SIGN_RESPONSE>; REQUEST_MESSAGE_SIGN(params: { message: string }): LetheanCompanionWrappedMethod<REQUEST_MESSAGE_SIGN_RESPONSE>;
GET_WHITELIST(params?: Record<string, never>): GET_WHITELIST_RESPONSE; GET_WHITELIST(params?: Record<string, never>): GET_WHITELIST_RESPONSE;
ASSETS_WHITELIST_ADD(params: { asset_id: string }): ZanoCompanionWrappedMethod<ASSETS_WHITELIST_ADD_RESPONSE>; ASSETS_WHITELIST_ADD(params: { asset_id: string }): LetheanCompanionWrappedMethod<ASSETS_WHITELIST_ADD_RESPONSE>;
GET_ALIAS_DETAILS(params: { alias: string }): GET_ALIAS_DETAILS_RESPONSE; GET_ALIAS_DETAILS(params: { alias: string }): GET_ALIAS_DETAILS_RESPONSE;
CREATE_ALIAS(params: { alias: string; comment?: string }): ZanoCompanionWrappedMethod<CREATE_ALIAS_RESPONSE>; CREATE_ALIAS(params: { alias: string; comment?: string }): LetheanCompanionWrappedMethod<CREATE_ALIAS_RESPONSE>;
}; };
export type ZanoCompanionMethodParams<Method extends keyof ZanoCompanionMethods> = Parameters<ZanoCompanionMethods[Method]>[0]; export type LetheanCompanionMethodParams<Method extends keyof LetheanCompanionMethods> = Parameters<LetheanCompanionMethods[Method]>[0];
export type ZanoCompanionMethodResult<Method extends keyof ZanoCompanionMethods> = ReturnType<ZanoCompanionMethods[Method]>; export type LetheanCompanionMethodResult<Method extends keyof LetheanCompanionMethods> = ReturnType<LetheanCompanionMethods[Method]>;

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -3,5 +3,5 @@ import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
base: "/zano-companion-testsuit/", base: "/lethean-companion-testsuit/",
}); });