robosats/frontend/src/models/Federation.model.ts

260 lines
7.9 KiB
TypeScript
Raw Normal View History

2024-10-11 13:57:42 +00:00
import { SimplePool, VerifiedEvent, Event } from 'nostr-tools';
import {
Coordinator,
2023-10-27 11:00:53 +00:00
type Exchange,
type Origin,
type PublicOrder,
type Settings,
defaultExchange,
} from '.';
import defaultFederation from '../../static/federation.json';
2024-07-12 10:57:09 +00:00
import { systemClient } from '../services/System';
import { getHost } from '../utils';
import { coordinatorDefaultValues } from './Coordinator.model';
import { updateExchangeInfo } from './Exchange.model';
2024-09-12 08:10:27 +00:00
import eventToPublicOrder from '../utils/nostr';
2024-10-11 13:57:42 +00:00
import { SubCloser } from 'nostr-tools/lib/types/pool';
2024-08-15 14:26:04 +00:00
type FederationHooks = 'onFederationUpdate';
export class Federation {
2024-03-28 22:21:15 +00:00
constructor(origin: Origin, settings: Settings, hostUrl: string) {
this.coordinators = Object.entries(defaultFederation).reduce(
(acc: Record<string, Coordinator>, [key, value]: [string, any]) => {
if (getHost() !== '127.0.0.1:8000' && key === 'local') {
// Do not add `Local Dev` unless it is running on localhost
return acc;
} else {
2024-05-28 06:17:25 +00:00
acc[key] = new Coordinator(value, origin, settings, hostUrl);
2024-05-25 17:33:07 +00:00
return acc;
}
},
{},
);
this.exchange = {
...defaultExchange,
totalCoordinators: Object.keys(this.coordinators).length,
};
2024-09-12 08:10:27 +00:00
this.book = {};
this.hooks = {
onFederationUpdate: [],
};
2024-03-28 22:21:15 +00:00
Object.keys(defaultFederation).forEach((key) => {
if (key !== 'local' || getHost() === '127.0.0.1:8000') {
// Do not add `Local Dev` unless it is running on localhost
this.addCoordinator(origin, settings, hostUrl, defaultFederation[key]);
}
});
2024-03-28 22:21:15 +00:00
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
this.loading = true;
2024-03-28 22:21:15 +00:00
const host = getHost();
const url = `${window.location.protocol}//${host}`;
2024-03-28 22:21:15 +00:00
const tesnetHost = Object.values(this.coordinators).find((coor) => {
return Object.values(coor.testnet).includes(url);
});
if (tesnetHost) settings.network = 'testnet';
2024-10-11 13:57:42 +00:00
this.connection = null;
const relays = [
'ws://4t4jxmivv6uqej6xzx2jx3fxh75gtt65v3szjoqmc4ugdlhipzdat6yd.onion/nostr',
// 'ws://ngdk7ocdzmz5kzsysa3om6du7ycj2evxp2f2olfkyq37htx3gllwp2yd.onion/nostr'
];
this.relayPool.trustedRelayURLs = new Set<string>(relays);
}
public coordinators: Record<string, Coordinator>;
public exchange: Exchange;
2024-09-12 08:10:27 +00:00
public book: Record<string, PublicOrder>;
public loading: boolean;
2024-10-11 13:57:42 +00:00
public connection: 'api' | 'nostr' | null;
2023-10-27 11:00:53 +00:00
public hooks: Record<FederationHooks, Array<() => void>>;
2024-09-12 08:10:27 +00:00
public relayPool: SimplePool = new SimplePool();
2024-10-11 13:57:42 +00:00
public relaySubscriptions: SubCloser[] = [];
setConnection = (connection: 'api' | 'nostr'): void => {
this.connection = connection;
if (this.connection === 'nostr') {
this.connectNostr();
} else {
this.relayPool.close(Array.from(this.relayPool.trustedRelayURLs));
this.updateBook();
}
};
2024-09-12 08:10:27 +00:00
connectNostr = (): void => {
this.loading = true;
this.book = {};
2024-10-11 13:57:42 +00:00
this.exchange.loadingCache = this.relayPool.trustedRelayURLs.size;
2024-09-12 08:10:27 +00:00
const authors = Object.values(defaultFederation)
.map((f) => f.nostrHexPubkey)
.filter((item) => item !== undefined);
2024-10-11 13:57:42 +00:00
const sub = this.relayPool.subscribeMany(
Array.from(this.relayPool.trustedRelayURLs),
2024-09-12 08:10:27 +00:00
[
{
authors,
kinds: [38383],
'#n': ['mainnet'],
},
],
{
onevent: (event) => {
const { dTag, publicOrder } = eventToPublicOrder(event);
if (publicOrder) {
this.book[dTag] = publicOrder;
} else {
delete this.book[dTag];
}
},
oneose: () => {
2024-10-11 13:57:42 +00:00
this.exchange.loadingCache = this.exchange.loadingCache - 1;
this.loading = this.exchange.loadingCache > 0 && this.exchange.loadingCoordinators > 0;
2024-09-12 08:10:27 +00:00
this.updateExchange();
this.triggerHook('onFederationUpdate');
},
onclose: () => {
2024-10-11 13:57:42 +00:00
this.exchange.loadingCache = this.exchange.loadingCache - 1;
this.loading = this.exchange.loadingCache > 0 && this.exchange.loadingCoordinators > 0;
2024-09-12 08:10:27 +00:00
this.updateExchange();
this.triggerHook('onFederationUpdate');
},
},
);
2024-10-11 13:57:42 +00:00
this.relaySubscriptions.push(sub);
2024-09-12 08:10:27 +00:00
};
addCoordinator = (
origin: Origin,
settings: Settings,
hostUrl: string,
attributes: Record<any, any>,
2024-08-10 15:54:13 +00:00
): void => {
const value = {
...coordinatorDefaultValues,
...attributes,
};
this.coordinators[value.shortAlias] = new Coordinator(value, origin, settings, hostUrl);
this.exchange.totalCoordinators = Object.keys(this.coordinators).length;
this.updateEnabledCoordinators();
this.triggerHook('onFederationUpdate');
};
// Hooks
registerHook = (hookName: FederationHooks, fn: () => void): void => {
this.hooks[hookName].push(fn);
};
triggerHook = (hookName: FederationHooks): void => {
2023-10-27 11:00:53 +00:00
this.hooks[hookName]?.forEach((fn) => {
fn();
});
};
onCoordinatorSaved = (): void => {
2024-10-11 13:57:42 +00:00
if (this.connection === 'api') {
this.book = Object.values(this.coordinators).reduce<Record<string, PublicOrder>>(
(book, coordinator) => {
return { ...book, ...coordinator.book };
},
{},
);
}
this.exchange.loadingCoordinators =
this.exchange.loadingCoordinators < 1 ? 0 : this.exchange.loadingCoordinators - 1;
2024-10-11 13:57:42 +00:00
this.loading = this.exchange.loadingCache > 0 && this.exchange.loadingCoordinators > 0;
this.updateExchange();
this.triggerHook('onFederationUpdate');
};
2024-03-28 22:21:15 +00:00
updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise<void> => {
2024-07-12 10:57:09 +00:00
const federationUrls = {};
for (const coor of Object.values(this.coordinators)) {
2024-03-28 22:21:15 +00:00
coor.updateUrl(origin, settings, hostUrl);
2024-07-12 10:57:09 +00:00
federationUrls[coor.shortAlias] = coor.url;
}
2024-07-12 10:57:09 +00:00
systemClient.setCookie('federation', JSON.stringify(federationUrls));
};
2024-10-11 13:57:42 +00:00
updateMeta = async (): Promise<void> => {
this.loading = true;
this.exchange.info = {
num_public_buy_orders: 0,
num_public_sell_orders: 0,
book_liquidity: 0,
active_robots_today: 0,
last_day_nonkyc_btc_premium: 0,
last_day_volume: 0,
lifetime_volume: 0,
version: { major: 0, minor: 0, patch: 0 },
};
2024-03-28 22:21:15 +00:00
this.exchange.onlineCoordinators = 0;
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
2024-03-28 22:21:15 +00:00
this.updateEnabledCoordinators();
2024-09-12 08:10:27 +00:00
2023-11-02 14:15:18 +00:00
for (const coor of Object.values(this.coordinators)) {
2024-10-11 13:57:42 +00:00
void coor.updateMeta(() => {
2024-03-28 22:21:15 +00:00
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
this.onCoordinatorSaved();
2023-10-27 11:00:53 +00:00
});
2023-11-02 14:15:18 +00:00
}
};
2023-12-22 12:58:59 +00:00
updateBook = async (): Promise<void> => {
2024-10-11 13:57:42 +00:00
if (this.connection !== 'api') return;
this.loading = true;
this.book = {};
this.triggerHook('onFederationUpdate');
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
for (const coor of Object.values(this.coordinators)) {
void coor.updateBook(() => {
this.onCoordinatorSaved();
this.triggerHook('onFederationUpdate');
});
}
2023-12-22 12:58:59 +00:00
};
2023-11-02 14:15:18 +00:00
updateExchange = (): void => {
this.exchange.info = updateExchangeInfo(this);
this.triggerHook('onFederationUpdate');
};
2023-10-27 11:00:53 +00:00
// Coordinators
getCoordinator = (shortAlias: string): Coordinator => {
return this.coordinators[shortAlias];
};
2023-11-02 14:15:18 +00:00
disableCoordinator = (shortAlias: string): void => {
this.coordinators[shortAlias].disable();
this.updateEnabledCoordinators();
2024-08-15 14:26:04 +00:00
this.triggerHook('onFederationUpdate');
};
2023-11-02 14:15:18 +00:00
enableCoordinator = (shortAlias: string): void => {
2023-11-01 12:18:00 +00:00
this.coordinators[shortAlias].enable(() => {
this.updateEnabledCoordinators();
2024-08-15 14:26:04 +00:00
this.triggerHook('onFederationUpdate');
2023-11-01 12:18:00 +00:00
});
};
updateEnabledCoordinators = (): void => {
this.exchange.enabledCoordinators = Object.values(this.coordinators).filter(
(c) => c.enabled,
).length;
this.triggerHook('onFederationUpdate');
};
}
export default Federation;