45 lines
2.9 KiB
TypeScript
Executable File
45 lines
2.9 KiB
TypeScript
Executable File
import { z } from 'zod';
|
|
import { NETWORK } from './network.ts';
|
|
import { ADDITIONAL_MARKET_FEE } from './market.ts';
|
|
import { parseAtomicXmr } from './xmr-amount.ts';
|
|
|
|
/** REVIEW CANDIDATE ONLY. Never accepted by the active transactionSchema or custody HTTP API. */
|
|
export const SALE_CANDIDATE = 'custodial-sale-lock-candidate-v1';
|
|
const hex = z.string().regex(/^[a-f0-9]{64}$/);
|
|
const height = z.number().int().nonnegative().safe();
|
|
const amount = z.string().max(22).refine(s => { try { return parseAtomicXmr(s) > 0n; } catch { return false; } });
|
|
export const saleTermsSchema = z.object({ protocol: z.literal(SALE_CANDIDATE), network: z.literal(NETWORK),
|
|
chainId: hex, policyDigest: hex, custodianId: z.string().uuid(), orderId: z.string().uuid(),
|
|
cardHash: hex, sequence: height, seller: hex, buyer: hex, host: hex,
|
|
priceAtomicXmr: amount, feeBps: z.number().int().min(0).max(5000),
|
|
hostFeeAddress: z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{95}$/).optional(),
|
|
lockUntilHeight: height, expiresHeight: height }).strict();
|
|
export type SaleTerms = z.infer<typeof saleTermsSchema>;
|
|
export const saleSignatureSchema = z.object({ publicKey: z.string().min(1).max(256),
|
|
signature: z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/).length(88) }).strict();
|
|
const reference = { protocol: z.literal(SALE_CANDIDATE), chainId: hex, saleId: hex };
|
|
export const saleTransactionSchema = z.discriminatedUnion('type', [
|
|
z.object({ type: z.literal('sale-lock'), terms: saleTermsSchema,
|
|
sellerAuthorization: saleSignatureSchema, buyerAuthorization: saleSignatureSchema }).strict(),
|
|
z.object({ type: z.literal('sale-complete'), ...reference, reservationDigest: hex,
|
|
hostAuthorization: saleSignatureSchema }).strict(),
|
|
z.object({ type: z.literal('sale-cancel'), ...reference,
|
|
hostAuthorization: saleSignatureSchema, buyerAuthorization: saleSignatureSchema }).strict(),
|
|
z.object({ type: z.literal('sale-expire'), ...reference }).strict(),
|
|
]);
|
|
export type SaleTransaction = z.infer<typeof saleTransactionSchema>;
|
|
export function saleTermsMessage(t: SaleTerms): string {
|
|
const tuple: unknown[] = [SALE_CANDIDATE, 'terms', t.network, t.chainId, t.policyDigest,
|
|
t.custodianId, t.orderId, t.cardHash, t.sequence, t.seller, t.buyer, t.host,
|
|
'XMR', t.priceAtomicXmr, t.feeBps, ADDITIONAL_MARKET_FEE.bps, ADDITIONAL_MARKET_FEE.address,
|
|
t.lockUntilHeight, t.expiresHeight];
|
|
if (t.hostFeeAddress !== undefined) tuple.push(['host-fee-recipient-v1', t.hostFeeAddress]);
|
|
return JSON.stringify(tuple);
|
|
}
|
|
export function saleCompletionMessage(t: Pick<Extract<SaleTransaction, { type: 'sale-complete' }>, 'chainId' | 'saleId' | 'reservationDigest'>): string {
|
|
return JSON.stringify([SALE_CANDIDATE, 'complete-funded-reservation', t.chainId, t.saleId, t.reservationDigest]);
|
|
}
|
|
export function saleCancellationMessage(t: { chainId: string; saleId: string }): string {
|
|
return JSON.stringify([SALE_CANDIDATE, 'cancel', t.chainId, t.saleId]);
|
|
}
|