From 7ffb5c44b5f8df61f86fab056f91012e7f9647d3 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Mon, 4 May 2026 09:28:23 +0000 Subject: [PATCH] Fix history. Fix invitation accept --- src/services/history.ts | 188 +++++++------------------------------ src/services/invitation.ts | 5 +- 2 files changed, 40 insertions(+), 153 deletions(-) diff --git a/src/services/history.ts b/src/services/history.ts index e090b93..544500e 100644 --- a/src/services/history.ts +++ b/src/services/history.ts @@ -1,5 +1,9 @@ import { binToHex } from "@bitauth/libauth"; -import { compileCashAssemblyString, type Engine } from "@xo-cash/engine"; +import { + compileCashAssemblyString, + type Engine, + listInvitationCommitsByEntity, +} from "@xo-cash/engine"; import type { UnspentOutputData } from "@xo-cash/state"; import type { XOInvitation, @@ -59,6 +63,7 @@ interface InvitationContext { invitation: Invitation; template: XOTemplate | null; variables: Record; + walletCommits: XOInvitationCommit[]; walletEntityIdentifier?: string; } @@ -73,12 +78,8 @@ export class HistoryService { private invitations: Invitation[], ) {} - async extractEntities(invitation: XOInvitation): Promise { - const entities = new Set(); - for (const commit of invitation.commits) { - entities.add(commit.entityIdentifier); - } - return Array.from(entities); + extractEntities(invitation: XOInvitation): Record { + return listInvitationCommitsByEntity(invitation); } // Entities are currently static per invitation. So, we can try to match the roles to entities by: @@ -127,8 +128,6 @@ export class HistoryService { async getHistory(): Promise { const allUtxos = await this.engine.listUnspentOutputsData(); - const ownOutpoints = new Set(); - const ownLockingBytecodes = new Set(); const invitationByOrigin = new Map(); const outpointValueSatoshis = new Map(); @@ -137,8 +136,6 @@ export class HistoryService { utxo.outpointTransactionHash, utxo.outpointIndex, ); - ownOutpoints.add(outpointKey); - ownLockingBytecodes.add(utxo.scriptHash); outpointValueSatoshis.set(outpointKey, BigInt(utxo.valueSatoshis)); } @@ -148,15 +145,15 @@ export class HistoryService { const template = (await this.engine.getTemplate(invitation.data.templateIdentifier)) ?? null; - const walletEntityIdentifier = this.resolveWalletEntityIdentifier( - invitation, - ownOutpoints, - ownLockingBytecodes, + const walletCommits = await this.getWalletCommitsForInvitation( + invitation.data, ); + const walletEntityIdentifier = walletCommits[0]?.entityIdentifier; contexts.set(invitation.data.invitationIdentifier, { invitation, template, variables, + walletCommits, walletEntityIdentifier, }); this.indexInvitationOutputsByUtxoOrigin(invitationByOrigin, invitation); @@ -186,7 +183,6 @@ export class HistoryService { const invitationInputs = this.buildWalletInputItemsForInvitation( context, roles[0], - invitationOutputs.length > 0, outpointValueSatoshis, ); const invitationDescription = this.deriveInvitationDescription( @@ -287,51 +283,25 @@ export class HistoryService { return outputs; } + private async getWalletCommitsForInvitation( + invitation: XOInvitation, + ): Promise { + try { + return await this.engine.getOwnCommits(invitation); + } catch { + return []; + } + } + private buildWalletInputItemsForInvitation( context: InvitationContext, walletRole?: string, - hasWalletOutputs: boolean = false, outpointValueSatoshis: Map = new Map(), ): HistoryUtxoItem[] { const invitation = context.invitation.data; - const commits = invitation.commits ?? []; - const commitsByEntity = context.walletEntityIdentifier - ? commits.filter( - (commit) => - commit.entityIdentifier === context.walletEntityIdentifier, - ) - : []; - const commitsByRole = walletRole - ? commits.filter( - (commit) => - this.deriveCommitRoleIdentifier( - commit, - invitation, - context.template, - ) === walletRole, - ) - : []; - - let relevantCommits = commitsByEntity.filter( + const relevantCommits = context.walletCommits.filter( (commit) => (commit.data.inputs?.length ?? 0) > 0, ); - if (relevantCommits.length === 0) { - relevantCommits = commitsByRole.filter( - (commit) => (commit.data.inputs?.length ?? 0) > 0, - ); - } - if (relevantCommits.length === 0 && walletRole === "sender") { - relevantCommits = commits.filter( - (commit) => (commit.data.inputs?.length ?? 0) > 0, - ); - } - // Sender fallback only when no wallet outputs were matched. - if (relevantCommits.length === 0 && !hasWalletOutputs) { - relevantCommits = commits.filter( - (commit) => (commit.data.inputs?.length ?? 0) > 0, - ); - } - const txDescription = this.deriveTransactionActivityDescription( invitation, context.template, @@ -355,7 +325,10 @@ export class HistoryService { context.variables, ); const templateName = context.template?.name ?? "UnknownTemplate"; - const role = walletRole ?? "sender"; + const role = + this.deriveCommitRoleIdentifier(commit, invitation, context.template) ?? + walletRole ?? + "sender"; const inputValue = this.resolveInputSatoshis( txHash, inputIndex, @@ -422,13 +395,6 @@ export class HistoryService { }; } - /** - * TODO: This is completely incorrect. It should be deriving the roles of the user based on the entity IDs in the commits, then mapping each commit to Inputs/Outputs so we know what belongs to the user. - * There are a few changes that will need to be made to make this work: - * 1. Provide a way to derive all entity IDs of the user for the invitation (If we are going with XPub) - * 2. Provide a way to get only the User's commits (and their inputs/outputs) - * 3. (Maybe) Include role on inputs and outputs - This one might be fine with just using the commit entity id - */ private deriveWalletRolesForInvitation( context: InvitationContext, outputs: HistoryUtxoItem[], @@ -444,33 +410,20 @@ export class HistoryService { roles.add("receiver"); } - const hasInputCommit = ( - context.walletEntityIdentifier - ? context.invitation.data.commits.filter( - (c) => c.entityIdentifier === context.walletEntityIdentifier, - ) - : context.invitation.data.commits - ).some((c) => (c.data.inputs?.length ?? 0) > 0); - - if (hasInputCommit) roles.add("sender"); - if ( - !hasInputCommit && - outputs.length === 0 && - context.invitation.data.commits.some( - (c) => (c.data.inputs?.length ?? 0) > 0, - ) - ) { - roles.add("sender"); - } - if (roles.size === 0) { - const inferred = this.extractInvitationRoleIdentifier( + for (const commit of context.walletCommits) { + const role = this.deriveCommitRoleIdentifier( + commit, context.invitation.data, context.template, - context.walletEntityIdentifier, ); - if (inferred) roles.add(inferred); + if (role) roles.add(role); } + const hasInputCommit = context.walletCommits.some( + (c) => (c.data.inputs?.length ?? 0) > 0, + ); + if (hasInputCommit) roles.add("sender"); + return roles.size > 0 ? Array.from(roles) : ["unknown"]; } @@ -538,54 +491,6 @@ export class HistoryService { return invitationByUtxoOrigin.get(originKey)?.roleIdentifier; } - private resolveWalletEntityIdentifier( - invitation: Invitation, - ownUtxoOutpointKeys: Set, - ownLockingBytecodes: Set, - ): string | undefined { - const scores = new Map(); - const addScore = (entityIdentifier: string, delta: number): void => { - scores.set(entityIdentifier, (scores.get(entityIdentifier) ?? 0) + delta); - }; - - for (const commit of invitation.data.commits) { - for (const input of commit.data.inputs ?? []) { - const txHash = input.outpointTransactionHash - ? input.outpointTransactionHash instanceof Uint8Array - ? binToHex(input.outpointTransactionHash) - : String(input.outpointTransactionHash) - : undefined; - if (!txHash || input.outpointIndex === undefined) continue; - if ( - ownUtxoOutpointKeys.has( - this.getOutpointKey(txHash, input.outpointIndex), - ) - ) { - addScore(commit.entityIdentifier, 3); - } - } - for (const output of commit.data.outputs ?? []) { - const lockingBytecodeHex = output.lockingBytecode - ? this.toLockingBytecodeHex(output.lockingBytecode) - : undefined; - if (!lockingBytecodeHex) continue; - if (ownLockingBytecodes.has(lockingBytecodeHex)) { - addScore(commit.entityIdentifier, 2); - } - } - } - - let bestEntity: string | undefined; - let bestScore = 0; - for (const [entity, score] of scores.entries()) { - if (score > bestScore) { - bestScore = score; - bestEntity = entity; - } - } - return bestEntity; - } - private deriveUtxoDescription( utxo: UnspentOutputData, template: XOTemplate | null, @@ -715,27 +620,6 @@ export class HistoryService { return undefined; } - private extractInvitationRoleIdentifier( - invitation: XOInvitation, - template: XOTemplate | null, - walletEntityIdentifier?: string, - ): string | undefined { - if (walletEntityIdentifier) { - const commits = invitation.commits.filter( - (commit) => commit.entityIdentifier === walletEntityIdentifier, - ); - for (const commit of commits) { - const role = this.deriveCommitRoleIdentifier( - commit, - invitation, - template, - ); - if (role) return role; - } - } - return undefined; - } - private inferRoleFromOutputIdentifier( outputIdentifier: string, ): string | undefined { diff --git a/src/services/invitation.ts b/src/services/invitation.ts index 400b9c4..ff3ace6 100644 --- a/src/services/invitation.ts +++ b/src/services/invitation.ts @@ -85,8 +85,11 @@ export class Invitation extends EventEmitter { throw new Error(`Template not found: ${invitation.templateIdentifier}`); } + // engine invitation (I have no idea if this is required) + const engineInvitation = await dependencies.engine.acceptInvitation(invitation); + // Create the invitation - const invitationInstance = new Invitation(invitation, dependencies); + const invitationInstance = new Invitation(engineInvitation, dependencies); // Start the invitation and its tracking await invitationInstance.start();