Fix history. Fix invitation accept
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import { binToHex } from "@bitauth/libauth";
|
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 { UnspentOutputData } from "@xo-cash/state";
|
||||||
import type {
|
import type {
|
||||||
XOInvitation,
|
XOInvitation,
|
||||||
@@ -59,6 +63,7 @@ interface InvitationContext {
|
|||||||
invitation: Invitation;
|
invitation: Invitation;
|
||||||
template: XOTemplate | null;
|
template: XOTemplate | null;
|
||||||
variables: Record<string, XOInvitationVariableValue>;
|
variables: Record<string, XOInvitationVariableValue>;
|
||||||
|
walletCommits: XOInvitationCommit[];
|
||||||
walletEntityIdentifier?: string;
|
walletEntityIdentifier?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +78,8 @@ export class HistoryService {
|
|||||||
private invitations: Invitation[],
|
private invitations: Invitation[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async extractEntities(invitation: XOInvitation): Promise<string[]> {
|
extractEntities(invitation: XOInvitation): Record<string, XOInvitationCommit[]> {
|
||||||
const entities = new Set<string>();
|
return listInvitationCommitsByEntity(invitation);
|
||||||
for (const commit of invitation.commits) {
|
|
||||||
entities.add(commit.entityIdentifier);
|
|
||||||
}
|
|
||||||
return Array.from(entities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entities are currently static per invitation. So, we can try to match the roles to entities by:
|
// 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[]> {
|
async getHistory(): Promise<HistoryItem[]> {
|
||||||
const allUtxos = await this.engine.listUnspentOutputsData();
|
const allUtxos = await this.engine.listUnspentOutputsData();
|
||||||
const ownOutpoints = new Set<string>();
|
|
||||||
const ownLockingBytecodes = new Set<string>();
|
|
||||||
const invitationByOrigin = new Map<string, UtxoOriginContext>();
|
const invitationByOrigin = new Map<string, UtxoOriginContext>();
|
||||||
const outpointValueSatoshis = new Map<string, bigint>();
|
const outpointValueSatoshis = new Map<string, bigint>();
|
||||||
|
|
||||||
@@ -137,8 +136,6 @@ export class HistoryService {
|
|||||||
utxo.outpointTransactionHash,
|
utxo.outpointTransactionHash,
|
||||||
utxo.outpointIndex,
|
utxo.outpointIndex,
|
||||||
);
|
);
|
||||||
ownOutpoints.add(outpointKey);
|
|
||||||
ownLockingBytecodes.add(utxo.scriptHash);
|
|
||||||
outpointValueSatoshis.set(outpointKey, BigInt(utxo.valueSatoshis));
|
outpointValueSatoshis.set(outpointKey, BigInt(utxo.valueSatoshis));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,15 +145,15 @@ export class HistoryService {
|
|||||||
const template =
|
const template =
|
||||||
(await this.engine.getTemplate(invitation.data.templateIdentifier)) ??
|
(await this.engine.getTemplate(invitation.data.templateIdentifier)) ??
|
||||||
null;
|
null;
|
||||||
const walletEntityIdentifier = this.resolveWalletEntityIdentifier(
|
const walletCommits = await this.getWalletCommitsForInvitation(
|
||||||
invitation,
|
invitation.data,
|
||||||
ownOutpoints,
|
|
||||||
ownLockingBytecodes,
|
|
||||||
);
|
);
|
||||||
|
const walletEntityIdentifier = walletCommits[0]?.entityIdentifier;
|
||||||
contexts.set(invitation.data.invitationIdentifier, {
|
contexts.set(invitation.data.invitationIdentifier, {
|
||||||
invitation,
|
invitation,
|
||||||
template,
|
template,
|
||||||
variables,
|
variables,
|
||||||
|
walletCommits,
|
||||||
walletEntityIdentifier,
|
walletEntityIdentifier,
|
||||||
});
|
});
|
||||||
this.indexInvitationOutputsByUtxoOrigin(invitationByOrigin, invitation);
|
this.indexInvitationOutputsByUtxoOrigin(invitationByOrigin, invitation);
|
||||||
@@ -186,7 +183,6 @@ export class HistoryService {
|
|||||||
const invitationInputs = this.buildWalletInputItemsForInvitation(
|
const invitationInputs = this.buildWalletInputItemsForInvitation(
|
||||||
context,
|
context,
|
||||||
roles[0],
|
roles[0],
|
||||||
invitationOutputs.length > 0,
|
|
||||||
outpointValueSatoshis,
|
outpointValueSatoshis,
|
||||||
);
|
);
|
||||||
const invitationDescription = this.deriveInvitationDescription(
|
const invitationDescription = this.deriveInvitationDescription(
|
||||||
@@ -287,51 +283,25 @@ export class HistoryService {
|
|||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getWalletCommitsForInvitation(
|
||||||
|
invitation: XOInvitation,
|
||||||
|
): Promise<XOInvitationCommit[]> {
|
||||||
|
try {
|
||||||
|
return await this.engine.getOwnCommits(invitation);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private buildWalletInputItemsForInvitation(
|
private buildWalletInputItemsForInvitation(
|
||||||
context: InvitationContext,
|
context: InvitationContext,
|
||||||
walletRole?: string,
|
walletRole?: string,
|
||||||
hasWalletOutputs: boolean = false,
|
|
||||||
outpointValueSatoshis: Map<string, bigint> = new Map(),
|
outpointValueSatoshis: Map<string, bigint> = new Map(),
|
||||||
): HistoryUtxoItem[] {
|
): HistoryUtxoItem[] {
|
||||||
const invitation = context.invitation.data;
|
const invitation = context.invitation.data;
|
||||||
const commits = invitation.commits ?? [];
|
const relevantCommits = context.walletCommits.filter(
|
||||||
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(
|
|
||||||
(commit) => (commit.data.inputs?.length ?? 0) > 0,
|
(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(
|
const txDescription = this.deriveTransactionActivityDescription(
|
||||||
invitation,
|
invitation,
|
||||||
context.template,
|
context.template,
|
||||||
@@ -355,7 +325,10 @@ export class HistoryService {
|
|||||||
context.variables,
|
context.variables,
|
||||||
);
|
);
|
||||||
const templateName = context.template?.name ?? "UnknownTemplate";
|
const templateName = context.template?.name ?? "UnknownTemplate";
|
||||||
const role = walletRole ?? "sender";
|
const role =
|
||||||
|
this.deriveCommitRoleIdentifier(commit, invitation, context.template) ??
|
||||||
|
walletRole ??
|
||||||
|
"sender";
|
||||||
const inputValue = this.resolveInputSatoshis(
|
const inputValue = this.resolveInputSatoshis(
|
||||||
txHash,
|
txHash,
|
||||||
inputIndex,
|
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(
|
private deriveWalletRolesForInvitation(
|
||||||
context: InvitationContext,
|
context: InvitationContext,
|
||||||
outputs: HistoryUtxoItem[],
|
outputs: HistoryUtxoItem[],
|
||||||
@@ -444,33 +410,20 @@ export class HistoryService {
|
|||||||
roles.add("receiver");
|
roles.add("receiver");
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasInputCommit = (
|
for (const commit of context.walletCommits) {
|
||||||
context.walletEntityIdentifier
|
const role = this.deriveCommitRoleIdentifier(
|
||||||
? context.invitation.data.commits.filter(
|
commit,
|
||||||
(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(
|
|
||||||
context.invitation.data,
|
context.invitation.data,
|
||||||
context.template,
|
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"];
|
return roles.size > 0 ? Array.from(roles) : ["unknown"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,54 +491,6 @@ export class HistoryService {
|
|||||||
return invitationByUtxoOrigin.get(originKey)?.roleIdentifier;
|
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(
|
private deriveUtxoDescription(
|
||||||
utxo: UnspentOutputData,
|
utxo: UnspentOutputData,
|
||||||
template: XOTemplate | null,
|
template: XOTemplate | null,
|
||||||
@@ -715,27 +620,6 @@ export class HistoryService {
|
|||||||
return undefined;
|
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(
|
private inferRoleFromOutputIdentifier(
|
||||||
outputIdentifier: string,
|
outputIdentifier: string,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
|
|||||||
@@ -85,8 +85,11 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
|||||||
throw new Error(`Template not found: ${invitation.templateIdentifier}`);
|
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
|
// Create the invitation
|
||||||
const invitationInstance = new Invitation(invitation, dependencies);
|
const invitationInstance = new Invitation(engineInvitation, dependencies);
|
||||||
|
|
||||||
// Start the invitation and its tracking
|
// Start the invitation and its tracking
|
||||||
await invitationInstance.start();
|
await invitationInstance.start();
|
||||||
|
|||||||
Reference in New Issue
Block a user