68 lines
4.1 KiB
TypeScript
Executable File
68 lines
4.1 KiB
TypeScript
Executable File
import { z } from 'zod';
|
|
import { NETWORK, type NftTransfer } from './testnet.ts';
|
|
import { ADDITIONAL_MARKET_FEE, type MarketInfo, type MarketListing, type MarketOrder } from './market.ts';
|
|
import { MAX_XMR_AMOUNT } from './xmr-amount.ts';
|
|
|
|
const id = z.string().uuid();
|
|
const hex = z.string().regex(/^[a-f0-9]{64}$/);
|
|
const atomicPrice = z.string().regex(/^(0|[1-9]\d{0,21})$/).refine(value => {
|
|
try {
|
|
const amount = BigInt(value);
|
|
return amount > 0n && amount <= MAX_XMR_AMOUNT;
|
|
} catch { return false; }
|
|
}, 'XMR price must be a positive bounded atomic amount');
|
|
const feeBps = z.number().int().min(0).max(5000);
|
|
|
|
export const marketActionV3Schema = z.discriminatedUnion('type', [
|
|
z.object({ type: z.literal('list'), cardHash: hex, sequence: z.number().int().nonnegative().safe(),
|
|
kind: z.enum(['fixed', 'auction']), priceAtomicXmr: atomicPrice, endsAt: z.number().int().nonnegative().nullable(), feeBps }).strict(),
|
|
z.object({ type: z.literal('bid'), listingId: id, priceAtomicXmr: atomicPrice }).strict(),
|
|
z.object({ type: z.literal('buy'), listingId: id, priceAtomicXmr: atomicPrice, feeBps }).strict(),
|
|
z.object({ type: z.literal('cancel'), listingId: id }).strict(),
|
|
z.object({ type: z.literal('retire-legacy-listing'), listingId: id }).strict(),
|
|
z.object({ type: z.literal('mock-pay'), orderId: id }).strict(),
|
|
z.object({ type: z.literal('refund'), orderId: id }).strict(),
|
|
z.object({ type: z.literal('deliver'), orderId: id, transfer: z.object({ network: z.literal(NETWORK),
|
|
chainId: hex, cardHash: hex, from: hex, to: hex, sequence: z.number().int().nonnegative().safe(),
|
|
publicKey: z.string().max(256), signature: z.string().max(128) }).strict() }).strict(),
|
|
z.object({ type: z.literal('activity') }).strict(),
|
|
]);
|
|
export type MarketActionV3 = z.infer<typeof marketActionV3Schema>;
|
|
export const marketCommandV3Schema = z.object({ version: z.literal(3), network: z.literal(NETWORK), chainId: hex,
|
|
marketId: id, actor: hex, publicKey: z.string().max(256), nonce: id, expiresAt: z.number().int().nonnegative().safe(),
|
|
action: marketActionV3Schema, signature: z.string().max(128) }).strict();
|
|
export type MarketCommandV3 = z.infer<typeof marketCommandV3Schema>;
|
|
|
|
/** New domain: price and currency are exact XMR atomic units, never fiat-converted. */
|
|
export function marketMessageV3(command: Omit<MarketCommandV3, 'signature'>): string {
|
|
const a = command.action;
|
|
const terms = a.type === 'list' ? [a.cardHash, a.sequence, a.kind, 'XMR', a.priceAtomicXmr, a.endsAt, a.feeBps]
|
|
: a.type === 'bid' ? [a.listingId, 'XMR', a.priceAtomicXmr]
|
|
: a.type === 'buy' ? [a.listingId, 'XMR', a.priceAtomicXmr, a.feeBps]
|
|
: a.type === 'cancel' || a.type === 'retire-legacy-listing' ? [a.listingId]
|
|
: a.type === 'activity' ? []
|
|
: a.type === 'deliver' ? [a.orderId, a.transfer.network, a.transfer.chainId, a.transfer.cardHash,
|
|
a.transfer.from, a.transfer.to, a.transfer.sequence, a.transfer.publicKey, a.transfer.signature]
|
|
: [a.orderId];
|
|
const feeTerms = a.type === 'list' || a.type === 'buy' ? [ADDITIONAL_MARKET_FEE.bps, ADDITIONAL_MARKET_FEE.address] : [];
|
|
return JSON.stringify([NETWORK, 'market-command-v3', command.version, command.chainId, command.marketId,
|
|
command.actor, command.publicKey, command.nonce, command.expiresAt, a.type, terms, feeTerms]);
|
|
}
|
|
|
|
export type MarketListingV3 = Omit<MarketListing, 'priceCents' | 'highestBid'> & {
|
|
termsVersion: 3; priceAtomicXmr: string;
|
|
highestBid: { bidder: string; priceAtomicXmr: string } | null;
|
|
};
|
|
export type MarketOrderV3 = Omit<MarketOrder, 'priceCents'> & { termsVersion: 3; priceAtomicXmr: string };
|
|
export type MarketListingRecord = MarketListing | MarketListingV3;
|
|
export type MarketOrderRecord = MarketOrder | MarketOrderV3;
|
|
export type MarketInfoV3 = MarketInfo & { marketVersion: 3 };
|
|
export type MarketActivityV3 = { listings: MarketListingRecord[]; orders: MarketOrderRecord[] };
|
|
|
|
export function isXmrListing(listing: MarketListingRecord): listing is MarketListingV3 {
|
|
return 'priceAtomicXmr' in listing;
|
|
}
|
|
export function isXmrOrder(order: MarketOrderRecord): order is MarketOrderV3 {
|
|
return 'priceAtomicXmr' in order;
|
|
}
|