Fix receive and send
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import type { AcceptInvitationParameters, AppendInvitationParameters, Engine, FindSuitableResourcesParameters } from '@xo-cash/engine';
|
||||
import { hasInvitationExpired } from '@xo-cash/engine';
|
||||
import { hasInvitationExpired, mergeInvitationCommits } from '@xo-cash/engine';
|
||||
import type { XOInvitation, XOInvitationCommit, XOInvitationInput, XOInvitationOutput, XOInvitationVariable, XOInvitationVariableValue } from '@xo-cash/types';
|
||||
import type { UnspentOutputData } from '@xo-cash/state';
|
||||
import { binToHex, encodeTransaction, generateTransaction, hashTransaction, hexToBin } from '@bitauth/libauth';
|
||||
|
||||
import type { SSEvent } from '../utils/sse-client.js';
|
||||
import type { SyncServer } from '../utils/sync-server.js';
|
||||
import type { Storage } from './storage.js';
|
||||
import type { ElectrumService } from './electrum.js';
|
||||
|
||||
import { EventEmitter } from '../utils/event-emitter.js'
|
||||
import { decodeExtendedJsonObject } from '../utils/ext-json.js';
|
||||
@@ -20,6 +22,7 @@ export type InvitationDependencies = {
|
||||
syncServer: SyncServer;
|
||||
storage: Storage;
|
||||
engine: Engine;
|
||||
electrum: ElectrumService;
|
||||
}
|
||||
|
||||
export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
@@ -87,16 +90,7 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
* TODO: This should be a composite with the sync server (probably. We currently double handle this work, which is stupid)
|
||||
*/
|
||||
private storage: Storage;
|
||||
|
||||
/**
|
||||
* True after we have successfully called sign() on this invitation (session-only, not persisted).
|
||||
*/
|
||||
private _weHaveSigned = false;
|
||||
|
||||
/**
|
||||
* True after we have successfully called broadcast() on this invitation (session-only, not persisted).
|
||||
*/
|
||||
private _broadcasted = false;
|
||||
private electrum: ElectrumService;
|
||||
|
||||
/**
|
||||
* The status of the invitation (last emitted word: pending, actionable, signed, ready, complete, expired, unknown).
|
||||
@@ -116,6 +110,7 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
this.engine = dependencies.engine;
|
||||
this.syncServer = dependencies.syncServer;
|
||||
this.storage = dependencies.storage;
|
||||
this.electrum = dependencies.electrum;
|
||||
|
||||
// I cannot express this enough, but the event handler does not need a clean up.
|
||||
// There is this beautiful thing called a "garbage collector". Once this class is removed from scope (removed from the invitations array) all the references
|
||||
@@ -217,21 +212,14 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
/**
|
||||
* Internal status computation: returns a single word.
|
||||
* NOTE: This could be a Enum-like object as well. May be a nice improvement. - DO NOT USE TS ENUM, THEY ARENT NATIVELY SUPPORTED IN NODE.JS
|
||||
* - expired: any commit has expired
|
||||
* - complete: we have broadcast this invitation
|
||||
* - expired: any commit has expired
|
||||
* - ready: no missing requirements and we have signed (ready to broadcast)
|
||||
* - signed: we have signed but there are still missing parts (waiting for others)
|
||||
* - actionable: you can provide data (missing requirements and/or you can sign)
|
||||
* - unknown: template/action not found or error
|
||||
*/
|
||||
private async computeStatusInternal(): Promise<string> {
|
||||
if (hasInvitationExpired(this.data)) {
|
||||
return 'expired';
|
||||
}
|
||||
if (this._broadcasted) {
|
||||
return 'complete';
|
||||
}
|
||||
|
||||
let missingReqs;
|
||||
try {
|
||||
missingReqs = await this.engine.listMissingRequirements(this.data);
|
||||
@@ -245,15 +233,74 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
(missingReqs.outputs?.length ?? 0) > 0 ||
|
||||
(missingReqs.roles !== undefined && Object.keys(missingReqs.roles).length > 0);
|
||||
|
||||
if (!hasMissing && this._weHaveSigned) {
|
||||
const hasSignedCommit = this.hasSignedCommitInInvitation();
|
||||
|
||||
if (!hasMissing) {
|
||||
const transactionHash = await this.deriveTransactionHash();
|
||||
if (transactionHash && await this.electrum.hasSeenTransaction(transactionHash)) {
|
||||
return 'complete';
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInvitationExpired(this.data)) {
|
||||
return 'expired';
|
||||
}
|
||||
|
||||
if (!hasMissing && hasSignedCommit) {
|
||||
return 'ready';
|
||||
}
|
||||
if (hasMissing && this._weHaveSigned) {
|
||||
if (hasMissing && hasSignedCommit) {
|
||||
return 'signed';
|
||||
}
|
||||
return 'actionable';
|
||||
}
|
||||
|
||||
private hasSignedCommitInInvitation(): boolean {
|
||||
for (const commit of this.data.commits) {
|
||||
for (const input of commit.data.inputs ?? []) {
|
||||
if (!input.mergesWith) continue;
|
||||
if (input.unlockingBytecode === undefined) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the transaction to get the TX hash, this is so we can check its status on the blockchain.
|
||||
* TODO: Remove this. This should be part of the engine. The code is virtually identical to `executeAction` except it doesnt throw if the invitation is expired
|
||||
* @returns txHash or undefined if the transaction could not be built
|
||||
*/
|
||||
private async deriveTransactionHash(): Promise<string | undefined> {
|
||||
try {
|
||||
const template = await this.engine.getTemplate(this.data.templateIdentifier);
|
||||
if (!template) return undefined;
|
||||
|
||||
const mergedCommit = mergeInvitationCommits(this.data, template);
|
||||
if (!mergedCommit) return undefined;
|
||||
|
||||
const transactionResult = generateTransaction({
|
||||
version: mergedCommit.transactionVersion,
|
||||
locktime: mergedCommit.transactionLocktime,
|
||||
// @ts-expect-error merged inputs include additional invitation metadata.
|
||||
inputs: mergedCommit.inputs,
|
||||
// @ts-expect-error merged outputs include additional invitation metadata.
|
||||
outputs: mergedCommit.outputs,
|
||||
});
|
||||
|
||||
if (!transactionResult.success) return undefined;
|
||||
|
||||
const transactionHex = binToHex(encodeTransaction(transactionResult.transaction));
|
||||
const rawHash: unknown = hashTransaction(hexToBin(transactionHex));
|
||||
if (typeof rawHash === 'string') return rawHash;
|
||||
if (rawHash instanceof Uint8Array) return binToHex(rawHash);
|
||||
return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of the invitation and emit the new single-word status.
|
||||
*/
|
||||
@@ -291,7 +338,6 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
await this.storage.set(this.data.invitationIdentifier, signedInvitation);
|
||||
|
||||
this.data = signedInvitation;
|
||||
this._weHaveSigned = true;
|
||||
|
||||
// Update the status of the invitation
|
||||
await this.updateStatus();
|
||||
@@ -306,8 +352,6 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
broadcastTransaction: true,
|
||||
});
|
||||
|
||||
this._broadcasted = true;
|
||||
|
||||
// Update the status of the invitation
|
||||
await this.updateStatus();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user