Large amount of changes. Successfully broadcasts txs
This commit is contained in:
@@ -55,6 +55,8 @@ export class AppService extends EventEmitter<AppEventMap> {
|
||||
await engine.importTemplate(p2pkhTemplate);
|
||||
|
||||
// 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(p2pkhTemplate),
|
||||
'receiveOutput',
|
||||
@@ -63,9 +65,10 @@ export class AppService extends EventEmitter<AppEventMap> {
|
||||
|
||||
// Create our own storage for the invitations
|
||||
const storage = await Storage.create(config.invitationStoragePath);
|
||||
const walletStorage = await storage.child(seedHash.slice(0, 8))
|
||||
|
||||
// Create the app service
|
||||
return new AppService(engine, storage, config);
|
||||
return new AppService(engine, walletStorage, config);
|
||||
}
|
||||
|
||||
constructor(engine: Engine, storage: Storage, config: AppConfig) {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* - UTXOs we own (with descriptions derived from template outputs)
|
||||
*/
|
||||
|
||||
import type { Engine } from '@xo-cash/engine';
|
||||
import type { XOInvitation, XOTemplate } from '@xo-cash/types';
|
||||
import { type Engine, compileCashAssemblyString } from '@xo-cash/engine';
|
||||
import type { XOInvitation, XOInvitationVariableValue, XOTemplate } from '@xo-cash/types';
|
||||
import type { UnspentOutputData } from '@xo-cash/state';
|
||||
import type { Invitation } from './invitation.js';
|
||||
import { binToHex } from '@bitauth/libauth';
|
||||
@@ -203,7 +203,7 @@ export class HistoryService {
|
||||
const outputDef = template.outputs?.[utxo.outputIdentifier];
|
||||
|
||||
if (!outputDef) {
|
||||
return `${utxo.outputIdentifier} output`;
|
||||
return `[${template.name}] ${utxo.outputIdentifier} output`;
|
||||
}
|
||||
|
||||
// Start with the output name or identifier
|
||||
@@ -211,11 +211,7 @@ export class HistoryService {
|
||||
|
||||
// If there's a description, parse it and replace variable placeholders
|
||||
if (outputDef.description) {
|
||||
description = outputDef.description
|
||||
// Replace <variableName> placeholders (we don't have variable values here, so just clean up)
|
||||
.replace(/<([^>]+)>/g, (_, varId) => varId)
|
||||
// Remove $() wrappers
|
||||
.replace(/\$\(([^)]+)\)/g, '$1');
|
||||
description = compileCashAssemblyString(outputDef.description, {})
|
||||
}
|
||||
|
||||
return description;
|
||||
@@ -239,14 +235,13 @@ export class HistoryService {
|
||||
}
|
||||
|
||||
const committedVariables = invitation.commits.flatMap(c => c.data.variables ?? []);
|
||||
const formattedVariables = committedVariables.reduce((acc, v) => {
|
||||
acc[v.variableIdentifier ?? ''] = v.value;
|
||||
return acc;
|
||||
}, {} as Record<string, XOInvitationVariableValue>);
|
||||
|
||||
const description = compileCashAssemblyString(transaction.description, formattedVariables);
|
||||
|
||||
return transaction.description
|
||||
// Replace <variableName> with actual values
|
||||
.replace(/<([^>]+)>/g, (match, varId) => {
|
||||
const variable = committedVariables.find(v => v.variableIdentifier === varId);
|
||||
return variable ? String(variable.value) : match;
|
||||
})
|
||||
// Remove the $() wrapper around variable expressions
|
||||
.replace(/\$\(([^)]+)\)/g, '$1');
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user