Wallet/Application Integration Example (TypeScript)¶
This example uses the wallet/runtime façade exports and keeps protocol authority distinct from optional supplemental economics support.
External adopters should import from package roots only:
@bch-stealth/direct-spend-wallet and @bch-stealth/prover-runtime.
import {
assembleDirectSpendTx,
buildDirectSpendPlan,
recoverDirectSpendOutputs,
type DirectSpendWalletRuntimeExecutorV1,
} from "@bch-stealth/direct-spend-wallet";
import {
prepareDirectSpendProofInput,
proveDirectSpend,
verifyDirectSpend,
} from "@bch-stealth/prover-runtime";
type ProtocolAuthorityInput = {
outpointRef: string; // authoritative continuation source (txid:vout)
valueSats: number;
};
type SupplementalInput = {
outpointRef: string;
valueSats: number;
} | null;
function runtimeExecutorFromProof(
proofInput: ReturnType<typeof prepareDirectSpendProofInput>,
proved: Awaited<ReturnType<typeof proveDirectSpend>>,
verify: Awaited<ReturnType<typeof verifyDirectSpend>>,
): DirectSpendWalletRuntimeExecutorV1 {
return async () => ({
request: proofInput,
estimate: {
backendId: proved.prove.backendId,
estimatedProofBytes: proved.prove.estimatedProofBytes,
estimatedProofBlobBytes: proved.prove.pbv1Bytes.length,
},
prove: proved.prove,
verify,
});
}
export async function runDirectSpendHop(args: {
piv1Bytes: Uint8Array;
witnessBytes: Uint8Array;
encryptedPayloadBytes?: Uint8Array | null;
hintsBytes?: Uint8Array | null;
protocolAuthority: ProtocolAuthorityInput;
supplemental: SupplementalInput;
}) {
const proofInput = prepareDirectSpendProofInput({
piv1Bytes: args.piv1Bytes,
witnessBytes: args.witnessBytes,
encryptedPayloadBytes: args.encryptedPayloadBytes ?? null,
hintsBytes: args.hintsBytes ?? null,
determinism: { mode: "deterministic" },
});
const proved = await proveDirectSpend(proofInput);
const verify = await verifyDirectSpend(proved);
if (!verify.ok) throw new Error(verify.reason ?? "verifyDirectSpend failed");
const plan = buildDirectSpendPlan({
request: proofInput,
runtimeExecutor: runtimeExecutorFromProof(proofInput, proved, verify),
hostChecks: {
transcriptBinding: async () => undefined,
ofm2v2: async () => undefined,
continuationShell: async () => undefined,
malformedCarrier: async () => undefined,
routingCompatibility: async () => undefined,
},
assembleTx: async (run) => {
// Protocol authority input is mandatory.
// Supplemental input is optional economics support only.
return {
authority: args.protocolAuthority,
supplemental: args.supplemental,
proofBytes: run.prove.proofBytes,
pbv1Bytes: run.prove.pbv1Bytes,
};
},
broadcastTx: async (txPlan) => {
// Replace with your tx builder + BCH broadcast implementation.
return {
txidHex: "replace-with-broadcast-txid",
rawTxHex: buildRawTxHexFromPlan(txPlan),
};
},
persistState: async (broadcastResult) => {
// Persist latest continuation outpoint + sender state.
await persistContinuationState(broadcastResult.txidHex);
},
});
const assembled = await assembleDirectSpendTx(plan);
const recovery = recoverDirectSpendOutputs({
identity: {
scanKeyAHex: "11".repeat(32),
spendKeyBHex: "22".repeat(32),
},
currentTipHeight: 0,
chainCandidates: [],
});
return { assembled, recovery };
}
function buildRawTxHexFromPlan(_plan: unknown): string {
throw new Error("Provide wallet-specific tx assembly/signing implementation");
}
async function persistContinuationState(_txidHex: string): Promise<void> {
// Implement wallet state persistence.
}
Required Preservation Rules¶
- The authoritative protocol source is the continuation input, not supplemental funding.
- Supplemental transparent input (if used) must be explicitly classified as economics support.
- Runtime proof/PBV1 bytes must remain identical through verify, carrier packing, and broadcast.
- Host checks should fail closed before broadcast if boundary mismatches are detected.
packages/direct-spend-wallet/src/*andstandalone/prover-runtime/src/*are internal implementation paths, not stable external integration entrypoints.