/* eslint-disable @stylistic/newline-per-chained-call */ import type { XOInvitation } from '@xo-cash/types'; import { z } from 'zod'; import { satoshisSchema, uint8ArraySchema, xoTemplateNftCapabilitySchema } from '../template/schemas.ts'; /** * Validation schema for a reference to an input or output in an earlier commit that a later item extends. * * ``` * { * "inputs|outputs": [ * { * "mergesWith": { "commitIdentifier": "...", "itemIndex": 0 } ← this schema * } * ] * } * ``` */ export const xoInvitationMergesWithReferenceSchema = z .object({ commitIdentifier: z.string().describe('The commit that holds the item being extended.'), itemIndex: z.number().int().nonnegative().describe('The position of the extended item inside that commit.'), }) .strict(); /** * Validation schema for the template action a commit is resolved against when it differs from the invitation root. */ export const xoInvitationActionReferenceSchema = z .object({ templateIdentifier: z.string().describe('The template that defines the action.'), actionIdentifier: z.string().describe('The action inside that template.'), }) .strict(); // ============================================================ // Commit data // ============================================================ /** * Validation schema for a variable value. Matches every value type an invitation variable can hold. */ export const xoInvitationVariableValueSchema = z .union([ z.bigint(), z.boolean(), z.number(), z.string() ]) .describe('A variable value expressed as a bigint, boolean, number, or string.'); /** * Validation schema for a variable supplied by a commit. */ export const xoInvitationVariableSchema = z .object({ variableIdentifier: z.string().describe('The template variable this value is for.'), value: xoInvitationVariableValueSchema, roleIdentifier: z.string().exactOptional().describe('The role this variable belongs to.'), mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), }) .strict(); /** * Validation schema for a cashtoken. */ export const outputTokenSchema = z .object({ category: uint8ArraySchema.describe('The token category.'), amount: z.bigint().describe('The fungible token amount.'), nft: z .object({ capability: xoTemplateNftCapabilitySchema, commitment: uint8ArraySchema.describe('The NFT commitment.'), }) .strict() .exactOptional(), }) .strict(); /** * Validation schema for a cashtoken supplied by a commit output. Every field is optional because a later commit may * supply the fields an earlier commit left unset. */ export const xoInvitationTokenSchema = z .object({ amount: z.bigint().exactOptional().describe('The fungible token amount.'), category: uint8ArraySchema.exactOptional().describe('The token category.'), nft: z .object({ capability: xoTemplateNftCapabilitySchema.exactOptional(), commitment: uint8ArraySchema.exactOptional().describe('The NFT commitment.'), }) .strict() .exactOptional(), }) .strict(); /** * Validation schema for a value one participant resolved from its own template key, such as a public key or a signature. */ export const xoInvitationResolutionSchema = z .object({ resolutionIdentifier: z.string().describe('The key operation this value resolves, such as someKey.public_key.'), value: uint8ArraySchema.describe('The resolved bytes.'), roleIdentifier: z.string().exactOptional().describe('The role whose key this value was resolved from.'), }) .strict(); /** * Validation schema for a transaction input supplied by a commit. */ export const xoInvitationInputSchema = z .object({ outpointIndex: z.number().int().nonnegative().exactOptional().describe('The index of the spent output in its transaction.'), outpointTransactionHash: uint8ArraySchema.exactOptional().describe('The hash of the transaction that holds the spent output.'), sequenceNumber: z.number().int().nonnegative().exactOptional().describe('The sequence number of this input.'), unlockingBytecode: uint8ArraySchema.exactOptional().describe('The script that unlocks the spent output.'), inputIdentifier: z.string().exactOptional().describe('The template input this input fills.'), transactionIndex: z.number().int().nonnegative().exactOptional().describe('The position this input must take in the final transaction.'), roleIdentifier: z.string().exactOptional().describe('The role this input belongs to.'), resolutions: z.array(xoInvitationResolutionSchema).exactOptional(), mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), valueSatoshis: satoshisSchema.exactOptional(), token: outputTokenSchema.exactOptional(), }) .strict(); /** * Validation schema for a transaction output supplied by a commit. */ export const xoInvitationOutputSchema = z .object({ lockingBytecode: uint8ArraySchema.exactOptional().describe('The locking bytecode of this output.'), valueSatoshis: satoshisSchema.exactOptional(), token: xoInvitationTokenSchema.exactOptional(), outputIdentifier: z.string().exactOptional().describe('The template output this output fills.'), transactionIndex: z.number().int().nonnegative().exactOptional().describe('The position this output must take in the final transaction.'), secretIdentifier: z.string().exactOptional().describe('The secret used to derive this output.'), roleIdentifier: z.string().exactOptional().describe('The role this output belongs to.'), mergesWith: xoInvitationMergesWithReferenceSchema.exactOptional(), }) .strict(); /** * Validation schema for the data a commit supplies. */ export const xoInvitationCommitDataSchema = z .object({ transactionVersion: z.number().int().nonnegative().exactOptional().describe('The transaction version.'), transactionLocktime: z.number().int().nonnegative().exactOptional().describe('The transaction locktime.'), variables: z.array(xoInvitationVariableSchema).exactOptional(), inputs: z.array(xoInvitationInputSchema).exactOptional(), outputs: z.array(xoInvitationOutputSchema).exactOptional(), resolutions: z.array(xoInvitationResolutionSchema).exactOptional(), roleIdentifier: z.string().exactOptional().describe('The role the committing entity takes in the action with this commit.'), }) .strict(); /** * Validation schema for one signed commit in an invitation. */ export const xoInvitationCommitSchema = z .object({ commitIdentifier: z.string().describe('The identifier of this commit.'), // The type declares this key on every commit, but serialization drops it on the first commit where it is undefined. // Prefault restores the missing key, in this case it's undefined. previousCommitIdentifier: z.string().optional().prefault(undefined).describe('The commit this one follows. Undefined on the first commit.'), entityIdentifier: z.string().describe('The entity that made this commit.'), createdAtTimestamp: z.number().int().nonnegative().describe('When this commit was created, as a Unix timestamp in milliseconds.'), actionReference: xoInvitationActionReferenceSchema.exactOptional(), data: xoInvitationCommitDataSchema, signature: z.string().describe('The signature over this commit.'), expiresAtTimestamp: z.number().int().nonnegative().describe('When this commit expires, as a Unix timestamp in milliseconds.'), memo: z.string().exactOptional().describe('A free text note attached to this commit.'), }) .strict(); // ============================================================ // Results // ============================================================ /** * Validation schema for a transaction result that has been created but not broadcast. */ export const xoInvitationTransactionCreatedResultSchema = z .object({ transactionHash: z.string().describe('The hash of the created transaction.'), createdAtTimestamp: z.number().int().nonnegative().describe('When the transaction was created, as a Unix timestamp in milliseconds.'), createdByEntityIdentifier: z.string().describe('The entity that created the transaction.'), createdBySignature: z.string().describe('The signature over the created result.'), }) .strict(); /** * Validation schema for a transaction result that has been created and broadcast. */ export const xoInvitationTransactionBroadcastedResultSchema = xoInvitationTransactionCreatedResultSchema .extend({ broadcastedAtTimestamp: z.number().int().nonnegative().describe('When the transaction was broadcasted, as a Unix timestamp in milliseconds.'), broadcastedByEntityIdentifier: z.string().describe('The entity that broadcasted the transaction.'), broadcastedBySignature: z.string().describe('The signature over the broadcasted result.'), }) .strict(); /** * Validation schema for a data result created on an invitation. */ export const xoInvitationCreatedResultSchema = z .object({ value: xoInvitationVariableValueSchema, createdAtTimestamp: z.number().int().nonnegative().describe('When the value was created, as a Unix timestamp in milliseconds.'), createdByEntityIdentifier: z.string().describe('The entity that created the value.'), createdBySignature: z.string().describe('The signature over the created value.'), }) .strict(); /** * Validation schema for the results created on an invitation. */ export const xoInvitationResultSchema = z .object({ transactions: z .record(z.string(), z.union([ xoInvitationTransactionBroadcastedResultSchema, xoInvitationTransactionCreatedResultSchema ])) .exactOptional(), data: z.record(z.string(), xoInvitationCreatedResultSchema).exactOptional(), }) .strict(); // ============================================================ // Invitation // ============================================================ /** * Validation schema for an invitation. */ export const xoInvitationSchema: z.ZodType = z .object({ invitationIdentifier: z.string().describe('The identifier of this invitation.'), createdAtTimestamp: z.number().int().nonnegative().describe('When this invitation was created, as a Unix timestamp in milliseconds.'), templateIdentifier: z.string().describe('The template this invitation uses.'), actionIdentifier: z.string().describe('The template action this invitation performs.'), commits: z.array(xoInvitationCommitSchema), result: xoInvitationResultSchema.exactOptional(), }) .strict();