Relaycode

Input Map

Type resolution system for mapping Substrate types to UI components.

Input Map API

The input map module (lib/input-map.ts) provides a type resolution system that maps Substrate types to appropriate UI input components. It uses a priority-based pattern matching system to find the best component for each type.

Functions

findComponent()

Find the appropriate input component for a Substrate type.

function findComponent(
  typeName: string,
  typeId?: number
): ParamComponentType & { typeId?: number }

Parameters:

  • typeName - The type name from metadata (e.g., "AccountId", "Balance", "Vec<u8>")
  • typeId - Optional type ID for complex types that need registry lookup

Returns: Object containing:

  • component - The React component to render
  • schema - Zod validation schema for the type
  • typeId - The passed-through typeId (if provided)

Example:

import { findComponent } from "@/lib/input-map";

// Simple type lookup
const { component: AccountComponent } = findComponent("AccountId");
// Returns: Account component

// Complex type with typeId
const { component: VectorComponent, typeId } = findComponent("Vec<Balance>", 123);
// Returns: Vector component with typeId for nested type resolution

// Unknown type falls back to Text
const { component: TextComponent } = findComponent("SomeUnknownType");
// Returns: Text component

Type Resolution

The system uses a priority-based registry to match types. Higher priority patterns are checked first.

Priority Order

