UNPKG

@lifi/compose-spec

Version:

Public wire-format types and schemas for Compose flows

424 lines (416 loc) 11.5 kB
// src/flowSchema.ts import { Schema } from "effect"; var SolTypeSchema = Schema.Literal( "uint8", "uint16", "uint32", "uint64", "uint128", "uint256", "int128", "int256", "address", "bool", "bytes", "bytes4", "bytes32", "string" ); var ChainIdSchema = Schema.Number.pipe(Schema.int(), Schema.positive()); var ResourceSchema = Schema.Union( Schema.Struct({ kind: Schema.Literal("native"), chainId: ChainIdSchema }), Schema.Struct({ kind: Schema.Literal("erc20"), token: Schema.String, chainId: ChainIdSchema }) ); var ResourceInputSchema = Schema.Struct({ name: Schema.String, resource: ResourceSchema }); var HandleInputSchema = Schema.Struct({ name: Schema.String, type: SolTypeSchema }); var FlowInputSchema = Schema.Union( ResourceInputSchema, HandleInputSchema ); var 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"' }) ) }); var LiteralBindingSchema = Schema.Struct({ kind: SolTypeSchema, value: Schema.String }); var BindValueSchema = Schema.Union(RefSchema, LiteralBindingSchema); var AppliedGuardSchema = Schema.Struct( { kind: Schema.String }, { key: Schema.String, value: Schema.Unknown } ); var 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)) }); var ContinuationSchema = Schema.Struct({ awaits: Schema.String, flowId: Schema.String }); var FlowSchema = Schema.Struct({ version: Schema.Literal(1), id: Schema.String, chainId: ChainIdSchema, inputs: Schema.Array(FlowInputSchema), nodes: Schema.Array(CallSchema), continuation: Schema.optional(ContinuationSchema) }); // src/flow.ts var isResourceInput = (input) => "resource" in input; var flowInputType = (input) => isResourceInput(input) ? "uint256" : input.type; var flowInputMode = (input) => isResourceInput(input) ? "linear" : "copy"; var flowInputResource = (input) => isResourceInput(input) ? input.resource : void 0; var isResourcePort = (port) => port.kind === "resource"; var isHandlePort = (port) => port.kind === "handle"; var isResourceOutputPort = (port) => port.kind === "resource_output"; var isMaterialiserInput = (s) => typeof s === "object" && s !== null && "kind" in s; // src/resource.ts var erc20Resource = (token, chainId) => ({ kind: "erc20", token, chainId }); var nativeResource = (chainId) => ({ kind: "native", chainId }); var isERC20Resource = (r) => r.kind === "erc20"; var isNativeResource = (r) => r.kind === "native"; var resourcesEqual = (a, b) => { if (isNativeResource(a) && isNativeResource(b)) { return a.chainId === b.chainId; } if (isERC20Resource(a) && isERC20Resource(b)) { return a.chainId === b.chainId && a.token.toLowerCase() === b.token.toLowerCase(); } return false; }; var foldResource = (r, cases) => r.kind === "native" ? cases.native(r.chainId) : cases.erc20(r.token, r.chainId); var resourceKey = (r) => foldResource(r, { native: (chainId) => `native:${chainId}`, erc20: (token, chainId) => `erc20:${token.toLowerCase()}:${chainId}` }); var erc20Token = (r) => { if (r.kind !== "erc20") throw new Error(`Expected erc20 resource, got ${r.kind}`); return r.token; }; // src/flashloan.ts var PROVIDER_KIND = { AAVE_V3: 0, ERC3156: 1, BALANCER_V2: 2, MORPHO_BLUE: 3 }; var providerKindNames = [ "aave-v3", "erc3156", "balancer-v2", "morpho-blue" ]; var providerKindByName = { "aave-v3": PROVIDER_KIND.AAVE_V3, erc3156: PROVIDER_KIND.ERC3156, "balancer-v2": PROVIDER_KIND.BALANCER_V2, "morpho-blue": PROVIDER_KIND.MORPHO_BLUE }; // src/constructors.ts var inputRef = (port) => ({ $ref: `input.${port}` }); var outputRef = (node, port) => ({ $ref: `${node}.${port}` }); var optionalProp = (key, value) => value != null ? { [key]: value } : {}; var linear = (name, type) => ({ name, type, mode: "linear", availability: "now" }); var copy = (name, type) => ({ name, type, mode: "copy", availability: "now" }); var handle = (name, type) => ({ name, type }); var native = (name, chainId) => ({ name, resource: nativeResource(chainId) }); var erc20 = (name, token, chainId) => ({ name, resource: erc20Resource(token, chainId) }); var resource = (name, accepts, options) => ({ kind: "resource", name, accepts, mode: "linear", ...optionalProp("optional", options?.optional) }); var readResource = (name, accepts) => ({ kind: "resource", name, accepts, mode: "copy" }); var opHandle = (name, type, options) => ({ kind: "handle", name, type, mode: "copy", ...optionalProp("expose", options?.expose), ...optionalProp("units", options?.units) }); var resourceOutput = (name, options) => { const opts = typeof options === "string" ? { availability: options } : options; return { kind: "resource_output", name, mode: "linear", ...optionalProp("availability", opts?.availability), ...optionalProp("providesMinimum", opts?.providesMinimum), ...optionalProp("omitIfZero", opts?.omitIfZero), ...optionalProp("deliveryAddressInput", opts?.deliveryAddressInput) }; }; // src/ref.ts var isContextKey = (key) => key === "sender" || key === "executionAddress"; var RESERVED_REF_SCOPES = /* @__PURE__ */ new Set([ "input", "context", "literal" ]); var isRef = (v) => typeof v === "object" && v !== null && "$ref" in v && typeof v.$ref === "string"; var parseRef = (ref) => { const dot = ref.$ref.indexOf("."); if (dot === -1) throw new Error(`Invalid ref: "${ref.$ref}" (must contain a dot)`); const prefix = ref.$ref.slice(0, dot); const suffix = ref.$ref.slice(dot + 1); if (prefix === "input") return { scope: "input", port: suffix }; if (prefix === "context") { if (!isContextKey(suffix)) throw new Error(`Unknown context key: "${suffix}"`); return { scope: "context", key: suffix }; } return { scope: "output", node: prefix, port: suffix }; }; var foldRef = (ref, cases) => { if (ref.scope === "input") return cases.input(ref.port); if (ref.scope === "context") return cases.context(ref.key); return cases.output(ref.node, ref.port); }; var refKey = (ref) => foldRef(parseRef(ref), { input: (port) => `input:${port}`, context: (key) => `context:${key}`, output: (node, port) => `output:${node}:${port}` }); // src/materialiserMetadata.ts var foldMaterialiserMetadata = (meta, cases) => { if (meta.kind === "exact") return cases.exact(meta.amount); if (meta.kind === "exact-native") return cases.exactNative(meta.amount); return cases.runtime(); }; // src/zodSchemas.ts import z from "zod"; var SolTypeZod = z.enum([ "uint8", "uint16", "uint32", "uint64", "uint128", "uint256", "int128", "int256", "address", "bool", "bytes", "bytes4", "bytes32", "string" ]); var ResourcePortZod = z.object({ kind: z.literal("resource"), name: z.string(), accepts: z.enum(["erc20", "native", "any"]), mode: z.enum(["linear", "copy"]), optional: z.boolean().optional() }); var HANDLE_UNITS = [ "raw", "wad", "ray", "bps", "token-decimals" ]; var STATIC_SOL_TYPES = [ "uint256", "uint128", "uint64", "uint32", "uint16", "uint8", "address", "bool", "bytes32" ]; var HandleUnitsZod = z.enum(HANDLE_UNITS); var StaticSolTypeZod = z.enum(STATIC_SOL_TYPES); var STATIC_SOL_TYPE_SET = new Set(STATIC_SOL_TYPES); var isStaticSolType = (value) => STATIC_SOL_TYPE_SET.has(value); var HandlePortZod = z.object({ kind: z.literal("handle"), name: z.string(), type: SolTypeZod, mode: z.enum(["linear", "copy"]), expose: z.boolean().optional(), units: HandleUnitsZod.optional() }); var InputPortZod = z.discriminatedUnion("kind", [ ResourcePortZod, HandlePortZod ]); var ResourceOutputPortZod = z.object({ kind: z.literal("resource_output"), name: z.string(), mode: z.enum(["linear", "copy"]), availability: z.enum(["now", "future"]).optional(), providesMinimum: z.boolean().optional(), omitIfZero: z.boolean().optional(), deliveryAddressInput: z.string().optional() }); var OutputPortZod = z.discriminatedUnion("kind", [ ResourceOutputPortZod, HandlePortZod ]); var ManifestOperationZod = z.object({ id: z.string(), description: z.string().optional(), inputs: z.array(InputPortZod), outputs: z.array(OutputPortZod), configSchema: z.unknown().optional() }); var PortKindEnum = z.enum(["resource", "resource_output", "handle"]); var SelectorMatchZod = z.object({ kind: z.union([PortKindEnum, z.array(PortKindEnum)]), mode: z.enum(["linear", "copy"]).optional(), type: z.enum(["erc20", "native", "any"]).optional() }); var ConfigSelectionZod = z.object({ kind: z.literal("config"), configKey: z.string(), cardinality: z.enum(["one", "many"]) }); var AllMatchingSelectionZod = z.object({ kind: z.literal("all_matching") }); var SelectorSelectionZod = z.discriminatedUnion("kind", [ ConfigSelectionZod, AllMatchingSelectionZod ]); var GuardSelectorZod = z.object({ binding: z.string(), source: z.enum(["inputs", "outputs"]), match: SelectorMatchZod, selection: SelectorSelectionZod }); var GuardCompatibilityZod = z.object({ selectors: z.array(GuardSelectorZod) }); var ManifestGuardZod = z.object({ kind: z.string(), description: z.string().optional(), configSchema: z.unknown().optional(), compatibility: GuardCompatibilityZod.optional() }); var ManifestMaterialiserZod = z.object({ kind: z.string(), description: z.string().optional(), accepts: z.enum(["resource", "handle", "any"]), configSchema: z.unknown().optional() }); var ManifestPreconditionZod = z.object({ type: z.string(), description: z.string().optional(), configSchema: z.unknown().optional() }); var ComposeManifestZod = z.object({ manifestVersion: z.number(), manifestHash: z.string(), flowSchema: z.object({}).passthrough(), operations: z.array(ManifestOperationZod), guards: z.array(ManifestGuardZod), materialisers: z.array(ManifestMaterialiserZod), preconditions: z.array(ManifestPreconditionZod).optional() }); export { AppliedGuardSchema, BindValueSchema, CallSchema, ComposeManifestZod, ContinuationSchema, FlowInputSchema, FlowSchema, GuardCompatibilityZod, GuardSelectorZod, HANDLE_UNITS, HandleInputSchema, HandlePortZod, HandleUnitsZod, InputPortZod, LiteralBindingSchema, ManifestGuardZod, ManifestMaterialiserZod, ManifestOperationZod, ManifestPreconditionZod, OutputPortZod, PROVIDER_KIND, RESERVED_REF_SCOPES, RefSchema, ResourceInputSchema, ResourceOutputPortZod, ResourcePortZod, ResourceSchema, STATIC_SOL_TYPES, SolTypeSchema, StaticSolTypeZod, copy, erc20, erc20Resource, erc20Token, flowInputMode, flowInputResource, flowInputType, foldMaterialiserMetadata, foldRef, foldResource, handle, inputRef, isContextKey, isERC20Resource, isHandlePort, isMaterialiserInput, isNativeResource, isRef, isResourceInput, isResourceOutputPort, isResourcePort, isStaticSolType, linear, native, nativeResource, opHandle, outputRef, parseRef, providerKindByName, providerKindNames, readResource, refKey, resource, resourceKey, resourceOutput, resourcesEqual };