UNPKG

@lifi/compose-spec

Version:

Public wire-format types and schemas for Compose flows

140 lines (123 loc) 4.16 kB
import { Schema } from 'effect'; import { normalizeResource } from './chainCapabilities.js'; export const SolTypeSchema = Schema.Literal( 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'int128', 'int256', 'address', 'bool', 'bytes', 'bytes4', 'bytes32', 'string', ); // A positive integer chain id. Single source of truth on the Effect side // (`continuation.ts` imports it; `zodSchemas.ts` mirrors it as `chainIdZod`). export const ChainIdSchema = Schema.Number.pipe( Schema.int(), Schema.positive(), ); const RawResourceSchema = Schema.Union( Schema.Struct({ kind: Schema.Literal('native'), chainId: ChainIdSchema }), Schema.Struct({ kind: Schema.Literal('erc20'), token: Schema.String, chainId: ChainIdSchema, }), ); /** * THE chain-capabilities chokepoint. * * `ResourceSchema` is a decode transform: it parses the plain `native | erc20` * wire union (`RawResourceSchema`) and then runs `normalizeResource`, so on * `as-erc20` chains (e.g. Tempo) a `{ kind: "native", chainId }` resource is * rewritten to its ERC-20 form (PathUSD) before any application code sees it. * Because every Resource-bearing schema embeds this one — `FlowSchema` for flow * inputs, op `configSchema`s for op configs — making the schema itself * chain-aware covers every parsed entry point in one place. * * The `encode` direction is the identity: the ERC-20 form is itself valid wire * format, and the rewrite is deliberately not reversible (we never want to * re-introduce a `native` resource when serialising). `JSONSchema.make` derives * the published wire format from the encoded (`from`) side, so the manifest * keeps advertising the plain union with both arms. */ export const ResourceSchema = Schema.transform( RawResourceSchema, Schema.typeSchema(RawResourceSchema), { strict: true, decode: (raw) => normalizeResource(raw), encode: (resource) => resource, }, ); export const ResourceInputSchema = Schema.Struct({ name: Schema.String, resource: ResourceSchema, }); export const HandleInputSchema = Schema.Struct({ name: Schema.String, type: SolTypeSchema, }); export const FlowInputSchema = Schema.Union( ResourceInputSchema, HandleInputSchema, ); export const RefSchema = Schema.Struct({ $ref: Schema.String.pipe( Schema.filter((s) => s.indexOf('.') > 0, { message: () => '$ref must be a dotpath like "input.name" or "nodeId.port"', }), ), }); export const LiteralBindingSchema = Schema.Struct({ kind: SolTypeSchema, value: Schema.String, }); export const BindValueSchema = Schema.Union(RefSchema, LiteralBindingSchema); export const AppliedGuardSchema = Schema.Struct( { kind: Schema.String }, { key: Schema.String, value: Schema.Unknown }, ); export const CallSchema = Schema.Struct({ id: Schema.String, op: Schema.String, bind: Schema.optionalWith( Schema.Record({ key: Schema.String, value: BindValueSchema }), { default: () => ({}) }, ), config: Schema.optionalWith( Schema.Record({ key: Schema.String, value: Schema.Unknown }), { default: () => ({}) }, ), guards: Schema.optional(Schema.Array(AppliedGuardSchema)), }); export const ContinuationSchema = Schema.Struct({ awaits: Schema.String, flowId: Schema.String, }); export const FlowSchema = Schema.Struct({ version: Schema.Literal(1), id: Schema.String, chainId: ChainIdSchema, inputs: Schema.Array(FlowInputSchema), nodes: Schema.Array(CallSchema), continuations: Schema.optional(Schema.Array(ContinuationSchema)), }); export type SolType = typeof SolTypeSchema.Type; export type ResourceInput = typeof ResourceInputSchema.Type; export type HandleInput = typeof HandleInputSchema.Type; export type FlowInput = typeof FlowInputSchema.Type; export type Ref = typeof RefSchema.Type; export type LiteralBinding = typeof LiteralBindingSchema.Type; export type BindValue = typeof BindValueSchema.Type; export type AppliedGuard = typeof AppliedGuardSchema.Type; export type Call = typeof CallSchema.Type; export type Continuation = typeof ContinuationSchema.Type; export type Flow = typeof FlowSchema.Type;