Large amount of changes. Successfully broadcasts txs

This commit is contained in:
2026-03-08 15:53:50 +00:00
parent 66e9918e04
commit 9ef1720e1f
19 changed files with 1374 additions and 352 deletions

View File

@@ -1,6 +1,6 @@
import type { AcceptInvitationParameters, AppendInvitationParameters, Engine, FindSuitableResourcesParameters } from '@xo-cash/engine';
import { hasInvitationExpired } from '@xo-cash/engine';
import type { XOInvitation, XOInvitationCommit, XOInvitationInput, XOInvitationOutput, XOInvitationVariable } from '@xo-cash/types';
import type { XOInvitation, XOInvitationCommit, XOInvitationInput, XOInvitationOutput, XOInvitationVariable, XOInvitationVariableValue } from '@xo-cash/types';
import type { UnspentOutputData } from '@xo-cash/state';
import type { SSEvent } from '../utils/sse-client.js';
@@ -9,6 +9,7 @@ import type { Storage } from './storage.js';
import { EventEmitter } from '../utils/event-emitter.js'
import { decodeExtendedJsonObject } from '../utils/ext-json.js';
import { compileCashAssemblyString } from '@xo-cash/engine';
export type InvitationEventMap = {
'invitation-updated': XOInvitation;
@@ -32,14 +33,12 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
// Try to get the invitation from the storage
const invitationFromStorage = await dependencies.storage.get(invitation);
if (invitationFromStorage) {
console.log(`Invitation found in storage: ${invitation}`);
return this.create(invitationFromStorage, dependencies);
}
// Try to get the invitation from the sync server
const invitationFromSyncServer = await dependencies.syncServer.getInvitation(invitation);
if (invitationFromSyncServer && invitationFromSyncServer.invitationIdentifier === invitation) {
console.log(`Invitation found in sync server: ${invitation}`);
return this.create(invitationFromSyncServer, dependencies);
}
@@ -345,6 +344,14 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
await this.syncServer.publishInvitation(this.data);
}
/**
* Generate the locking bytecode for the invitation
* TODO: Find out if this has side-effects or needs special handling
*/
async generateLockingBytecode(outputIdentifier: string, roleIdentifier?: string): Promise<string> {
return this.engine.generateLockingBytecode(this.data.templateIdentifier, outputIdentifier, roleIdentifier);
}
async addOutputs(outputs: XOInvitationOutput[]): Promise<void> {
// Add the outputs to the invitation
await this.append({ outputs });
@@ -410,4 +417,89 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
async getLockingBytecode(outputIdentifier: string, roleIdentifier?: string): Promise<string> {
return this.engine.generateLockingBytecode(this.data.templateIdentifier, outputIdentifier, roleIdentifier);
}
}
/**
* Get the sats out for the invitation
* TODO: Clean up this function. Why is it so big? Can obviously make it 2 functions instead of recursive, but still...
*/
async getSatsOut(outputIdentifier?: string): Promise<bigint> {
// If an output identifier is provided, find all outputs with that identifier, and its valueSatoshis identifier back to the variables
if (outputIdentifier) {
// Get the valueSatoshis identifier from the template
const template = await this.engine.getTemplate(this.data.templateIdentifier);
if (!template) {
throw new Error(`Template not found: ${this.data.templateIdentifier} when trying to get sats out for output: ${outputIdentifier}`);
}
const output = template.outputs[outputIdentifier];
if (!output) {
throw new Error(`Output not found: ${outputIdentifier} in template: ${this.data.templateIdentifier}`);
}
const valueSatoshisIdentifier = output.valueSatoshis;
if (!valueSatoshisIdentifier) {
throw new Error(`Value satoshis identifier not found: ${outputIdentifier} in template: ${this.data.templateIdentifier}`);
}
// Create a list of all the variables from the commits
const variables = this.data.commits.flatMap(c => c.data?.variables ?? []);
// Create a dictionary of the variables
const formattedVariables = variables.reduce((acc, v) => {
acc[v.variableIdentifier ?? ''] = v.value;
return acc;
}, {} as Record<string, XOInvitationVariableValue>);
// Compile the CashAssembly expression to get the value satoshis (It handles the variable replacement for us)
const valueSatoshis = await compileCashAssemblyString(String(valueSatoshisIdentifier), formattedVariables);
// 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);
}
// If we didnt get an output identifier, go through the action outputs and sum the valueSatoshis
const action = this.data.actionIdentifier;
if (!action) {
throw new Error(`Action not found: ${this.data.actionIdentifier} when trying to get sats out for output: ${outputIdentifier}`);
}
// Get the template
const template = await this.engine.getTemplate(this.data.templateIdentifier);
if (!template) {
throw new Error(`Template not found: ${this.data.templateIdentifier} when trying to get sats out for action: ${action}`);
}
// Get the transaction ID from the action
const transactionID = template.actions[action]?.transaction
if (!transactionID) {
throw new Error(`Transactions not found: ${action} in template: ${this.data.templateIdentifier}`);
}
// Get the transaction from the template
const transaction = template.transactions?.[transactionID];
if (!transaction) {
throw new Error(`Transaction not found: ${transactionID} in template: ${this.data.templateIdentifier}`);
}
// Get the outputs from the transaction
const outputs = transaction.outputs;
if (!outputs) {
throw new Error(`Outputs not found: ${transactionID} in template: ${this.data.templateIdentifier}`);
}
// Create a value to store the cummulative total of the outputs
let totalSats = 0n;
// Iterate through the outputs and sum the valueSatoshis
for (const output of outputs) {
if (typeof output === 'string') {
totalSats += await this.getSatsOut(output);
} else {
totalSats += await this.getSatsOut(output.output);
}
}
return totalSats;
}
}