feat: add balance-aware provider resale agent

This commit is contained in:
david
2026-08-27 09:54:53 +02:00
parent 8baf127c77
commit e54eb55f2b
41 changed files with 3579 additions and 38 deletions

View File

@@ -28,6 +28,8 @@ export interface PostAdvertParams {
walletKey: WalletKey;
advertDatum: AdvertDatum;
deposit_lovelace: bigint;
/** Durable journal hook invoked with signed CBOR before broadcast. */
beforeSubmit?: (built: PostAdvertBuildResult) => Promise<void>;
}
export async function buildPostAdvertTx(
@@ -75,6 +77,7 @@ export async function buildPostAdvertTx(
walletKey,
advertDatum,
deposit_lovelace,
beforeSubmit: params.beforeSubmit,
});
}
@@ -102,13 +105,15 @@ export async function buildPostAdvertTx(
const txCborHex = encodeTxBody(body);
const expectedTxHash = sha256Hex(txCborHex);
// Submit so the mock seeds the new UTxO + spends inputs (none here).
await chain.submitTx(txCborHex);
return {
const built: PostAdvertBuildResult = {
txCborHex,
expectedTxHash,
advertOutputRef: { txHash: expectedTxHash, index: 0 },
};
await params.beforeSubmit?.(built);
// Submit so the mock seeds the new UTxO + spends inputs (none here).
await chain.submitTx(txCborHex);
return built;
}

View File

@@ -26,6 +26,8 @@ export interface SubmitParams {
supplierKey: WalletKey;
escrowRef: OutputReference;
receiptHash: string;
/** Durable journal hook invoked with signed CBOR before broadcast. */
beforeSubmit?: (built: BuildResult) => Promise<void>;
}
function isHex32(s: string): boolean {
@@ -113,6 +115,7 @@ export async function buildSubmitTx(
deliverBy: datum.deliver_by,
tipMs,
receiptHash: receiptHash.toLowerCase(),
beforeSubmit: params.beforeSubmit,
});
}
@@ -138,6 +141,7 @@ export async function buildSubmitTx(
const txCborHex = encodeTxBody(body);
const expectedTxHash = sha256Hex(txCborHex);
await params.beforeSubmit?.({ txCborHex, expectedTxHash });
await chain.submitTx(txCborHex);
return { txCborHex, expectedTxHash };

View File

@@ -5,6 +5,10 @@
export type { ChatMessage, ToolCall, WalletKey, BuildResult, PostAdvertBuildResult, PostEscrowBuildResult } from "./types.js";
export { TxConstructionError } from "./types.js";
export { normalizeChatMessage, chatMessagesEquivalent, toolCallsEqual } from "./chatMessage.js";
export {
BOUNDED_INPUT_DETAIL_MARKER,
chatInputTokenUpperBound,
} from "./inputBound.js";
export { mockSlotToWallclockMs, mockWallclockMsToSlot, NETWORK_BUFFER_MS } from "./internal/constants.js";
export { detectCborBackend, type CborBackend } from "./internal/cborBackend.js";
export { escrowLockFloor, minAdaForEscrowDatum } from "./internal/minAdaFloor.js";

View File

@@ -0,0 +1,26 @@
import type { ChatMessage } from "./types.js";
import { canonicalize } from "../cbor/canonical.js";
/** Advert detail-URI marker for suppliers that require live input-cap preflight. */
export const BOUNDED_INPUT_DETAIL_MARKER = "#vector-bounded-input-v1";
const CHAT_MESSAGE_OVERHEAD = 8;
const CHAT_REPLY_PRIMER = 3;
const encoder = new TextEncoder();
/**
* Conservative, tokenizer-independent upper bound for text chat input.
*
* Every UTF-8 byte counts as one token unit. The fixed per-message and reply
* overhead covers role separators and the provider chat template. Major
* byte-fallback tokenizers cannot emit more content tokens than input bytes.
*/
export function chatInputTokenUpperBound(
messages: readonly ChatMessage[],
): number {
return (
CHAT_REPLY_PRIMER +
CHAT_MESSAGE_OVERHEAD * messages.length +
encoder.encode(canonicalize(messages)).byteLength
);
}

View File

@@ -101,6 +101,7 @@ export interface LiveSubmitParams {
deliverBy: number;
tipMs: number;
receiptHash: string;
beforeSubmit?: (built: BuildResult) => Promise<void>;
}
export interface LiveAcceptParams {
@@ -136,6 +137,7 @@ export interface LivePostAdvertParams {
walletKey: WalletKey;
advertDatum: AdvertDatum;
deposit_lovelace: bigint;
beforeSubmit?: (built: PostAdvertBuildResult) => Promise<void>;
}
export interface LiveRetireAdvertParams {
@@ -499,6 +501,7 @@ async function buildSpendOpenOrClaimedTx(opts: {
validFromMs: number;
validToMs: number;
signerPkh: string;
beforeSubmit?: (built: BuildResult) => Promise<void>;
}): Promise<BuildResult> {
const provider = buildLucidProvider(opts.chain);
const { lucid } = await createLucidContext(provider, opts.walletKey, {
@@ -585,6 +588,7 @@ async function buildSpendOpenOrClaimedTx(opts: {
const txCborHex = signed.toCBOR();
const expectedTxHash = signed.toHash();
await opts.beforeSubmit?.({ txCborHex, expectedTxHash });
await opts.chain.submitTx(txCborHex);
@@ -650,6 +654,7 @@ export async function buildLiveTxForSubmit(params: LiveSubmitParams): Promise<Bu
validFromMs: tipMs,
validToMs: stampMs,
signerPkh: params.supplierKey.pubKeyHash,
beforeSubmit: params.beforeSubmit,
});
}
@@ -910,14 +915,16 @@ export async function buildLiveTxForAdvert(
const txCborHex = signed.toCBOR();
const expectedTxHash = signed.toHash();
await chain.submitTx(txCborHex);
return {
const built: PostAdvertBuildResult = {
txCborHex,
expectedTxHash,
advertOutputRef: { txHash: expectedTxHash, index: 0 },
};
await params.beforeSubmit?.(built);
await chain.submitTx(txCborHex);
return built;
}
// ─── RetireAdvert ────────────────────────────────────────────────────