Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions src/trader/intent-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,18 +891,23 @@ export function createIntentEngine(deps: IntentEngineDeps): IntentEngine {
intents.set(intentId, draftRecord);

// Transition DRAFT → ACTIVE (validates transition)
// Build description for market posting
// Build description for market posting.
//
// The market-api is a semantic-search database: counterparties
// discover us by matching the description. We do NOT send a
// structured `price` field — the description embeds all the
// structured rate/volume info already (encodeDescription is the
// canonical TIP-0 form, parseDescription extracts the fields on
// the receiving side). Sending price separately is redundant AND
// hits an unhandled exception in the deployed market-api server
// (HTTP 500 with HTML body) — sphere-sdk#475 §6 surfaced this.
//
// category + currency are still sent because they're useful
// semantic tags for the search index (and the server accepts
// strings there without issue). The `price` field stays
// OPTIONAL in PostIntentRequest for callers with a separate
// numeric pricing flow — the trader doesn't.
const description = encodeDescription(tradingIntent);
// Bigint midpoint of the rate band, serialized to a decimal
// string for the wire. NEVER cast to Number — for a typical
// 18-decimal quote asset, the midpoint of a 0.08–0.12 band is
// 1e17 which is past Number.MAX_SAFE_INTEGER (≈9e15). Coercing
// loses precision, the market-api server rejects with HTTP 500,
// and the trader logs only an opaque status code. The SDK's
// PostIntentRequest.price field is `string` for exactly this
// reason (see sphere-sdk PR #483); pass `.toString()` through
// and the JSON serialization is correct end-to-end.
const midpointRate = (rateMin + rateMax) / 2n;
const expiresInDays = Math.max(
1,
Math.ceil((expiryMs - nowMs()) / 86_400_000),
Expand All @@ -914,7 +919,6 @@ export function createIntentEngine(deps: IntentEngineDeps): IntentEngine {
description,
intentType: params.direction,
category: `${params.base_asset}/${params.quote_asset}`,
price: midpointRate.toString(),
currency: params.quote_asset,
// Prefer @nametag for contactHandle — it resolves faster than DIRECT://
// which requires a binding event lookup that may not have propagated yet.
Expand Down
16 changes: 8 additions & 8 deletions src/trader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,15 @@ export interface MarketPostRequest {
readonly intentType: 'buy' | 'sell';
readonly category: string;
/**
* Price as a decimal-string bigint in the quote currency's smallest
* units. Same convention as token amounts in sphere-sdk (TXF amount
* fields, transfer payloads): internally bigint, over the wire
* decimal-string. Avoids JavaScript's `Number` precision loss for
* values above 2^53 (an 18-decimal coin hits that ceiling around
* 0.09 in human units). See sphere-sdk PR #483 for the SDK-side
* type that aligns with this.
* Optional structured price hint (decimal-string bigint in the
* quote currency's smallest units). The trader does NOT set this —
* the market-api is a semantic-search database, so we let the
* description (which encodes the full rate band per TIP-0) be the
* source of truth. `price` stays in the shape for callers that have
* a single-point pricing use case, but emitting it from the trader
* is both redundant and trips the deployed market-api server.
*/
readonly price: string;
readonly price?: string;
readonly currency: string;
readonly contactHandle: string;
readonly expiresInDays: number;
Expand Down
10 changes: 6 additions & 4 deletions test/e2e/trader-intent-lifecycle.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,12 @@ describe('E2E: Trader Intent Lifecycle (T1 + T10)', () => {
expect(postCall.category).toBe('ALPHA/USDC');
expect(postCall.currency).toBe('USDC');
expect(postCall.contactHandle).toBe(AGENT_ADDRESS);
// Midpoint of 450-500 = 475, serialized as decimal-string bigint
// per sphere-sdk PostIntentRequest.price convention (no Number
// cast — see trader/intent-engine.ts comment block).
expect(postCall.price).toBe('475');
// No structured `price` field — the trader leans on the
// semantic-search engine in the deployed market-api. The
// description (asserted below) already carries the rate band
// in TIP-0 form, so a structured price hint would be redundant
// (and trips the server). See intent-engine.ts comment block.
expect(postCall.price).toBeUndefined();
// Description must match canonical format
expect(postCall.description).toContain('Selling 100-1000 ALPHA for USDC');
expect(postCall.description).toContain('Rate: 450-500 USDC per ALPHA');
Expand Down
Loading