From 3d6518e4657704c57beba9c572995b4b0eb75991 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Fri, 22 May 2026 10:18:30 +0200 Subject: [PATCH] Completely broken update to latest versions --- src/services/app.ts | 12 ++--- src/services/history.ts | 17 +++---- src/services/invitation.ts | 45 +++++++++++++------ .../screens/invitations/InvitationScreen.tsx | 14 +++--- src/utils/invitation-flow.ts | 2 +- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/services/app.ts b/src/services/app.ts index d404138..f8d8a67 100644 --- a/src/services/app.ts +++ b/src/services/app.ts @@ -105,7 +105,7 @@ export class AppService extends EventEmitter { templates.forEach(async (template) => { // engine.updateUnspentOutputsForTemplate(generateTemplateIdentifier(template)); - engine.subscribeToLockingBytecodesForTemplate(generateTemplateIdentifier(template)); + engine.subscribeToScriptHashForTemplate(generateTemplateIdentifier(template)); }); }; @@ -114,11 +114,11 @@ export class AppService extends EventEmitter { // Set default locking parameters for P2PKH // To my knowledge, this doesnt generate any lockscript, so discovery of funds will not work automatically. // TODO: Add discovery for funds in the first index? Or until we return 0 TXs? - await engine.setDefaultLockingParameters( - generateTemplateIdentifier(parseTemplate(p2pkhTemplate)), - "receiveOutput", - "receiver", - ); + // await engine.setDefaultLockingParameters( + // generateTemplateIdentifier(parseTemplate(p2pkhTemplate)), + // "receiveOutput", + // "receiver", + // ); // Create our own storage for the invitations const storage = await Storage.create(config.invitationStoragePath); diff --git a/src/services/history.ts b/src/services/history.ts index 959f798..4104c42 100644 --- a/src/services/history.ts +++ b/src/services/history.ts @@ -165,7 +165,7 @@ export class HistoryService { const templateIdentifiers = new Set(); for (const utxo of allUtxos) { - templateIdentifiers.add(utxo.templateIdentifier); + templateIdentifiers.add(utxo.scriptHash); } for (const invitation of this.invitations) { templateIdentifiers.add(invitation.data.templateIdentifier); @@ -186,7 +186,7 @@ export class HistoryService { metadataIndex: WalletMetadataIndex, ): Promise { const scriptHashData = metadataIndex.scriptHashDataByScriptHash.get(utxo.scriptHash); - const templateIdentifier = scriptHashData?.templateIdentifier ?? utxo.templateIdentifier; + const templateIdentifier = scriptHashData?.templateIdentifier!; const template = (await this.engine.getTemplate(templateIdentifier)) ?? null; return { @@ -299,7 +299,7 @@ export class HistoryService { if (!matchingContext) continue; const lockingBytecode = this.getOutputLockingBytecodeHex(output) ?? matchingContext.scriptHashData?.lockingBytecode; - const outputIdentifier = output.outputIdentifier ?? matchingContext.scriptHashData?.outputIdentifier ?? matchingContext.utxo.outputIdentifier; + const outputIdentifier = output.outputIdentifier ?? matchingContext.scriptHashData?.outputIdentifier; const role = output.roleIdentifier ?? this.getFirstEntityRole(entityRoles, commit.entityIdentifier) ?? @@ -380,20 +380,21 @@ export class HistoryService { if (usedUtxoIds.has(this.getUtxoId(context.utxo))) return false; if (scriptHash && context.utxo.scriptHash === scriptHash) return true; if (lockingBytecode && context.scriptHashData?.lockingBytecode === lockingBytecode) return true; - if (output.outputIdentifier && context.utxo.outputIdentifier === output.outputIdentifier) return true; + + if (output.outputIdentifier && context.scriptHashData?.outputIdentifier === output.outputIdentifier) return true; return false; }); } private projectStandaloneUtxo(context: UtxoContext): WalletHistoryItem { const output = this.projectUtxoOutput(context); - const templateIdentifier = context.scriptHashData?.templateIdentifier ?? context.utxo.templateIdentifier; + const templateIdentifier = context.scriptHashData?.templateIdentifier; const role = output.role; return { id: `utxo-${context.utxo.outpointTransactionHash}:${context.utxo.outpointIndex}`, source: "utxo", - templateIdentifier, + templateIdentifier: templateIdentifier ?? "", template: context.template?.name ?? "UnknownTemplate", roles: role ? [role] : ["unknown"], description: output.description, @@ -404,7 +405,7 @@ export class HistoryService { } private projectUtxoOutput(context: UtxoContext): WalletHistoryOutput { - const outputIdentifier = context.scriptHashData?.outputIdentifier ?? context.utxo.outputIdentifier; + const outputIdentifier = context.scriptHashData?.outputIdentifier; const role = context.scriptHashData?.roleIdentifier; return { @@ -557,7 +558,7 @@ export class HistoryService { variables: Record, ): string { try { - return compileCashAssemblyString(description, variables); + return compileCashAssemblyString({ cashAssemblyText: description, variables, evaluationDecodeMode: 'utf8' }); } catch { return this.interpolateSimpleCashAssemblyVariables(description, variables); } diff --git a/src/services/invitation.ts b/src/services/invitation.ts index a740853..6f7fac3 100644 --- a/src/services/invitation.ts +++ b/src/services/invitation.ts @@ -15,6 +15,10 @@ import type { } from "@xo-cash/types"; import type { UnspentOutputData } from "@xo-cash/state"; import { + bigIntToBinUint64LE, + bigIntToBinUintBE, + bigIntToBinUintLE, + bigIntToVmNumber, binToHex, encodeTransaction, generateTransaction, @@ -28,7 +32,7 @@ import type { BaseStorage } from "./storage.js"; import type { BlockchainService } from "./electrum.js"; import { EventEmitter } from "../utils/event-emitter.js"; -import { decodeExtendedJsonObject } from "../utils/ext-json.js"; +import { decodeExtendedJson, decodeExtendedJsonObject, encodeExtendedJson } from "../utils/ext-json.js"; import { compileCashAssemblyString } from "@xo-cash/engine"; export type InvitationEventMap = { @@ -279,7 +283,8 @@ export class Invitation extends EventEmitter { private async computeStatusInternal(): Promise { let missingReqs; try { - missingReqs = await this.engine.listMissingRequirements(this.data); + const missingRequirements = await this.engine.listMissingRequirements(this.data.invitationIdentifier); + missingReqs = missingRequirements.templateRequirements; } catch { return "unknown"; } @@ -394,7 +399,7 @@ export class Invitation extends EventEmitter { */ async sign(): Promise { // Sign the invitation - const signedInvitation = await this.engine.signInvitation(this.data); + const signedInvitation = await this.engine.signInvitation(this.data.invitationIdentifier); // Publish the signed invitation to the sync server this.publishInvitation(signedInvitation); @@ -413,7 +418,7 @@ export class Invitation extends EventEmitter { * @returns The transaction hash returned by the network after broadcast. */ async broadcast(): Promise { - const txHash = await this.engine.executeAction(this.data, { + const txHash = await this.engine.executeAction(this.data.invitationIdentifier, { broadcastTransaction: true, }); @@ -431,7 +436,7 @@ export class Invitation extends EventEmitter { */ async append(data: AppendInvitationParameters): Promise { // Append the commit to the invitation - this.data = await this.engine.appendInvitation(this.data, data); + this.data = await this.engine.appendInvitation(this.data.invitationIdentifier, data); // Sync the invitation to the sync server await this.publishInvitation(this.data); @@ -546,7 +551,7 @@ export class Invitation extends EventEmitter { * Get the missing requirements for the invitation */ async getMissingRequirements() { - return this.engine.listMissingRequirements(this.data); + return this.engine.listMissingRequirements(this.data.invitationIdentifier); } /** @@ -608,33 +613,41 @@ export class Invitation extends EventEmitter { ); } - const valueSatoshisIdentifier = output.valueSatoshis; - if (!valueSatoshisIdentifier) { + const valueSatoshisExpression = output.valueSatoshis; + if (!valueSatoshisExpression) { throw new Error( `Value satoshis identifier not found: ${outputIdentifier} in template: ${this.data.templateIdentifier}`, ); } + console.dir(this.data, { depth: null }); + // Create a list of all the variables from the commits const variables = this.data.commits.flatMap( (c) => c.data?.variables ?? [], ); + console.dir(variables, { depth: null }); // Create a dictionary of the variables const formattedVariables = variables.reduce( (acc, v) => { - acc[v.variableIdentifier ?? ""] = v.value; + const { variableIdentifier, value } = v; + console.log(typeof value); + acc[variableIdentifier ?? ""] = value; return acc; }, {} as Record, ); + console.dir(formattedVariables, { depth: null }); + // Compile the CashAssembly expression to get the value satoshis (It handles the variable replacement for us) - const valueSatoshis = await compileCashAssemblyString( - String(valueSatoshisIdentifier), - formattedVariables, + const valueSatoshis = compileCashAssemblyString( + { cashAssemblyText: String(valueSatoshisExpression), variables: formattedVariables, evaluationDecodeMode: 'bigint' }, ); + console.dir(valueSatoshis, { depth: null }); + // Return the value satoshis as a bigint // TODO: Check this of a vulnerability or crash - I assume there might be one if someone made the `valueSatoshis` a malicious expression return BigInt(valueSatoshis); @@ -688,9 +701,13 @@ export class Invitation extends EventEmitter { // Iterate through the outputs and sum the valueSatoshis for (const output of outputs) { if (typeof output === "string") { - totalSats += await this.getSatsOut(output); + const sats = await this.getSatsOut(output); + console.log(`Sats for output: ${output} is ${sats}`); + totalSats += sats } else { - totalSats += await this.getSatsOut(output.output); + const sats = await this.getSatsOut(output.output); + console.log(`Sats for output: ${output.output} is ${sats}`); + totalSats += sats; } } diff --git a/src/tui/screens/invitations/InvitationScreen.tsx b/src/tui/screens/invitations/InvitationScreen.tsx index 825efa6..c7c8bc4 100644 --- a/src/tui/screens/invitations/InvitationScreen.tsx +++ b/src/tui/screens/invitations/InvitationScreen.tsx @@ -221,7 +221,7 @@ export function InvitationScreen(): React.ReactElement { let isCurrent = true; - appService.engine.getOwnCommits(selectedInvitation.data) + appService.engine.findOwnCommits(selectedInvitation.data.invitationIdentifier) .then((ownCommits) => { if (!isCurrent) return; @@ -723,10 +723,14 @@ export function InvitationScreen(): React.ReactElement { {outputTemplate?.name ?? output.outputIdentifier ?? `Output ${idx}`} {/* Output description */} - {outputTemplate?.description && ' - ' + compileCashAssemblyString(outputTemplate?.description ?? '', variables.reduce((acc, variable) => { - acc[variable.variableIdentifier] = variable.value as XOInvitationVariableValue; - return acc; - }, {} as Record))} + {outputTemplate?.description && ' - ' + compileCashAssemblyString({ + cashAssemblyText: outputTemplate?.description, + variables: variables.reduce((acc, variable) => { + acc[variable.variableIdentifier] = variable.value as XOInvitationVariableValue; + + return acc; + }, {} as Record) + })} {/* Output value */} {outputSatoshis !== null && ` (${formatSatoshis(outputSatoshis)}${getFiatSuffix(outputSatoshis)})`} diff --git a/src/utils/invitation-flow.ts b/src/utils/invitation-flow.ts index 016a33d..65a35ac 100644 --- a/src/utils/invitation-flow.ts +++ b/src/utils/invitation-flow.ts @@ -30,7 +30,7 @@ export const isInvitationRequirementsComplete = async ( invitation: Invitation, ): Promise => { const missingRequirements = await invitation.getMissingRequirements(); - return !hasMissingRequirements(missingRequirements); + return !hasMissingRequirements(missingRequirements.templateRequirements); }; // TODO: Move to engine in templates.ts