2024-08-15 14:26:04 +00:00
|
|
|
import { type LimitList, type PublicOrder, type Settings } from '.';
|
2024-04-30 14:01:54 +00:00
|
|
|
import { roboidentitiesClient } from '../services/Roboidentities/Web';
|
2023-10-27 10:01:59 +00:00
|
|
|
import { apiClient } from '../services/api';
|
|
|
|
import { compareUpdateLimit } from './Limit.model';
|
|
|
|
|
|
|
|
export interface Contact {
|
|
|
|
nostr?: string | undefined;
|
|
|
|
pgp?: string | undefined;
|
|
|
|
fingerprint?: string | undefined;
|
|
|
|
email?: string | undefined;
|
|
|
|
telegram?: string | undefined;
|
|
|
|
reddit?: string | undefined;
|
|
|
|
matrix?: string | undefined;
|
2023-11-03 12:13:59 +00:00
|
|
|
simplex?: string | undefined;
|
2023-10-27 10:01:59 +00:00
|
|
|
twitter?: string | undefined;
|
|
|
|
website?: string | undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Version {
|
|
|
|
major: number;
|
|
|
|
minor: number;
|
|
|
|
patch: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Badges {
|
|
|
|
isFounder?: boolean | undefined;
|
|
|
|
donatesToDevFund: number;
|
|
|
|
hasGoodOpSec?: boolean | undefined;
|
|
|
|
robotsLove?: boolean | undefined;
|
|
|
|
hasLargeLimits?: boolean | undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Info {
|
|
|
|
num_public_buy_orders: number;
|
|
|
|
num_public_sell_orders: number;
|
|
|
|
book_liquidity: number;
|
|
|
|
active_robots_today: number;
|
|
|
|
last_day_nonkyc_btc_premium: number;
|
|
|
|
last_day_volume: number;
|
|
|
|
lifetime_volume: number;
|
|
|
|
lnd_version?: string;
|
|
|
|
cln_version?: string;
|
|
|
|
robosats_running_commit_hash: string;
|
|
|
|
alternative_site: string;
|
|
|
|
alternative_name: string;
|
|
|
|
node_alias: string;
|
|
|
|
node_id: string;
|
|
|
|
version: Version;
|
|
|
|
maker_fee: number;
|
|
|
|
taker_fee: number;
|
|
|
|
bond_size: number;
|
2024-02-11 17:17:06 +00:00
|
|
|
min_order_size: number;
|
|
|
|
max_order_size: number;
|
|
|
|
swap_enabled: boolean;
|
|
|
|
max_swap: number;
|
2023-10-27 10:01:59 +00:00
|
|
|
current_swap_fee_rate: number;
|
|
|
|
network: 'mainnet' | 'testnet' | undefined;
|
|
|
|
openUpdateClient: boolean;
|
|
|
|
notice_severity: 'none' | 'warning' | 'error' | 'success' | 'info';
|
|
|
|
notice_message: string;
|
|
|
|
loading: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Origin = 'onion' | 'i2p' | 'clearnet';
|
|
|
|
|
2024-06-25 10:52:20 +00:00
|
|
|
export const coordinatorDefaultValues = {
|
|
|
|
longAlias: '',
|
|
|
|
shortAlias: '',
|
|
|
|
description: '',
|
|
|
|
motto: '',
|
|
|
|
color: '#000',
|
|
|
|
size_limit: 21 * 100000000,
|
|
|
|
established: new Date(),
|
|
|
|
policies: {},
|
|
|
|
contact: {
|
|
|
|
email: '',
|
|
|
|
telegram: '',
|
|
|
|
simplex: '',
|
|
|
|
matrix: '',
|
|
|
|
website: '',
|
|
|
|
nostr: '',
|
|
|
|
pgp: '',
|
|
|
|
fingerprint: '',
|
|
|
|
},
|
|
|
|
badges: {
|
|
|
|
isFounder: false,
|
|
|
|
donatesToDevFund: 0,
|
|
|
|
hasGoodOpSec: false,
|
|
|
|
robotsLove: false,
|
|
|
|
hasLargeLimits: false,
|
|
|
|
},
|
|
|
|
mainnet: undefined,
|
|
|
|
testnet: undefined,
|
|
|
|
mainnetNodesPubkeys: '',
|
|
|
|
testnetNodesPubkeys: '',
|
|
|
|
federated: true,
|
|
|
|
};
|
|
|
|
|
2023-10-27 10:01:59 +00:00
|
|
|
export interface Origins {
|
|
|
|
clearnet: Origin | undefined;
|
|
|
|
onion: Origin | undefined;
|
|
|
|
i2p: Origin | undefined;
|
|
|
|
}
|
|
|
|
|
2024-02-08 00:35:14 +00:00
|
|
|
function calculateSizeLimit(inputDate: Date): number {
|
|
|
|
const now = new Date();
|
|
|
|
const numDifficultyAdjustments = Math.ceil(
|
|
|
|
(now.getTime() - inputDate.getTime()) / (1000 * 60 * 60 * 24 * 14),
|
|
|
|
);
|
|
|
|
|
|
|
|
let value = 250000;
|
|
|
|
for (let i = 1; i < numDifficultyAdjustments; i++) {
|
|
|
|
value *= 1.3;
|
|
|
|
if (i >= 12) {
|
|
|
|
// after 12 difficulty adjustments (6 weeks) limit becomes 21 BTC (mature coordinator)
|
|
|
|
return 21 * 100000000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2023-10-27 10:01:59 +00:00
|
|
|
export class Coordinator {
|
2024-03-28 22:21:15 +00:00
|
|
|
constructor(value: any, origin: Origin, settings: Settings, hostUrl: string) {
|
2024-02-08 00:35:14 +00:00
|
|
|
const established = new Date(value.established);
|
2023-10-27 10:01:59 +00:00
|
|
|
this.longAlias = value.longAlias;
|
|
|
|
this.shortAlias = value.shortAlias;
|
|
|
|
this.description = value.description;
|
2024-10-18 10:36:26 +00:00
|
|
|
this.federated = value.federated ?? false;
|
2023-10-27 10:01:59 +00:00
|
|
|
this.motto = value.motto;
|
|
|
|
this.color = value.color;
|
2024-02-08 00:35:14 +00:00
|
|
|
this.size_limit = value.badges.isFounder ? 21 * 100000000 : calculateSizeLimit(established);
|
|
|
|
this.established = established;
|
2023-10-27 10:01:59 +00:00
|
|
|
this.policies = value.policies;
|
|
|
|
this.contact = value.contact;
|
|
|
|
this.badges = value.badges;
|
|
|
|
this.mainnet = value.mainnet;
|
|
|
|
this.testnet = value.testnet;
|
|
|
|
this.mainnetNodesPubkeys = value.mainnetNodesPubkeys;
|
|
|
|
this.testnetNodesPubkeys = value.testnetNodesPubkeys;
|
|
|
|
this.url = '';
|
|
|
|
this.basePath = '';
|
2024-03-28 22:21:15 +00:00
|
|
|
|
|
|
|
this.updateUrl(origin, settings, hostUrl);
|
2023-10-27 10:01:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// These properties are loaded from federation.json
|
|
|
|
public longAlias: string;
|
|
|
|
public shortAlias: string;
|
2024-06-25 10:52:20 +00:00
|
|
|
public federated: boolean;
|
2023-10-27 10:01:59 +00:00
|
|
|
public enabled?: boolean = true;
|
|
|
|
public description: string;
|
|
|
|
public motto: string;
|
|
|
|
public color: string;
|
2024-02-07 14:57:36 +00:00
|
|
|
public size_limit: number;
|
2024-02-08 00:35:14 +00:00
|
|
|
public established: Date;
|
2023-10-27 10:01:59 +00:00
|
|
|
public policies: Record<string, string> = {};
|
|
|
|
public contact: Contact | undefined;
|
|
|
|
public badges: Badges;
|
|
|
|
public mainnet: Origins;
|
|
|
|
public testnet: Origins;
|
|
|
|
public mainnetNodesPubkeys: string[] | undefined;
|
|
|
|
public testnetNodesPubkeys: string[] | undefined;
|
|
|
|
public url: string;
|
|
|
|
public basePath: string;
|
|
|
|
|
|
|
|
// These properties are fetched from coordinator API
|
2024-09-12 08:10:27 +00:00
|
|
|
public book: Record<string, PublicOrder> = {};
|
2023-10-27 10:01:59 +00:00
|
|
|
public loadingBook: boolean = false;
|
|
|
|
public info?: Info | undefined = undefined;
|
|
|
|
public loadingInfo: boolean = false;
|
|
|
|
public limits: LimitList = {};
|
|
|
|
public loadingLimits: boolean = false;
|
|
|
|
|
2024-03-28 22:21:15 +00:00
|
|
|
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
|
2023-10-27 10:01:59 +00:00
|
|
|
if (settings.selfhostedClient && this.shortAlias !== 'local') {
|
|
|
|
this.url = hostUrl;
|
|
|
|
this.basePath = `/${settings.network}/${this.shortAlias}`;
|
|
|
|
} else {
|
|
|
|
this.url = String(this[settings.network][origin]);
|
|
|
|
this.basePath = '';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-10-11 13:57:42 +00:00
|
|
|
generateAllMakerAvatars = async (): Promise<void> => {
|
|
|
|
for (const order of Object.values(this.book)) {
|
2024-08-10 15:54:13 +00:00
|
|
|
void roboidentitiesClient.generateRobohash(order.maker_hash_id, 'small');
|
2024-04-28 12:34:32 +00:00
|
|
|
}
|
2023-12-02 11:31:21 +00:00
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
loadBook = (onDataLoad: () => void = () => {}): void => {
|
2023-12-22 12:58:59 +00:00
|
|
|
if (!this.enabled) return;
|
2023-12-30 16:45:45 +00:00
|
|
|
if (this.url === '') return;
|
2023-10-27 10:01:59 +00:00
|
|
|
if (this.loadingBook) return;
|
|
|
|
|
2024-10-11 13:57:42 +00:00
|
|
|
this.loadingBook = true;
|
|
|
|
this.book = {};
|
|
|
|
|
|
|
|
apiClient
|
|
|
|
.get(this.url, `${this.basePath}/api/book/`)
|
|
|
|
.then((data) => {
|
|
|
|
if (!data?.not_found) {
|
|
|
|
this.book = (data as PublicOrder[]).reduce<Record<string, PublicOrder>>((book, order) => {
|
|
|
|
order.coordinatorShortAlias = this.shortAlias;
|
|
|
|
return { ...book, [this.shortAlias + order.id]: order };
|
|
|
|
}, {});
|
|
|
|
void this.generateAllMakerAvatars();
|
|
|
|
onDataLoad();
|
|
|
|
} else {
|
|
|
|
onDataLoad();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.log(e);
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.loadingBook = false;
|
|
|
|
});
|
2023-10-27 10:01:59 +00:00
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
loadLimits = (onDataLoad: () => void = () => {}): void => {
|
2023-12-22 12:58:59 +00:00
|
|
|
if (!this.enabled) return;
|
2023-12-30 16:45:45 +00:00
|
|
|
if (this.url === '') return;
|
2023-10-27 10:01:59 +00:00
|
|
|
if (this.loadingLimits) return;
|
|
|
|
|
|
|
|
this.loadingLimits = true;
|
|
|
|
|
|
|
|
apiClient
|
|
|
|
.get(this.url, `${this.basePath}/api/limits/`)
|
|
|
|
.then((data) => {
|
2023-11-20 14:45:58 +00:00
|
|
|
if (data !== null) {
|
2023-10-27 10:01:59 +00:00
|
|
|
const newLimits = data as LimitList;
|
|
|
|
|
|
|
|
for (const currency in this.limits) {
|
|
|
|
newLimits[currency] = compareUpdateLimit(this.limits[currency], newLimits[currency]);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.limits = newLimits;
|
|
|
|
onDataLoad();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.log(e);
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.loadingLimits = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
loadInfo = (onDataLoad: () => void = () => {}): void => {
|
2023-12-22 12:58:59 +00:00
|
|
|
if (!this.enabled) return;
|
2023-12-30 16:45:45 +00:00
|
|
|
if (this.url === '') return;
|
2023-10-27 10:01:59 +00:00
|
|
|
if (this.loadingInfo) return;
|
|
|
|
|
|
|
|
this.loadingInfo = true;
|
|
|
|
|
|
|
|
apiClient
|
|
|
|
.get(this.url, `${this.basePath}/api/info/`)
|
|
|
|
.then((data) => {
|
2023-11-20 14:45:58 +00:00
|
|
|
if (data !== null) {
|
|
|
|
this.info = data as Info;
|
|
|
|
onDataLoad();
|
|
|
|
}
|
2023-10-27 10:01:59 +00:00
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
console.log(e);
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
this.loadingInfo = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
enable = (onEnabled: () => void = () => {}): void => {
|
2023-10-27 10:01:59 +00:00
|
|
|
this.enabled = true;
|
2024-10-17 09:14:52 +00:00
|
|
|
void this.loadLimits(() => {
|
2023-11-01 12:18:00 +00:00
|
|
|
onEnabled();
|
|
|
|
});
|
2023-10-27 10:01:59 +00:00
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
disable = (): void => {
|
2023-10-27 10:01:59 +00:00
|
|
|
this.enabled = false;
|
|
|
|
this.info = undefined;
|
|
|
|
this.limits = {};
|
2024-09-12 08:10:27 +00:00
|
|
|
this.book = {};
|
2023-10-27 10:01:59 +00:00
|
|
|
};
|
|
|
|
|
2023-11-02 14:15:18 +00:00
|
|
|
getBaseUrl = (): string => {
|
2023-10-27 10:01:59 +00:00
|
|
|
return this.url + this.basePath;
|
|
|
|
};
|
|
|
|
|
|
|
|
getEndpoint = (
|
|
|
|
network: 'mainnet' | 'testnet',
|
|
|
|
origin: Origin,
|
|
|
|
selfHosted: boolean,
|
|
|
|
hostUrl: string,
|
|
|
|
): { url: string; basePath: string } => {
|
|
|
|
if (selfHosted && this.shortAlias !== 'local') {
|
|
|
|
return { url: hostUrl, basePath: `/${network}/${this.shortAlias}` };
|
|
|
|
} else {
|
|
|
|
return { url: String(this[network][origin]), basePath: '' };
|
|
|
|
}
|
|
|
|
};
|
2022-10-30 19:13:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default Coordinator;
|