Fix history. Fix invitation accept
This commit is contained in:
@@ -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<string, XOInvitationVariableValue>;
|
||||
walletCommits: XOInvitationCommit[];
|
||||
walletEntityIdentifier?: string;
|
||||
}
|
||||
|
||||
@@ -73,12 +78,8 @@ export class HistoryService {
|
||||
private invitations: Invitation[],
|
||||
) {}
|
||||
|
||||
async extractEntities(invitation: XOInvitation): Promise<string[]> {
|
||||
const entities = new Set<string>();
|
||||
for (const commit of invitation.commits) {
|
||||
entities.add(commit.entityIdentifier);
|
||||
}
|
||||
return Array.from(entities);
|
||||
extractEntities(invitation: XOInvitation): Record<string, XOInvitationCommit[]> {
|
||||
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<HistoryItem[]> {
|
||||
const allUtxos = await this.engine.listUnspentOutputsData();
|
||||
const ownOutpoints = new Set<string>();
|
||||
const ownLockingBytecodes = new Set<string>();
|
||||
const invitationByOrigin = new Map<string, UtxoOriginContext>();
|
||||
const outpointValueSatoshis = new Map<string, bigint>();
|
||||
|
||||
@@ -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<XOInvitationCommit[]> {
|
||||
try {
|
||||
return await this.engine.getOwnCommits(invitation);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private buildWalletInputItemsForInvitation(
|
||||
context: InvitationContext,
|
||||
walletRole?: string,
|
||||
hasWalletOutputs: boolean = false,
|
||||
outpointValueSatoshis: Map<string, bigint> = 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<string>,
|
||||
ownLockingBytecodes: Set<string>,
|
||||
): string | undefined {
|
||||
const scores = new Map<string, number>();
|
||||
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 {
|
||||
|
||||
@@ -85,8 +85,11 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user