All notable changes to this project will be documented in this file.
First SDK slice of Phase 5.2: agent frameworks call Hashlock's instant-settlement engine through the SDK ("single engine, two adapters" — this is the SDK adapter leg). All backend surfaces are live behind a feature flag; the SDK degrades gracefully when the flag is off.
- Types:
InstantFill(state unioncommitted | fronted | settled | reimbursed | cancelled;amountWeiis a decimal string over the full uint256 range — never converted tonumber),AgentPolicy(maxLatencyMs/maxFeeBps/minTrust),TrustLevel,InstantTakerResult,InstantFillFallbackReason,InstantFillOrphanedError.Quote.instantFill/Quote.solverVaultAddrresponse fields. - Maker:
submitQuoteacceptsinstantFill+solverVaultAddr. - Taker:
requestInstantFill(rfqId, quoteId);acceptQuote(quoteId, policy?)— policy is an optional PREFERENCE that can never fail the accept (SDK sanitizes invalid policies down to the standard path); flow helperrequestInstantFillAndAccept(rfqId, quoteId, { policy })enforcing the canonical order (fill succeeds → accept) with typed fallbacks:INSTANT_FILL_DISABLED/ lane conflict (extensions.lane) / already-requested → automatic standardacceptQuote; fill OK + accept FAIL →fill_orphanedresult carryingInstantFillOrphanedError, recoverable via the accept-onlyretryAcceptAfterInstantFill(fill)(never re-requests the fill — exactly-once per quote). Unexpected errors are thrown, never swallowed into fallback. - Solver:
markInstantFillFronted(fillId, frontTxHash);onInstantFillRequested(cb)(solver-scoped subscription);serveInstantFills(front, opts)= subscribe + auto-mark-fronted. - Taker notifications:
onInstantFillFronted(cb)(taker-scoped). - Subscriptions: minimal
graphql-transport-wsclient transport (zero new runtime dependencies). GlobalWebSocket(browsers, Node >= 22) used automatically;HashLockConfig.webSocket/wsEndpointfor Node 18/20 (wspackage) or custom endpoints. - Policy presets:
policyPresets.instant(maxLatencyMs: 3000) /.balanced(minTrust: 'med') /.trustless(minTrust: 'max') — 1:1 with the design §13.1 human-slider table. GraphQLError.errors[]entries now preserve the server'sextensions(code / flattened metadata) for typed classification (classifyInstantFillError).- 35 new tests (taker decision table, error classification, policy sanitization, ws handshake/subscription lifecycle, solver serve loop).
The initial instant surface was written from the design brief instead of
the real SDL; verification against the main repo's origin/main schema
found two confirmed drifts, fixed here BEFORE any release:
- Subscription payload types —
instantFillRequested/instantFillFronteddeliver dedicated event types, NOTInstantFill. The SDK previously selectedid … frontTxHash frontedAt createdAton both, which the server rejects (Cannot query field "id" on type "InstantFillRequested"). Now:InstantFillRequestedEvent(fillId quoteId rfqId state amountWei createdAt) andInstantFillFrontedEvent(fillId quoteId rfqId tradeId state amountWei frontTxHash frontedAt), with matching selection constantsINSTANT_FILL_REQUESTED_FIELDS/INSTANT_FILL_FRONTED_FIELDS.onInstantFillRequested/onInstantFillFronted/serveInstantFillscallbacks are typed accordingly, andserveInstantFillsnow fronts viaevent.fillId(the old code readfill.id, which does not exist on the event payload). minTrustwire type — the schema'sAgentPolicyInput.minTrustisInt(0-100 trust score); the SDK was sending theTrustLevelstring raw, which fails GraphQL Int coercion and rejects the ENTIREacceptQuote(violating the "a policy can never fail the accept" guarantee).sanitizeAgentPolicynow returns the wire shape (AgentPolicyWire, all fields integers):TrustLevelmaps viaTRUST_LEVEL_TO_SCORE(low→0, med→50, max→100 — med passes the backend's 50 reputation stub since the guard isminTrust > score; max steers trustless); raw 0-100 numbers are accepted, floored and clamped;maxFeeBpsclamps to 0-10000 andmaxLatencyMsto the 32-bit Int range for coercion safety.classifyInstantFillErrorwire shape — verified against the backend pipeline (trade-servicemaskTradeError+ gateway formatter):HashlockErrorserializes asextensions: { ...metadata, code, retryable }— metadata FLATTENED, HTTP status NEVER serialized. The classifier now reads the lane fromextensions.lane(withextensions.metadata.laneas a defensive fallback) and detects already-requested viacode === 'INVALID_STATE_TRANSITION'+ the "already requested" message; the deadextensions.status/http.status/CONFLICTpaths were removed. OtherINVALID_STATE_TRANSITIONerrors (quote no longer firm / expired) classify asnulland are rethrown.
test/fixtures/schema.graphql+schema.subscriptions.graphql: vendored authoritative SDL from the main repo (source path, git ref and commit SHA recorded in the fixture headers). Refresh withnode scripts/vendor-schema.mjs <main-repo-path>.src/__tests__/schema-validate.test.ts: every operation string the SDK sends (captured from the real code paths, including the ws subscribe frames) isvalidate()d against the vendored SDL using thegraphqlpackage (devDependency ONLY — runtime stays zero-dependency).- Known pre-existing legacy drift documented (skipped test, not fixed
to avoid breaking the v0.1.x public shape):
getHTLCStatusselectsinitiatorHTLC/counterpartyHTLC, but the schema'sHTLCStatusResultis flat — usegetHTLCs(tradeId)instead.
- Backward compatible / additive.
acceptQuotewithout a policy sends the exact legacy operation; plain quotes are untouched; no existing export changed shape. - Not yet published to npm (operator decision pending). Publish
with
npm publish --access publicafter operator sign-off.
- Cross-chain
createRFQ—baseChain/quoteChaininputs (RFQChainId) so cross-chain RFQs resolve(symbol, chain)composite tokens unambiguously. (Entry backfilled in 0.3.0; see git tag/commitbb2771ffor details.)
- Experimental field warning — when a caller sets any of the
experimental agent-layer fields (
attestation,agentInstance,minCounterpartyTier,hideIdentity) oncreateRFQ,submitQuote, orfundHTLC, the SDK now emits a one-timeconsole.warnper field/method pair explaining that GraphQL wire-through to the Cayman backend is not yet implemented and the field is currently a no-op at the network layer. - Warning can be suppressed with
HASHLOCK_SDK_SILENCE_EXPERIMENTAL=1for call sites that have already acknowledged the experimental status. - 2 new tests verifying the warning fires when experimental fields are set and stays silent when they are not.
The v0.1.3 release exposed experimental type surface for agent
flows but dropped the fields silently at the network layer — a
DX hazard, because a caller could set attestation and assume
it reached the backend. This warning closes the expectation gap.
- Principal + attestation type surface (experimental) — agents operating
under a KYC'd principal can now attach structured metadata to RFQs,
quotes, and HTLC funding calls:
PrincipalAttestationinterface: opaque binding from an order to a KYC'd entity (principalId, principalType, tier, blindId, issuedAt, expiresAt, proof) without leaking identity to counterpartiesAgentInstanceinterface: autonomous agent instance metadata (instanceId, strategy, version, spawnedAt)KycTierenum withNONE < BASIC < STANDARD < ENHANCED < INSTITUTIONALordering plusKYC_TIER_RANKandmeetsKycTier()helper- New optional response fields on
RFQ(attestationTier,attestationBlindId,minCounterpartyTier),Quote(attestationTier,attestationBlindId), andTrade(initiatorAttestationTier,counterpartyAttestationTier) - New optional input fields on
CreateRFQInput(attestation,agentInstance,minCounterpartyTier,hideIdentity),SubmitQuoteInput(attestation,agentInstance,hideIdentity), andFundHTLCInput(attestation,agentInstance)
- 6 new tests covering the agent principal layer and backward compat
- Widen
GraphQLClient.execute()variables parameter to acceptRecord<string, unknown> | object | undefinedso typed SDK inputs (e.g.,CreateRFQInput) satisfy the signature under strict TypeScript. This fix was a pre-existing CI lint failure from the v0.1.1 and v0.1.2 publish commits; v0.1.3 is the first version whose CI lint step is green.
- Backward compatible. All new fields are optional. Existing human OTC flows without attestation continue to work unchanged.
- EXPERIMENTAL. The new agent-layer fields are accepted at the SDK
type surface today but are not yet sent to the Cayman GraphQL
backend. Wire-through will land in a later release once the backend
schema accepts
PrincipalAttestationInputandAgentInstanceInput. Passing these fields today is a no-op at the network layer.
- Use
/api/graphqlendpoint to bypass CSRF protection.
- HTLC query uses
chainIdinstead ofchainTypeto match backend schema.
- Initial release
- RFQ trading: createRFQ, submitQuote, acceptQuote, listRFQs, cancelRFQ
- Trade management: getTrade, listTrades, acceptTrade, cancelTrade, confirmDirectTrade
- EVM HTLC: fundHTLC, claimHTLC, refundHTLC, getHTLCStatus
- Bitcoin HTLC: prepareBitcoinHTLC, buildBitcoinClaimPSBT, broadcastBitcoinTx
- Settlement wallet management: confirmSettlementWallets
- Full TypeScript types for all inputs and outputs
- Automatic retry with exponential backoff
- Error hierarchy: HashLockError, GraphQLError, NetworkError, AuthError
- ESM + CJS dual build