Format with prettier. Use screen mode for invitation import - dialog mode is broken.

This commit is contained in:
2026-03-23 10:15:48 +00:00
parent 7fd89c5663
commit b475b23beb
47 changed files with 1718 additions and 1098 deletions

View File

@@ -1,28 +1,28 @@
import {
import {
Engine,
type XOEngineOptions,
// This is temporary. Will likely be moved to where we import templates in the cli. I think that makes more sense as this is a library thing
generateTemplateIdentifier,
} from '@xo-cash/engine';
import type { XOInvitation } from '@xo-cash/types';
} from "@xo-cash/engine";
import type { XOInvitation } from "@xo-cash/types";
import { Invitation } from './invitation.js';
import { Storage } from './storage.js';
import { SyncServer } from '../utils/sync-server.js';
import { HistoryService } from './history.js';
import { ElectrumService } from './electrum.js';
import { Invitation } from "./invitation.js";
import { Storage } from "./storage.js";
import { SyncServer } from "../utils/sync-server.js";
import { HistoryService } from "./history.js";
import { ElectrumService } from "./electrum.js";
import { EventEmitter } from '../utils/event-emitter.js';
import { EventEmitter } from "../utils/event-emitter.js";
// TODO: Remove this. Exists to hash the seed for database namespace.
import { createHash } from 'crypto';
import { p2pkhTemplate } from '@xo-cash/templates';
import { hexToBin } from '@bitauth/libauth';
import { createHash } from "crypto";
import { p2pkhTemplate } from "@xo-cash/templates";
import { hexToBin } from "@bitauth/libauth";
export type AppEventMap = {
'invitation-added': Invitation;
'invitation-removed': Invitation;
}
"invitation-added": Invitation;
"invitation-removed": Invitation;
};
export interface AppConfig {
syncServerUrl: string;
@@ -38,14 +38,14 @@ export class AppService extends EventEmitter<AppEventMap> {
public config: AppConfig;
public history: HistoryService;
public electrum: ElectrumService;
public invitations: Invitation[] = [];
static async create(seed: string, config: AppConfig): Promise<AppService> {
// Because of a bug that lets wallets read the unspents of other wallets, we are going to manually namespace the storage paths for the app.
// We are going to do this by computing a hash of the seed and prefixing the storage paths with it.
const seedHash = createHash('sha256').update(seed).digest('hex');
const seedHash = createHash("sha256").update(seed).digest("hex");
// We want to only prefix the file name
const prefixedStoragePath = `${seedHash.slice(0, 8)}-${config.engineConfig.databaseFilename}`;
@@ -66,13 +66,13 @@ export class AppService extends EventEmitter<AppEventMap> {
// TODO: Add discovery for funds in the first index? Or until we return 0 TXs?
await engine.setDefaultLockingParameters(
generateTemplateIdentifier(p2pkhTemplate),
'receiveOutput',
'receiver',
"receiveOutput",
"receiver",
);
// Create our own storage for the invitations
const storage = await Storage.create(config.invitationStoragePath);
const walletStorage = await storage.child(seedHash.slice(0, 8))
const walletStorage = await storage.child(seedHash.slice(0, 8));
// Create the app service
const electrum = new ElectrumService({
@@ -86,23 +86,35 @@ export class AppService extends EventEmitter<AppEventMap> {
const allUnspentOutputs = await engine.listUnspentOutputsData();
// Get a set of all the invitation identifiers
const allInvitationIdentifiers = new Set(allUnspentOutputs.map(output => output.invitationIdentifier));
const allInvitationIdentifiers = new Set(
allUnspentOutputs.map((output) => output.invitationIdentifier),
);
// Iterate over the invitation identifiers and unreserve the outputs
for (const invitationIdentifier of allInvitationIdentifiers) {
// Get the outputs for the invitation
const outputs = allUnspentOutputs.filter(output => output.invitationIdentifier === invitationIdentifier);
const outputs = allUnspentOutputs.filter(
(output) => output.invitationIdentifier === invitationIdentifier,
);
// Unreserve the outputs
await engine.unreserveResources(outputs.map(output => ({
outpointTransactionHash: hexToBin(output.outpointTransactionHash),
outpointIndex: output.outpointIndex,
})), invitationIdentifier);
await engine.unreserveResources(
outputs.map((output) => ({
outpointTransactionHash: hexToBin(output.outpointTransactionHash),
outpointIndex: output.outpointIndex,
})),
invitationIdentifier,
);
}
return new AppService(engine, walletStorage, config, electrum);
}
constructor(engine: Engine, storage: Storage, config: AppConfig, electrum: ElectrumService) {
constructor(
engine: Engine,
storage: Storage,
config: AppConfig,
electrum: ElectrumService,
) {
super();
this.engine = engine;
@@ -112,10 +124,17 @@ export class AppService extends EventEmitter<AppEventMap> {
this.history = new HistoryService(engine, this.invitations);
}
async createInvitation(invitation: XOInvitation | string): Promise<Invitation> {
async createInvitation(
invitation: XOInvitation | string,
): Promise<Invitation> {
// Make sure the engine has the template imported
const invitationStorage = this.storage.child('invitations')
const invitationSyncServer = new SyncServer(this.config.syncServerUrl, typeof invitation === 'string' ? invitation : invitation.invitationIdentifier);
const invitationStorage = this.storage.child("invitations");
const invitationSyncServer = new SyncServer(
this.config.syncServerUrl,
typeof invitation === "string"
? invitation
: invitation.invitationIdentifier,
);
const deps = {
engine: this.engine,
@@ -138,26 +157,31 @@ export class AppService extends EventEmitter<AppEventMap> {
this.invitations.push(invitation);
// Emit the invitation-added event
this.emit('invitation-added', invitation);
this.emit("invitation-added", invitation);
}
async removeInvitation(invitation: Invitation): Promise<void> {
// Remove the invitation from the invitations array
this.invitations = this.invitations.filter(i => i !== invitation);
this.invitations = this.invitations.filter((i) => i !== invitation);
// Emit the invitation-removed event
this.emit('invitation-removed', invitation);
this.emit("invitation-removed", invitation);
}
async start(): Promise<void> {
// Get the invitations db
const invitationsDb = this.storage.child('invitations');
const invitationsDb = this.storage.child("invitations");
// Load invitations from storage
const invitations = await invitationsDb.all() as { key: string; value: XOInvitation }[];
const invitations = (await invitationsDb.all()) as {
key: string;
value: XOInvitation;
}[];
await Promise.all(invitations.map(async ({ key }) => {
await this.createInvitation(key);
}));
await Promise.all(
invitations.map(async ({ key }) => {
await this.createInvitation(key);
}),
);
}
}
}