PriorityComponentPatterns
100AccountAccountId, AccountId32, AccountId20, MultiAddress, Address, LookupSource, /^AccountId/, /^MultiAddress/
95BalanceBalance, BalanceOf, Compact<Balance>, Compact<BalanceOf>, /^Balance/
90AmountCompact<u128>, Compact<u64>, u128, u64, u32, u16, u8, i128, i64, i32, i16, i8, /Compact</
85Booleanbool
82Hash160H160, /^H160$/
80Hash256H256, Hash, /H256/, /Hash/
78Hash512H512, /^H512$/
75BytesBytes, Vec<u8>, /Bytes/
70CallCall, RuntimeCall, /Call$/, /RuntimeCall>/
65MomentMoment, /Moment/
60VoteVote, /^Vote$/
55VoteThresholdVoteThreshold, /VoteThreshold/
50KeyValueKeyValue, /KeyValue/
45Option/^Option</
43VectorFixed/^\[.+;\s*\d+\]$/ (e.g., [u8; 32])
42BTreeMap/^BTreeMap</
41BTreeSet/^BTreeSet</
40Vector/^Vec</, /^BoundedVec</
38Tuple/^\(/, /^Tuple/
35Struct(no patterns - fallback for composite types)
30Enum(no patterns - fallback for enum types)
Text(default fallback for unknown types)

Pattern Matching

Patterns can be:

  • Exact strings: "AccountId" matches only "AccountId"
  • Regular expressions: /^AccountId/ matches "AccountId", "AccountIdOf", etc.

The system checks patterns in priority order, returning the first match.

Example matches:

findComponent("AccountId");              // -> Account (exact match, priority 100)
findComponent("AccountIdOf<T>");         // -> Account (regex match, priority 100)
findComponent("Balance");                // -> Balance (exact match, priority 95)
findComponent("Compact<Balance>");       // -> Balance (exact match, priority 95)
findComponent("BalanceOf<T>");           // -> Balance (regex match, priority 95)
findComponent("H160");                   // -> Hash160 (exact match, priority 82)
findComponent("H512");                   // -> Hash512 (exact match, priority 78)
findComponent("Vec<u8>");               // -> Bytes (exact match, priority 75)
findComponent("[u8; 32]");              // -> VectorFixed (regex match, priority 43)
findComponent("BTreeMap<u32, u64>");    // -> BTreeMap (regex match, priority 42)
findComponent("BTreeSet<AccountId>");   // -> BTreeSet (regex match, priority 41)
findComponent("Vec<AccountId>");        // -> Vector (regex match, priority 40)
findComponent("Option<Balance>");       // -> Option (regex match, priority 45)
findComponent("UnknownType");           // -> Text (fallback)

Component Registration

Components are registered in the registry array with the following structure:

interface ComponentRegistration {
  component: React.ComponentType<any>;  // The React component
  schema: any;                          // Zod validation schema
  patterns: (string | RegExp)[];        // Matching patterns
  priority: number;                     // Higher = checked first
}

Current Registry

const registry: ComponentRegistration[] = [
  {
    component: Account,
    schema: Account.schema,
    patterns: ["AccountId", "AccountId32", "MultiAddress", /^AccountId/],
    priority: 100,
  },
  {
    component: Balance,
    schema: Balance.schema,
    patterns: ["Balance", "BalanceOf", /^Balance/],
    priority: 95,
  },
  // ... more registrations
];

Usage in Components

The input map is typically used when dynamically rendering extrinsic parameters:

import { findComponent } from "@/lib/input-map";

function ParameterInput({ field, client, onChange }) {
  const { component: Component, schema } = findComponent(
    field.typeName,
    field.typeId
  );

  return (
    <Component
      client={client}
      name={field.name}
      label={field.name}
      description={field.typeName}
      typeId={field.typeId}
      onChange={onChange}
    />
  );
}

With Complex Types

For complex types like Option<T>, Vec<T>, or Call, the component uses the typeId to resolve nested types:

// In Option component
function Option({ typeId, client, children, ...props }) {
  // If no children provided, resolve inner type from registry
  const innerType = useMemo(() => {
    if (children) return children;
    if (!client || !typeId) return null;

    const typeInfo = client.registry.findType(typeId);
    // Extract inner type and find its component
    const innerTypeId = typeInfo.typeDef.value.typeParam;
    const innerTypeName = client.registry.findType(innerTypeId).path?.join("::");

    return findComponent(innerTypeName, innerTypeId);
  }, [children, client, typeId]);

  // Render inner component
}

Extending the Registry

To add support for a custom type:

  1. Create the input component:
// components/params/inputs/my-custom.tsx
import { z } from "zod";
import type { ParamInputProps } from "../types";

const schema = z.string();

export function MyCustom({ name, label, onChange, ...props }: ParamInputProps) {
  return (
    <div>
      {/* Your input UI */}
    </div>
  );
}

MyCustom.schema = schema;
  1. Add to the registry in lib/input-map.ts:
import { MyCustom } from "@/components/params/inputs/my-custom";

const registry: ComponentRegistration[] = [
  // Add before lower-priority entries
  {
    component: MyCustom,
    schema: MyCustom.schema,
    patterns: ["MyCustomType", /^MyCustom/],
    priority: 85,  // Choose appropriate priority
  },
  // ... existing entries
];

Best Practices

  1. Choose appropriate priority: Higher priority for more specific types
  2. Use exact strings for common types: Faster matching than regex
  3. Use regex for type families: e.g., /^AccountId/ for all AccountId variants
  4. Provide meaningful fallbacks: Unknown types fall back to Text input
  5. Include validation schema: Each component should export a Zod schema

Type Patterns Reference

Common Substrate type patterns and their expected components:

TypeComponentNotes
AccountId, AccountId32AccountSS58 address input
MultiAddressAccountSupports Id, Index, Raw variants
Balance, BalanceOf<T>BalanceWith denomination selector
Compact<Balance>, Compact<BalanceOf>BalanceCompact-wrapped balances get denomination support
Compact<u128>AmountInteger input
u32, u64, u128AmountUnsigned integers
boolBooleanToggle switch
H160Hash16020-byte hex input
H256, BlockHashHash25632-byte hex input
H512Hash51264-byte hex input
Vec<u8>, BytesBytesHex byte array
[T; N]VectorFixedFixed-length array (e.g., [u8; 32])
BTreeMap<K, V>BTreeMapKey-value pair list
BTreeSet<T>BTreeSetUnique value set
Vec<T>VectorDynamic array
Option<T>OptionOptional with toggle
(T1, T2, ...)TuplePositional elements
{ field: T }StructNamed fields
enum { A, B }EnumVariant selector
Call, RuntimeCallCallNested extrinsic