https://github.com/aibtcdev/tx-schemas
Shared transaction schemas, state models, and response types for AIBTC services.
https://github.com/aibtcdev/tx-schemas
Last synced: 11 days ago
JSON representation
Shared transaction schemas, state models, and response types for AIBTC services.
- Host: GitHub
- URL: https://github.com/aibtcdev/tx-schemas
- Owner: aibtcdev
- License: mit
- Created: 2026-03-30T22:52:00.000Z (27 days ago)
- Default Branch: main
- Last Pushed: 2026-04-10T00:42:21.000Z (17 days ago)
- Last Synced: 2026-04-10T01:24:22.304Z (17 days ago)
- Language: TypeScript
- Size: 167 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @aibtc/tx-schemas
Shared zod schemas for AIBTC payment state models, first-party relay RPC schemas,
and external x402 HTTP schemas.
## Install
```bash
npm install @aibtc/tx-schemas zod
```
## Package Shape
- `@aibtc/tx-schemas` exports the full surface
- `@aibtc/tx-schemas/core` exports canonical payment enums, terminal reasons, and shared primitives
- `@aibtc/tx-schemas/rpc` exports internal relay service-binding schemas
- `@aibtc/tx-schemas/http` exports external x402 facilitator and polling schemas
- `@aibtc/tx-schemas/news` exports shared editorial/newsroom schemas, including beat lifecycle transition contracts
## Usage
```ts
import { PaymentStateSchema, PaymentStateCategoryByState } from "@aibtc/tx-schemas/core";
import { RpcSubmitPaymentResultSchema } from "@aibtc/tx-schemas/rpc";
import { HttpSettleRequestSchema } from "@aibtc/tx-schemas/http";
import { TERMINAL_REASONS } from "@aibtc/tx-schemas/terminal-reasons";
const state = PaymentStateSchema.parse("confirmed");
const category = PaymentStateCategoryByState[state];
const terminalReasons = TERMINAL_REASONS;
const rpcResult = RpcSubmitPaymentResultSchema.parse({
accepted: true,
paymentId: "pay_01J7QZXK5XRGBVMK3N9RTNF4WW",
status: "queued",
});
const settleRequest = HttpSettleRequestSchema.parse({
paymentPayload: {
x402Version: 2,
payload: { transaction: "0x1234" },
},
paymentRequirements: {
scheme: "exact",
network: "stacks:2147483648",
amount: "1000000",
asset: "STX",
payTo: "ST000000000000000000002AMW42H",
},
});
```
## Subpath Imports
- `@aibtc/tx-schemas`
- `@aibtc/tx-schemas/core`
- `@aibtc/tx-schemas/core/enums`
- `@aibtc/tx-schemas/core/schemas`
- `@aibtc/tx-schemas/core/primitives`
- `@aibtc/tx-schemas/terminal-reasons`
- `@aibtc/tx-schemas/core/terminal-reasons`
- `@aibtc/tx-schemas/rpc`
- `@aibtc/tx-schemas/http`
- `@aibtc/tx-schemas/news`
## Schema Rules
- Canonical payment states live in `core` and are the only shared payment-state source of truth.
- Public canonical states are `requires_payment`, `queued`, `broadcasting`, `mempool`, `confirmed`, `failed`, `replaced`, and `not_found`.
- `submitted` is reserved for relay internals and must not appear in the caller-facing contract.
- `rpc` and `http` may differ in field names and transport ergonomics, but they must reuse the same state semantics.
- The default protected-resource delivery invariant is `deliver-only-on-confirmed`.
- Any product that delivers on in-flight states should document that as an application exception, not a canonical package rule.
- `paymentId` is relay-owned and duplicate submission should reuse the same `paymentId` until terminal resolution.
- `payment-identifier` is client-supplied idempotency input only. It must not be treated as canonical public `paymentId`.
- Accepted duplicate submit responses should return the current caller-facing in-flight status for that `paymentId`: `queued`, `broadcasting`, or `mempool` as applicable.
- `queued_with_warning` remains an RPC-only temporary compatibility shim for warning-aware callers during migration.
- Polling contracts may surface `checkStatusUrl` as an additive convenience field, and internal/external polling should treat it as another way to reach the same `paymentId` lifecycle.
- If a downstream consumer has neither relay `paymentId` nor canonical `checkStatusUrl`, it must fail closed instead of inventing a polling identity.
- Terminal polling responses should carry a normalized `terminalReason` when one is known, even if transports also emit local error codes.
- Machine-readable contract exports for downstream repos include `CanonicalDomainBoundary`, `CANONICAL_POLLING_IDENTITY_FIELDS`, `RELAY_LIFECYCLE_BRIDGE`, and `TERMINAL_REASON_CATEGORY_HANDLING`.
More detail lives in [docs/package-schemas.md](docs/package-schemas.md),
[docs/boring-state-machine-contract.md](docs/boring-state-machine-contract.md),
[docs/x402-approval-spec.md](docs/x402-approval-spec.md), and
[docs/x402-state-machines.md](docs/x402-state-machines.md).
## Consuming sponsor wallet helpers
The `core` export ships pure state-machine helpers so that every service that
touches a sponsor wallet (`/sponsor`, `/relay`, `/settle`, …) drives off the
same primitives instead of re-implementing the nonce-conflict logic inline.
The canonical cycle is: classify what is at the nonce, decide what to do,
apply the decision to state, reconcile periodically against the mempool.
```ts
import {
classifyOccupant,
decideBroadcast,
adoptOrphan,
quarantine,
reconcile,
type HiroSponsorTxView,
type SponsorLedger,
type WalletCapacity,
} from "@aibtc/tx-schemas/core";
function handleConflict(
wallet: WalletCapacity,
ledger: SponsorLedger,
nonce: number,
mempoolHit: HiroSponsorTxView | null,
sponsorAddress: string
) {
const occupant = classifyOccupant(mempoolHit, sponsorAddress, ledger, nonce);
const decision = decideBroadcast(
wallet,
{ outcome: "nonce_conflict", isOrigin: false },
{ nonce, ledger, occupant }
);
switch (decision.kind) {
case "rbf_with_fee":
// re-sign at decision.fee, broadcast
return wallet;
case "adopt_then_rbf":
// `adopt_then_rbf` is only emitted when the occupant was classified as
// `sponsor_owned_orphan`, which requires a non-null mempool hit.
return adoptOrphan(wallet, mempoolHit!);
case "quarantine":
return quarantine(wallet, decision.nonce, decision.reason, {
txId: occupant.kind !== "untraceable" ? occupant.txId : undefined,
});
case "terminal":
// mark the payment terminal with decision.reason
return wallet;
case "first_broadcast":
return wallet;
}
}
// Periodic reconciliation pass adopts unrecorded sponsor txs and flags drops:
const { wallet: next, ledger: nextLedger, adopted, dropped } = reconcile(
wallet,
ledger,
mempoolReadByNonce,
sponsorAddress
);
```
All helpers are pure: inputs → new state. No I/O, and time-sensitive helpers
accept an injectable `now` option for deterministic tests; otherwise they use
the current time by default.