From blueprint-frontend
Use when building frontend applications for Tangle Blueprints — job submission, operator discovery, service provisioning, session auth, or integrating @tangle-network/blueprint-ui.
npx claudepluginhub tangle-network/skills --plugin blueprint-frontendThis skill uses the workspace's default tool permissions.
Use this skill when building React frontends for Tangle Network blueprints. Covers the shared UI library (`@tangle-network/blueprint-ui`), on-chain interaction patterns, session auth, and Web3 provider setup.
Triggers research for existing libraries, tools, and patterns before coding new features. Searches npm, PyPI, MCP/skills, GitHub; evaluates matches and decides adopt/extend/build.
Audits cross-stack repos (C++/Android/iOS/Web), classifies files as project/third-party/artifacts, detects embedded libraries, assigns module verdicts, generates interactive HTML reports.
Reorganizes X and LinkedIn networks: review-first pruning of low-value follows, priority-based add/follow recommendations, and drafts warm outreach in user's voice.
Share bugs, ideas, or general feedback.
Use this skill when building React frontends for Tangle Network blueprints. Covers the shared UI library (@tangle-network/blueprint-ui), on-chain interaction patterns, session auth, and Web3 provider setup.
For sandbox-specific frontend patterns (agent chat, terminal, sidecar auth), see sandbox-blueprint.
@tangle-network/blueprint-uiChain/contract interaction, job forms, stores, layout primitives. App-agnostic — no product-specific routing or copy.
Source: tangle-network/blueprint-ui
Three export entry points:
@tangle-network/blueprint-ui — hooks, stores, contracts, utilities@tangle-network/blueprint-ui/components — UI components@tangle-network/blueprint-ui/preset — UnoCSS theme tokensRegister blueprints at app startup to enable generic job forms and submission:
import { registerBlueprint, type BlueprintDefinition, type JobDefinition } from '@tangle-network/blueprint-ui';
const MY_JOBS: JobDefinition[] = [
{
id: 0,
name: 'create_instance',
label: 'Create Instance',
description: 'Provision a new sandbox instance',
category: 'lifecycle',
icon: 'i-ph:play',
pricingMultiplier: 1.0,
requiresSandbox: false,
fields: [
{
name: 'name',
label: 'Instance Name',
type: 'text',
required: true,
abiType: 'string',
abiParam: 'name',
},
{
name: 'cpu_cores',
label: 'CPU Cores',
type: 'number',
defaultValue: 2,
min: 1,
max: 16,
abiType: 'uint64',
abiParam: 'cpu_cores',
},
{
name: 'runtime_backend',
label: 'Runtime',
type: 'select',
defaultValue: 'docker',
options: [
{ label: 'Docker', value: 'docker' },
{ label: 'Firecracker', value: 'firecracker' },
],
abiType: 'string',
abiParam: 'runtime_backend',
},
],
},
];
const MY_BLUEPRINT: BlueprintDefinition = {
id: 'my-blueprint',
name: 'My Blueprint',
version: '1.0.0',
description: 'Description',
icon: 'i-ph:cube',
color: '#3B82F6',
contracts: {
31337: '0x...', // local
3799: '0x...', // testnet
},
jobs: MY_JOBS,
categories: [
{ key: 'lifecycle', label: 'Lifecycle', icon: 'i-ph:arrows-clockwise' },
],
};
registerBlueprint(MY_BLUEPRINT);
type JobCategory = 'lifecycle' | 'execution' | 'batch' | 'workflow' | 'ssh' | 'management';
interface JobFieldDef {
name: string;
label: string;
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'json' | 'combobox';
required?: boolean;
defaultValue?: string | number | boolean;
options?: { label: string; value: string }[];
helperText?: string;
min?: number; max?: number; step?: number;
abiType?: string; // e.g. 'uint64', 'string', 'address[]'
abiParam?: string; // Solidity param name
internal?: boolean; // included in encoding but hidden from form
}
interface JobDefinition {
id: number;
name: string;
label: string;
description: string;
category: JobCategory;
icon: string;
pricingMultiplier: number;
fields: JobFieldDef[];
requiresSandbox: boolean;
warning?: string;
contextParams?: AbiContextParam[]; // e.g. sidecar_url, sandbox_id
customEncoder?: (values, context?) => `0x${string}`; // for nested structs
}
useOperators() → discover operators for blueprint
↓
useQuotes() → fetch RFQ pricing from operators
↓
useJobForm() → manage form state from JobDefinition
↓
encodeJobArgs() → ABI-encode form values using field metadata
↓
useSubmitJob() → submit on-chain, track TX lifecycle
↓
useProvisionProgress() → poll provision status until ready
import { useSubmitJob } from '@tangle-network/blueprint-ui';
const { submitJob, status, txHash, callId, error, reset } = useSubmitJob();
await submitJob({
serviceId: 1n,
jobId: 0, // matches JobDefinition.id
args: encodeJobArgs(job, formValues, context),
label: 'Create Instance',
value: quotedPrice, // optional payment
});
// status: 'idle' → 'signing' → 'pending' → 'confirmed' | 'failed'
// callId: extracted from JobCalled event logs
import { useOperators } from '@tangle-network/blueprint-ui';
const { operators, operatorCount, isLoading, error } = useOperators();
// operators: { address, ecdsaPublicKey, rpcAddress }[]
import { useQuotes, formatCost } from '@tangle-network/blueprint-ui';
const { quotes, totalCost, isLoading, isSolvingPow } = useQuotes(
operators,
blueprintId,
ttlBlocks,
enabled,
);
// quotes: { operator, totalCost, signature, details, costRate }[]
// Internally solves 20-bit SHA256 PoW before requesting quote
import { useJobPrice } from '@tangle-network/blueprint-ui';
const { quote, formattedPrice, isLoading } = useJobPrice(
operatorRpcUrl,
serviceId,
jobIndex,
blueprintId,
enabled,
);
import { encodeJobArgs } from '@tangle-network/blueprint-ui';
const encoded = encodeJobArgs(jobDefinition, formValues, {
sidecar_url: 'http://...',
sandbox_id: '0x...',
});
// Returns ABI-encoded 0x... string
// Handles coercion: bools, uintX, strings, arrays
// Delegates to job.customEncoder for complex structs
import { useServiceValidation } from '@tangle-network/blueprint-ui';
const { validate, serviceInfo, isValidating, error } = useServiceValidation();
const info = await validate(serviceId, userAddress);
// info: { active, blueprintId, owner, operatorCount, operators, permitted, ttl, createdAt }
import { useProvisionProgress, getPhaseLabel } from '@tangle-network/blueprint-ui';
const { phase, progressPct, sandboxId, sidecarUrl, isReady, isFailed, message } =
useProvisionProgress(callId, operatorRpcUrl, enabled);
// Phases: queued → image_pull → container_create → container_start → health_check → ready | failed
// Polls every 2s, stops on terminal phase
import { useSessionAuth, useAuthenticatedFetch } from '@tangle-network/blueprint-ui';
const { session, isAuthenticated, authenticate, logout } = useSessionAuth(sandboxId, operatorUrl);
// Challenge-response: request challenge → sign with wallet → exchange for PASETO token
// Stored in sessionMapStore (persisted to localStorage, keyed by sandboxId)
const { authFetch } = useAuthenticatedFetch(sandboxId, operatorUrl);
const res = await authFetch('/api/instances');
// Auto-injects Bearer token, re-authenticates on 401
import { infraStore, updateInfra, getInfra } from '@tangle-network/blueprint-ui';
updateInfra({ blueprintId: '1', serviceId: '1' });
const { blueprintId, serviceId, serviceInfo } = getInfra();
// Persisted to localStorage
import { txListStore, addTx, updateTx, pendingCount } from '@tangle-network/blueprint-ui';
addTx({ hash, label: 'Create Instance', status: 'pending', chainId });
// Max 50 TXs, BigInt-aware serialization
import { getSession, setSession, removeSession, gcSessions } from '@tangle-network/blueprint-ui';
const session = getSession(sandboxId);
// { token, address, expiresAt, sandboxId }
// Auto-cleans expired sessions (60s buffer)
import { Web3Shell } from '@tangle-network/blueprint-ui/components';
import { tangleWalletChains, createTangleTransports, defaultConnectKitOptions } from '@tangle-network/blueprint-ui';
import { createConfig, WagmiProvider } from 'wagmi';
import { getDefaultConfig } from 'connectkit';
const config = createConfig(
getDefaultConfig({
chains: tangleWalletChains, // [tangleLocal, tangleTestnet, tangleMainnet, mainnet]
transports: createTangleTransports(),
walletConnectProjectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID,
...defaultConnectKitOptions,
}),
);
// Web3Shell wraps with WagmiProvider + QueryClientProvider
<Web3Shell config={config}>
<App />
</Web3Shell>
import {
tangleLocal, tangleTestnet, tangleMainnet,
configureNetworks, getNetworks, resolveRpcUrl,
selectedChainIdStore, getPublicClient, getAddresses,
} from '@tangle-network/blueprint-ui';
// Register networks with contract addresses
configureNetworks([
{ chain: tangleLocal, rpcUrl: resolveRpcUrl(), label: 'Local', addresses: { jobs: '0x...', services: '0x...' } },
{ chain: tangleTestnet, rpcUrl: 'https://testnet-rpc.tangle.tools', label: 'Testnet', addresses: { ... } },
]);
// Chain switching updates publicClient automatically
selectedChainIdStore.set(3799); // switch to testnet
const client = getPublicClient();
const { jobs, services } = getAddresses();
import {
AppDocument, Web3Shell, ChainSwitcher, ThemeToggle, AppToaster, AnimatedPage,
} from '@tangle-network/blueprint-ui/components';
// AppDocument: sets <html data-theme>, prevents FOUC, preloads fonts
// ChainSwitcher: dropdown for Local/Testnet/Mainnet
// ThemeToggle: dark/light toggle
// AppToaster: sonner toast integration
// AnimatedPage: framer-motion page transitions
import { BlueprintJobForm, JobExecutionDialog } from '@tangle-network/blueprint-ui/components';
import { useJobForm } from '@tangle-network/blueprint-ui';
// Manual form rendering:
const { values, errors, onChange, validate } = useJobForm(jobDefinition);
<BlueprintJobForm job={jobDefinition} values={values} onChange={onChange} errors={errors} />
// Complete dialog (form + pricing + submission):
<JobExecutionDialog
open={open}
onOpenChange={setOpen}
job={jobDefinition}
serviceId={serviceId}
context={{ sandbox_id: '0x...' }}
onSuccess={(callId) => watchProvision(callId)}
/>
import { bpThemeTokens } from '@tangle-network/blueprint-ui/preset';
// UnoCSS config:
export default defineConfig({
theme: {
colors: {
bp: bpThemeTokens('myapp'),
},
},
});
// Usage in components:
// text-bp-elements-textPrimary
// bg-bp-elements-background-depth-1
// border-bp-elements-borderColor
| Variable | Purpose |
|---|---|
VITE_CHAIN_ID | Default chain (31337 local, 3799 testnet, 5845 mainnet) |
VITE_RPC_URL | RPC endpoint (defaults to localhost:8545) |
VITE_BLUEPRINT_ID | Default blueprint ID |
VITE_SERVICE_ID | Default service ID |
VITE_OPERATOR_API_URL | Operator API endpoint |
VITE_WALLETCONNECT_PROJECT_ID | WalletConnect project ID |
VITE_OPERATOR_API_TOKEN | Operator bearer token (dev) |
On-chain (state-changing, via useSubmitJob):
Off-chain (operator HTTP API, via useAuthenticatedFetch):
Jobs mutate state. Everything else goes through the operator API.
src/index.ts — main exports (hooks, stores, contracts, utils)src/components.ts — component exportssrc/preset.ts — UnoCSS theme tokenssrc/hooks/useSubmitJob.ts — job submissionsrc/hooks/useOperators.ts — operator discoverysrc/hooks/useQuotes.ts — RFQ pricing with PoWsrc/hooks/useJobPrice.ts — per-job pricingsrc/hooks/useServiceValidation.ts — service validationsrc/hooks/useSessionAuth.ts — PASETO session managementsrc/hooks/useProvisionProgress.ts — provision trackingsrc/hooks/useJobForm.ts — form state managementsrc/blueprints/registry.ts — blueprint registrationsrc/contracts/abi.ts — Tangle contract ABIssrc/contracts/chains.ts — chain definitionssrc/contracts/publicClient.ts — reactive public clientsrc/contracts/generic-encoder.ts — ABI argument encodingsrc/stores/ — infraStore, sessionMapStore, txListStore, themeStoresrc/components/forms/BlueprintJobForm.tsx — job form renderersrc/components/forms/JobExecutionDialog.tsx — complete submission dialogeth_call and operator HTTP API.encodeJobArgs for ABI encoding. Don't hand-roll encoding from form values.gcSessions() or rely on sessionMapStore's built-in cleanup.