|
|
|
@@ -22,24 +22,6 @@ import type {
|
|
|
|
XOTemplateVariable,
|
|
|
|
XOTemplateVariable,
|
|
|
|
} from "@xo-cash/types";
|
|
|
|
} from "@xo-cash/types";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* View metadata copied from a template definition onto a resolved invitation item.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
interface TemplateViewMetadata {
|
|
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Role-specific view metadata from a template output definition.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
export interface ResolvedInvitationOutputRoleMetadata {
|
|
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* A variable from invitation commits enriched with its template definition.
|
|
|
|
* A variable from invitation commits enriched with its template definition.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
@@ -74,7 +56,10 @@ export type ResolvedInvitationOutput = XOInvitationOutput & {
|
|
|
|
name?: string;
|
|
|
|
name?: string;
|
|
|
|
description?: string;
|
|
|
|
description?: string;
|
|
|
|
icon?: string;
|
|
|
|
icon?: string;
|
|
|
|
roles?: Record<string, ResolvedInvitationOutputRoleMetadata>;
|
|
|
|
roles?: Record<
|
|
|
|
|
|
|
|
string,
|
|
|
|
|
|
|
|
{ name?: string; description?: string; icon?: string }
|
|
|
|
|
|
|
|
>;
|
|
|
|
lockingScript?: string;
|
|
|
|
lockingScript?: string;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@@ -93,11 +78,15 @@ export interface ResolvedInvitationData {
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Picks human-readable view fields from a template definition.
|
|
|
|
* Picks human-readable view fields from a template definition.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function pickTemplateViewMetadata(
|
|
|
|
export const pickTemplateViewMetadata = (definition?: {
|
|
|
|
definition: TemplateViewMetadata | undefined,
|
|
|
|
name?: string;
|
|
|
|
): TemplateViewMetadata {
|
|
|
|
description?: string;
|
|
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
|
|
}) => {
|
|
|
|
if (!definition) return {};
|
|
|
|
if (!definition) return {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only copy fields that are present so absent template metadata does not
|
|
|
|
|
|
|
|
// overwrite committed values when this object is spread onto a commit row.
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
...(definition.name !== undefined && { name: definition.name }),
|
|
|
|
...(definition.name !== undefined && { name: definition.name }),
|
|
|
|
...(definition.description !== undefined && {
|
|
|
|
...(definition.description !== undefined && {
|
|
|
|
@@ -105,14 +94,14 @@ function pickTemplateViewMetadata(
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
...(definition.icon !== undefined && { icon: definition.icon }),
|
|
|
|
...(definition.icon !== undefined && { icon: definition.icon }),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Picks variable metadata from a template variable definition.
|
|
|
|
* Picks variable metadata from a template variable definition.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function pickTemplateVariableMetadata(
|
|
|
|
export const pickTemplateVariableMetadata = (
|
|
|
|
definition: XOTemplateVariable | undefined,
|
|
|
|
definition?: XOTemplateVariable,
|
|
|
|
): Pick<ResolvedInvitationVariable, "name" | "description" | "type" | "hint"> {
|
|
|
|
) => {
|
|
|
|
if (!definition) return {};
|
|
|
|
if (!definition) return {};
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
@@ -120,17 +109,12 @@ function pickTemplateVariableMetadata(
|
|
|
|
...(definition.type !== undefined && { type: definition.type }),
|
|
|
|
...(definition.type !== undefined && { type: definition.type }),
|
|
|
|
...(definition.hint !== undefined && { hint: definition.hint }),
|
|
|
|
...(definition.hint !== undefined && { hint: definition.hint }),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Picks input metadata from a template input definition.
|
|
|
|
* Picks input metadata from a template input definition.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function pickTemplateInputMetadata(
|
|
|
|
export const pickTemplateInputMetadata = (definition?: XOTemplateInput) => {
|
|
|
|
definition: XOTemplateInput | undefined,
|
|
|
|
|
|
|
|
): Pick<
|
|
|
|
|
|
|
|
ResolvedInvitationInput,
|
|
|
|
|
|
|
|
"name" | "description" | "icon" | "unlockingScript" | "omitChangeAmounts"
|
|
|
|
|
|
|
|
> {
|
|
|
|
|
|
|
|
if (!definition) return {};
|
|
|
|
if (!definition) return {};
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
@@ -142,20 +126,7 @@ function pickTemplateInputMetadata(
|
|
|
|
omitChangeAmounts: definition.omitChangeAmounts,
|
|
|
|
omitChangeAmounts: definition.omitChangeAmounts,
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Template display metadata layered onto a committed output.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
interface TemplateOutputMetadata {
|
|
|
|
|
|
|
|
name?: string;
|
|
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
|
|
roles?: Record<string, ResolvedInvitationOutputRoleMetadata>;
|
|
|
|
|
|
|
|
lockingScript?: string;
|
|
|
|
|
|
|
|
valueSatoshis?: bigint | string;
|
|
|
|
|
|
|
|
token?: XOTemplateOutput["token"];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Picks output metadata from a template output definition.
|
|
|
|
* Picks output metadata from a template output definition.
|
|
|
|
@@ -164,9 +135,7 @@ interface TemplateOutputMetadata {
|
|
|
|
* defaults; display-oriented fields like name, description, and template
|
|
|
|
* defaults; display-oriented fields like name, description, and template
|
|
|
|
* valueSatoshis expressions are layered on for UI rendering.
|
|
|
|
* valueSatoshis expressions are layered on for UI rendering.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function pickTemplateOutputMetadata(
|
|
|
|
export const pickTemplateOutputMetadata = (definition?: XOTemplateOutput) => {
|
|
|
|
definition: XOTemplateOutput | undefined,
|
|
|
|
|
|
|
|
): TemplateOutputMetadata {
|
|
|
|
|
|
|
|
if (!definition) return {};
|
|
|
|
if (!definition) return {};
|
|
|
|
|
|
|
|
|
|
|
|
const roles = definition.roles
|
|
|
|
const roles = definition.roles
|
|
|
|
@@ -184,91 +153,94 @@ function pickTemplateOutputMetadata(
|
|
|
|
...(definition.lockingScript !== undefined && {
|
|
|
|
...(definition.lockingScript !== undefined && {
|
|
|
|
lockingScript: definition.lockingScript,
|
|
|
|
lockingScript: definition.lockingScript,
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
|
|
|
|
// Keep CashAssembly expressions (e.g. "$(<totalSatoshis>)") for UI compilation;
|
|
|
|
|
|
|
|
// committed bigint values on the output row take precedence when spread later.
|
|
|
|
...(definition.valueSatoshis !== undefined && {
|
|
|
|
...(definition.valueSatoshis !== undefined && {
|
|
|
|
valueSatoshis: definition.valueSatoshis,
|
|
|
|
valueSatoshis: definition.valueSatoshis,
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
...(definition.token !== undefined && { token: definition.token }),
|
|
|
|
...(definition.token !== undefined && { token: definition.token }),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Enriches a committed variable with its template definition.
|
|
|
|
* Enriches a committed variable with its template definition.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function resolveVariable(
|
|
|
|
export const resolveVariable = (
|
|
|
|
variable: XOInvitationVariable,
|
|
|
|
variable: XOInvitationVariable,
|
|
|
|
entityIdentifier: string,
|
|
|
|
entityIdentifier: string,
|
|
|
|
template: XOTemplate,
|
|
|
|
template: XOTemplate,
|
|
|
|
): ResolvedInvitationVariable {
|
|
|
|
): ResolvedInvitationVariable => ({
|
|
|
|
const definition = template.variables?.[variable.variableIdentifier];
|
|
|
|
entityIdentifier,
|
|
|
|
|
|
|
|
variableIdentifier: variable.variableIdentifier,
|
|
|
|
return {
|
|
|
|
...(variable.roleIdentifier !== undefined && {
|
|
|
|
entityIdentifier,
|
|
|
|
roleIdentifier: variable.roleIdentifier,
|
|
|
|
variableIdentifier: variable.variableIdentifier,
|
|
|
|
}),
|
|
|
|
...(variable.roleIdentifier !== undefined && {
|
|
|
|
value: variable.value,
|
|
|
|
roleIdentifier: variable.roleIdentifier,
|
|
|
|
...pickTemplateVariableMetadata(
|
|
|
|
}),
|
|
|
|
template.variables?.[variable.variableIdentifier],
|
|
|
|
value: variable.value,
|
|
|
|
),
|
|
|
|
...pickTemplateVariableMetadata(definition),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Enriches a committed input with its template definition when an identifier is present.
|
|
|
|
* Enriches a committed input with its template definition when an identifier is present.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function resolveInput(
|
|
|
|
export const resolveInput = (
|
|
|
|
input: XOInvitationInput,
|
|
|
|
input: XOInvitationInput,
|
|
|
|
entityIdentifier: string,
|
|
|
|
entityIdentifier: string,
|
|
|
|
template: XOTemplate,
|
|
|
|
template: XOTemplate,
|
|
|
|
): ResolvedInvitationInput {
|
|
|
|
): ResolvedInvitationInput => ({
|
|
|
|
const definition = input.inputIdentifier
|
|
|
|
entityIdentifier,
|
|
|
|
? template.inputs?.[input.inputIdentifier]
|
|
|
|
...input,
|
|
|
|
: undefined;
|
|
|
|
...pickTemplateInputMetadata(
|
|
|
|
|
|
|
|
input.inputIdentifier
|
|
|
|
return {
|
|
|
|
? template.inputs?.[input.inputIdentifier]
|
|
|
|
entityIdentifier,
|
|
|
|
: undefined,
|
|
|
|
...input,
|
|
|
|
),
|
|
|
|
...pickTemplateInputMetadata(definition),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Enriches a committed output with its template definition when an identifier is present.
|
|
|
|
* Enriches a committed output with its template definition when an identifier is present.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* Template metadata is spread after commit fields so display expressions (e.g.
|
|
|
|
|
|
|
|
* `valueSatoshis: "$(<totalSatoshis>)"`) layer on for the UI even when the merger
|
|
|
|
|
|
|
|
* already resolved a bigint for transaction encoding.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function resolveOutput(
|
|
|
|
export const resolveOutput = (
|
|
|
|
output: XOInvitationOutput,
|
|
|
|
output: XOInvitationOutput,
|
|
|
|
entityIdentifier: string,
|
|
|
|
entityIdentifier: string,
|
|
|
|
template: XOTemplate,
|
|
|
|
template: XOTemplate,
|
|
|
|
): ResolvedInvitationOutput {
|
|
|
|
): ResolvedInvitationOutput =>
|
|
|
|
const definition = output.outputIdentifier
|
|
|
|
({
|
|
|
|
? template.outputs?.[output.outputIdentifier]
|
|
|
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
const templateMetadata = pickTemplateOutputMetadata(definition);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
entityIdentifier,
|
|
|
|
entityIdentifier,
|
|
|
|
...output,
|
|
|
|
...output,
|
|
|
|
...templateMetadata,
|
|
|
|
...pickTemplateOutputMetadata(
|
|
|
|
} as ResolvedInvitationOutput;
|
|
|
|
output.outputIdentifier
|
|
|
|
}
|
|
|
|
? template.outputs?.[output.outputIdentifier]
|
|
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
// Template valueSatoshis may be a CashAssembly string while XOInvitationOutput
|
|
|
|
|
|
|
|
// expects bigint — the read model intentionally allows both for display.
|
|
|
|
|
|
|
|
}) as ResolvedInvitationOutput;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Converts hex or binary invitation bytecode fields to hex strings for display.
|
|
|
|
* Converts hex or binary invitation bytecode fields to hex strings for display.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function hexOrBinToHex(
|
|
|
|
export const hexOrBinToHex = (value?: string | Uint8Array) => {
|
|
|
|
value: string | Uint8Array | undefined,
|
|
|
|
|
|
|
|
): string | undefined {
|
|
|
|
|
|
|
|
if (value === undefined) {
|
|
|
|
if (value === undefined) {
|
|
|
|
return undefined;
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return typeof value === "string" ? value : binToHex(value);
|
|
|
|
return typeof value === "string" ? value : binToHex(value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Normalizes a merged input row for UI display (hex strings, no encoding placeholders).
|
|
|
|
* Normalizes a merged input row for UI display (hex strings, no encoding placeholders).
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* The engine merger returns libauth-ready binary fields and fills in encoding
|
|
|
|
|
|
|
|
* defaults (empty unlocking bytecode, sequence 0) that are not useful in the TUI.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function normalizeMergedInputForDisplay(input: XOInvitationInput): XOInvitationInput {
|
|
|
|
export const normalizeMergedInputForDisplay = (input: XOInvitationInput) => {
|
|
|
|
const normalized: XOInvitationInput = { ...input };
|
|
|
|
const normalized = { ...input };
|
|
|
|
|
|
|
|
|
|
|
|
if (input.outpointTransactionHash !== undefined) {
|
|
|
|
if (input.outpointTransactionHash !== undefined) {
|
|
|
|
normalized.outpointTransactionHash = hexOrBinToHex(
|
|
|
|
normalized.outpointTransactionHash = hexOrBinToHex(
|
|
|
|
@@ -277,6 +249,7 @@ function normalizeMergedInputForDisplay(input: XOInvitationInput): XOInvitationI
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (input.unlockingBytecode !== undefined) {
|
|
|
|
if (input.unlockingBytecode !== undefined) {
|
|
|
|
|
|
|
|
// Engine uses an empty Uint8Array as a placeholder until the input is signed.
|
|
|
|
const isPlaceholder =
|
|
|
|
const isPlaceholder =
|
|
|
|
input.unlockingBytecode instanceof Uint8Array &&
|
|
|
|
input.unlockingBytecode instanceof Uint8Array &&
|
|
|
|
input.unlockingBytecode.length === 0;
|
|
|
|
input.unlockingBytecode.length === 0;
|
|
|
|
@@ -290,20 +263,19 @@ function normalizeMergedInputForDisplay(input: XOInvitationInput): XOInvitationI
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Default sequence from the merger is not meaningful for display.
|
|
|
|
if (normalized.sequenceNumber === 0) {
|
|
|
|
if (normalized.sequenceNumber === 0) {
|
|
|
|
delete normalized.sequenceNumber;
|
|
|
|
delete normalized.sequenceNumber;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalized;
|
|
|
|
return normalized;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Normalizes a merged output row for UI display (hex strings).
|
|
|
|
* Normalizes a merged output row for UI display (hex strings).
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function normalizeMergedOutputForDisplay(
|
|
|
|
export const normalizeMergedOutputForDisplay = (output: XOInvitationOutput) => {
|
|
|
|
output: XOInvitationOutput,
|
|
|
|
const normalized = { ...output };
|
|
|
|
): XOInvitationOutput {
|
|
|
|
|
|
|
|
const normalized: XOInvitationOutput = { ...output };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (output.lockingBytecode !== undefined) {
|
|
|
|
if (output.lockingBytecode !== undefined) {
|
|
|
|
normalized.lockingBytecode = hexOrBinToHex(
|
|
|
|
normalized.lockingBytecode = hexOrBinToHex(
|
|
|
|
@@ -312,16 +284,16 @@ function normalizeMergedOutputForDisplay(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return normalized;
|
|
|
|
return normalized;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Recovers `outputIdentifier` from the source commit because the merger strips it
|
|
|
|
* Recovers `outputIdentifier` from the source commit because the merger strips it
|
|
|
|
* after template resolution.
|
|
|
|
* after template resolution.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function findOutputIdentifierForMergedOutput(
|
|
|
|
export const findOutputIdentifierForMergedOutput = (
|
|
|
|
commit: XOInvitationCommit | undefined,
|
|
|
|
commit: XOInvitationCommit | undefined,
|
|
|
|
mergedOutput: XOInvitationOutput,
|
|
|
|
mergedOutput: XOInvitationOutput,
|
|
|
|
): string | undefined {
|
|
|
|
) => {
|
|
|
|
const outputs = commit?.data?.outputs ?? [];
|
|
|
|
const outputs = commit?.data?.outputs ?? [];
|
|
|
|
const mergedBytecodeHex = hexOrBinToHex(mergedOutput.lockingBytecode);
|
|
|
|
const mergedBytecodeHex = hexOrBinToHex(mergedOutput.lockingBytecode);
|
|
|
|
|
|
|
|
|
|
|
|
@@ -332,6 +304,7 @@ function findOutputIdentifierForMergedOutput(
|
|
|
|
|
|
|
|
|
|
|
|
const commitBytecodeHex = hexOrBinToHex(commitOutput.lockingBytecode);
|
|
|
|
const commitBytecodeHex = hexOrBinToHex(commitOutput.lockingBytecode);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Match merged binary bytecode back to the committed row that carried the identifier.
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
mergedBytecodeHex !== undefined &&
|
|
|
|
mergedBytecodeHex !== undefined &&
|
|
|
|
commitBytecodeHex !== undefined &&
|
|
|
|
commitBytecodeHex !== undefined &&
|
|
|
|
@@ -341,42 +314,41 @@ function findOutputIdentifierForMergedOutput(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fall back when the commit has a single identified output (common case).
|
|
|
|
const outputsWithIdentifier = outputs.filter(
|
|
|
|
const outputsWithIdentifier = outputs.filter(
|
|
|
|
(commitOutput) => commitOutput.outputIdentifier !== undefined,
|
|
|
|
(commitOutput) => commitOutput.outputIdentifier !== undefined,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (outputsWithIdentifier.length === 1) {
|
|
|
|
if (outputsWithIdentifier.length === 1) {
|
|
|
|
const soleIdentifiedOutput = outputsWithIdentifier[0];
|
|
|
|
return outputsWithIdentifier[0]?.outputIdentifier;
|
|
|
|
return soleIdentifiedOutput?.outputIdentifier;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Whether two invitation variable rows refer to the same template variable slot.
|
|
|
|
* Whether two invitation variable rows refer to the same template variable slot.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function matchesInvitationVariable(
|
|
|
|
export const matchesInvitationVariable = (
|
|
|
|
left: XOInvitationVariable,
|
|
|
|
left: XOInvitationVariable,
|
|
|
|
right: XOInvitationVariable,
|
|
|
|
right: XOInvitationVariable,
|
|
|
|
): boolean {
|
|
|
|
) =>
|
|
|
|
return (
|
|
|
|
left.variableIdentifier === right.variableIdentifier &&
|
|
|
|
left.variableIdentifier === right.variableIdentifier &&
|
|
|
|
left.roleIdentifier === right.roleIdentifier;
|
|
|
|
left.roleIdentifier === right.roleIdentifier
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Finds the entity that authored a merged variable by scanning invitation commits.
|
|
|
|
* Finds the entity that authored a merged variable by scanning invitation commits.
|
|
|
|
* Last matching commit in array order wins. Best-effort until the engine orders
|
|
|
|
* Last matching commit in array order wins. Best-effort until the engine orders
|
|
|
|
* commits internally or exposes source attribution on merged variables.
|
|
|
|
* commits internally or exposes source attribution on merged variables.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function findVariableEntityIdentifier(
|
|
|
|
export const findVariableEntityIdentifier = (
|
|
|
|
variable: XOInvitationVariable,
|
|
|
|
variable: XOInvitationVariable,
|
|
|
|
commits: XOInvitationCommit[],
|
|
|
|
commits: XOInvitationCommit[],
|
|
|
|
): string {
|
|
|
|
) => {
|
|
|
|
let entityIdentifier = "";
|
|
|
|
let entityIdentifier = "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Merged variables do not carry sourceCommitIdentifier today; walk commits and
|
|
|
|
|
|
|
|
// let the last array match win (ordering deferred to the engine merger).
|
|
|
|
for (const commit of commits) {
|
|
|
|
for (const commit of commits) {
|
|
|
|
for (const commitVariable of commit.data?.variables ?? []) {
|
|
|
|
for (const commitVariable of commit.data?.variables ?? []) {
|
|
|
|
if (matchesInvitationVariable(commitVariable, variable)) {
|
|
|
|
if (matchesInvitationVariable(commitVariable, variable)) {
|
|
|
|
@@ -386,7 +358,7 @@ function findVariableEntityIdentifier(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return entityIdentifier;
|
|
|
|
return entityIdentifier;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Returns template-enriched invitation data for UI display.
|
|
|
|
* Returns template-enriched invitation data for UI display.
|
|
|
|
@@ -395,20 +367,18 @@ function findVariableEntityIdentifier(
|
|
|
|
* extensions and transaction indices are resolved. Variables come from the merged
|
|
|
|
* extensions and transaction indices are resolved. Variables come from the merged
|
|
|
|
* result and are enriched with template metadata. Commit ordering is delegated to
|
|
|
|
* result and are enriched with template metadata. Commit ordering is delegated to
|
|
|
|
* the engine merger.
|
|
|
|
* the engine merger.
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param invitation - The raw invitation in standard XO format.
|
|
|
|
|
|
|
|
* @param template - The template referenced by the invitation.
|
|
|
|
|
|
|
|
* @returns Resolved invitation data ready for display.
|
|
|
|
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function resolveCommitReferences(
|
|
|
|
export const resolveCommitReferences = (
|
|
|
|
invitation: XOInvitation,
|
|
|
|
invitation: XOInvitation,
|
|
|
|
template: XOTemplate,
|
|
|
|
template: XOTemplate,
|
|
|
|
): ResolvedInvitationData {
|
|
|
|
): ResolvedInvitationData => {
|
|
|
|
const commits = invitation.commits ?? [];
|
|
|
|
const commits = invitation.commits ?? [];
|
|
|
|
const commitsMap = new Map(
|
|
|
|
const commitsMap = new Map(
|
|
|
|
commits.map((commit) => [commit.commitIdentifier, commit]),
|
|
|
|
commits.map((commit) => [commit.commitIdentifier, commit]),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Merge rather than flatten so mergesWith input extensions and transactionIndex
|
|
|
|
|
|
|
|
// ordering are handled by the engine (see signing flow in engine.append/sign).
|
|
|
|
const merged = mergeInvitationCommits(
|
|
|
|
const merged = mergeInvitationCommits(
|
|
|
|
invitation as Parameters<typeof mergeInvitationCommits>[0],
|
|
|
|
invitation as Parameters<typeof mergeInvitationCommits>[0],
|
|
|
|
template,
|
|
|
|
template,
|
|
|
|
@@ -434,8 +404,10 @@ export function resolveCommitReferences(
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const inputs = merged.inputs.map((mergedInput) => {
|
|
|
|
const inputs = merged.inputs.map((mergedInput) => {
|
|
|
|
const commit = commitsMap.get(mergedInput.sourceCommitIdentifier);
|
|
|
|
const entityIdentifier =
|
|
|
|
const entityIdentifier = commit?.entityIdentifier ?? "";
|
|
|
|
commitsMap.get(mergedInput.sourceCommitIdentifier)?.entityIdentifier ??
|
|
|
|
|
|
|
|
"";
|
|
|
|
|
|
|
|
// Strip merger-only fields before normalization and template enrichment.
|
|
|
|
const {
|
|
|
|
const {
|
|
|
|
sourceCommitIdentifier: _sourceCommitIdentifier,
|
|
|
|
sourceCommitIdentifier: _sourceCommitIdentifier,
|
|
|
|
mergesWith: _mergesWith,
|
|
|
|
mergesWith: _mergesWith,
|
|
|
|
@@ -457,7 +429,11 @@ export function resolveCommitReferences(
|
|
|
|
mergesWith: _mergesWith,
|
|
|
|
mergesWith: _mergesWith,
|
|
|
|
...output
|
|
|
|
...output
|
|
|
|
} = mergedOutput;
|
|
|
|
} = mergedOutput;
|
|
|
|
const outputIdentifier = findOutputIdentifierForMergedOutput(commit, output);
|
|
|
|
const outputIdentifier = findOutputIdentifierForMergedOutput(
|
|
|
|
|
|
|
|
commit,
|
|
|
|
|
|
|
|
output,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-attach outputIdentifier so pickTemplateOutputMetadata can resolve names/roles.
|
|
|
|
const outputForDisplay = normalizeMergedOutputForDisplay(
|
|
|
|
const outputForDisplay = normalizeMergedOutputForDisplay(
|
|
|
|
outputIdentifier !== undefined ? { ...output, outputIdentifier } : output,
|
|
|
|
outputIdentifier !== undefined ? { ...output, outputIdentifier } : output,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@@ -473,4 +449,4 @@ export function resolveCommitReferences(
|
|
|
|
inputs,
|
|
|
|
inputs,
|
|
|
|
outputs,
|
|
|
|
outputs,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|