@tangle-network/blueprint-ui 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/blueprint-ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared blueprint UI components, hooks, and contract utilities for Tangle Network apps",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -12,9 +12,13 @@
12
12
  "url": "https://github.com/tangle-network/blueprint-ui/issues"
13
13
  },
14
14
  "type": "module",
15
- "main": "./src/index.ts",
16
- "types": "./src/index.ts",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "sideEffects": [
18
+ "**/*.css"
19
+ ],
17
20
  "files": [
21
+ "dist",
18
22
  "src",
19
23
  "README.md",
20
24
  "package.json",
@@ -24,16 +28,37 @@
24
28
  "access": "public"
25
29
  },
26
30
  "exports": {
27
- ".": "./src/index.ts",
28
- "./components": "./src/components.ts",
29
- "./preset": "./src/preset.ts"
31
+ ".": {
32
+ "import": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./host": {
37
+ "import": "./dist/host.js",
38
+ "types": "./dist/host.d.ts",
39
+ "default": "./dist/host.js"
40
+ },
41
+ "./components": {
42
+ "import": "./dist/components.js",
43
+ "types": "./dist/components.d.ts",
44
+ "default": "./dist/components.js"
45
+ },
46
+ "./preset": {
47
+ "import": "./dist/preset.js",
48
+ "types": "./dist/preset.d.ts",
49
+ "default": "./dist/preset.js"
50
+ },
51
+ "./styles.css": "./dist/styles.css"
30
52
  },
31
53
  "scripts": {
32
- "typecheck": "tsc --noEmit"
54
+ "build": "tsup && node scripts/build-styles.mjs",
55
+ "dev": "tsup --watch",
56
+ "typecheck": "tsc --noEmit",
57
+ "prepack": "npm run build"
33
58
  },
34
59
  "peerDependencies": {
35
60
  "@tanstack/react-query": "^5.0.0",
36
- "@nanostores/react": "^0.7.0",
61
+ "@nanostores/react": "^0.7.0 || ^1.0.0",
37
62
  "@radix-ui/react-dialog": "^1.1.0",
38
63
  "@radix-ui/react-select": "^2.1.0",
39
64
  "@radix-ui/react-separator": "^1.1.0",
@@ -44,14 +69,14 @@
44
69
  "class-variance-authority": "^0.7.0",
45
70
  "clsx": "^2.1.0",
46
71
  "framer-motion": "^12.0.0",
47
- "nanostores": "^0.10.0",
48
- "react": "^19.0.0",
49
- "react-dom": "^19.0.0",
72
+ "nanostores": "^0.10.0 || ^1.0.0",
73
+ "react": "^18.0.0 || ^19.0.0",
74
+ "react-dom": "^18.0.0 || ^19.0.0",
50
75
  "react-router": "^7.0.0",
51
76
  "sonner": "^2.0.0",
52
- "tailwind-merge": "^3.2.0",
77
+ "tailwind-merge": "^2.6.0 || ^3.2.0",
53
78
  "viem": "^2.31.0",
54
- "wagmi": "^3.3.0"
79
+ "wagmi": "^2.19.0 || ^3.3.0"
55
80
  },
56
81
  "peerDependenciesMeta": {},
57
82
  "devDependencies": {
@@ -65,6 +90,7 @@
65
90
  "@radix-ui/react-tooltip": "^1.2.8",
66
91
  "@types/react": "18.3.1",
67
92
  "@types/react-dom": "18.3.1",
93
+ "@iconify-json/ph": "^1.2.2",
68
94
  "blo": "^2.0.0",
69
95
  "class-variance-authority": "^0.7.1",
70
96
  "clsx": "^2.1.1",
@@ -75,7 +101,10 @@
75
101
  "react-router": "^7.13.0",
76
102
  "sonner": "^2.0.7",
77
103
  "tailwind-merge": "^3.5.0",
104
+ "tsup": "^8.5.0",
78
105
  "typescript": "^5.5.2",
106
+ "unocss": "^66.5.4",
107
+ "unocss-preset-animations": "^1.1.1",
79
108
  "viem": "^2.46.2",
80
109
  "wagmi": "^3.5.0"
81
110
  }
package/src/components.ts CHANGED
@@ -52,3 +52,6 @@ export type { FormSection } from './components/forms/BlueprintJobForm';
52
52
  export { BlueprintJobForm } from './components/forms/BlueprintJobForm';
53
53
  export { FormSummary } from './components/forms/FormSummary';
54
54
  export { JobExecutionDialog } from './components/forms/JobExecutionDialog';
55
+
56
+ // ── Blueprint Host ──
57
+ export { BlueprintHostHero, BlueprintHostPanel } from './host';
@@ -103,6 +103,7 @@ export const tangleServicesAbi = [
103
103
  { name: 'totalCost', type: 'uint256' },
104
104
  { name: 'timestamp', type: 'uint64' },
105
105
  { name: 'expiry', type: 'uint64' },
106
+ { name: 'confidentiality', type: 'uint8' },
106
107
  {
107
108
  name: 'securityCommitments',
108
109
  type: 'tuple[]',
@@ -118,6 +119,14 @@ export const tangleServicesAbi = [
118
119
  { name: 'exposureBps', type: 'uint16' },
119
120
  ],
120
121
  },
122
+ {
123
+ name: 'resourceCommitments',
124
+ type: 'tuple[]',
125
+ components: [
126
+ { name: 'kind', type: 'uint8' },
127
+ { name: 'count', type: 'uint64' },
128
+ ],
129
+ },
121
130
  ],
122
131
  },
123
132
  { name: 'signature', type: 'bytes' },
@@ -1,13 +1,14 @@
1
1
  import { defineChain } from 'viem';
2
2
  import { mainnet } from 'viem/chains';
3
3
  import type { Address, Chain } from 'viem';
4
+ import { getEnvVar, isDevEnv } from '../utils/env';
4
5
 
5
6
  /**
6
7
  * Resolve RPC URL for the current environment.
7
8
  * Handles local dev (hostname swap), Vite dev proxy, and remote access.
8
9
  */
9
10
  export function resolveRpcUrl(envUrl?: string): string {
10
- const configured = envUrl ?? import.meta.env.VITE_RPC_URL ?? 'http://localhost:8545';
11
+ const configured = envUrl ?? getEnvVar('VITE_RPC_URL') ?? 'http://localhost:8545';
11
12
  if (typeof window === 'undefined') return configured;
12
13
  try {
13
14
  const rpc = new URL(configured);
@@ -15,7 +16,7 @@ export function resolveRpcUrl(envUrl?: string): string {
15
16
  const pageHost = window.location.hostname;
16
17
  const isLocalPage = pageHost === '127.0.0.1' || pageHost === 'localhost';
17
18
  // Dev-mode proxy for LAN access to local RPC
18
- if (isLocalRpc && !isLocalPage && import.meta.env.DEV) {
19
+ if (isLocalRpc && !isLocalPage && isDevEnv()) {
19
20
  return `${window.location.origin}/rpc-proxy`;
20
21
  }
21
22
  // Non-dev LAN access: swap hostname
@@ -37,7 +38,7 @@ export interface LocalChainOptions {
37
38
  }
38
39
 
39
40
  export function createTangleLocalChain(options: LocalChainOptions = {}) {
40
- const chainId = options.chainId ?? Number(import.meta.env.VITE_CHAIN_ID ?? 31337);
41
+ const chainId = options.chainId ?? Number(getEnvVar('VITE_CHAIN_ID') ?? 31337);
41
42
  const localRpcUrl = resolveRpcUrl(options.rpcUrl);
42
43
 
43
44
  return defineChain({
@@ -3,8 +3,9 @@ import type { PublicClient } from 'viem';
3
3
  import { atom } from 'nanostores';
4
4
  import { getNetworks, tangleLocal, type CoreAddresses } from './chains';
5
5
  import { persistedAtom } from '../stores/persistedAtom';
6
+ import { getEnvVar } from '../utils/env';
6
7
 
7
- const defaultChainId = Number(import.meta.env.VITE_CHAIN_ID ?? tangleLocal.id);
8
+ const defaultChainId = Number(getEnvVar('VITE_CHAIN_ID') ?? tangleLocal.id);
8
9
 
9
10
  export const selectedChainIdStore = persistedAtom<number>({
10
11
  key: 'bp_selected_chain',
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { getEnvVar } from '../utils/env';
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // Types matching sandbox-runtime/src/provision_progress.rs
@@ -64,7 +65,7 @@ export function useProvisionProgress({
64
65
  const [isPolling, setIsPolling] = useState(false);
65
66
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
66
67
 
67
- const baseUrl = apiUrl ?? import.meta.env.VITE_OPERATOR_API_URL ?? 'http://localhost:9090';
68
+ const baseUrl = apiUrl ?? getEnvVar('VITE_OPERATOR_API_URL') ?? 'http://localhost:9090';
68
69
 
69
70
  const fetchProgress = useCallback(async () => {
70
71
  if (callId == null) return;
@@ -18,6 +18,16 @@ import { resolveOperatorRpc } from '../utils/resolveOperatorRpc';
18
18
 
19
19
  // ── Types ──
20
20
 
21
+ type SecurityCommitment = {
22
+ asset: { kind: number; token: Address };
23
+ exposureBps: number;
24
+ };
25
+
26
+ type ResourceCommitment = {
27
+ kind: number;
28
+ count: bigint;
29
+ };
30
+
21
31
  export interface OperatorQuote {
22
32
  operator: Address;
23
33
  totalCost: bigint;
@@ -28,12 +38,13 @@ export interface OperatorQuote {
28
38
  totalCost: bigint;
29
39
  timestamp: bigint;
30
40
  expiry: bigint;
31
- securityCommitments: readonly {
32
- asset: { kind: number; token: Address };
33
- exposureBps: number;
34
- }[];
41
+ confidentiality: number;
42
+ securityCommitments: readonly SecurityCommitment[];
43
+ resourceCommitments: readonly ResourceCommitment[];
35
44
  };
36
45
  costRate: number;
46
+ teeAttested?: boolean;
47
+ teeProvider?: string;
37
48
  }
38
49
 
39
50
  export interface UseQuotesResult {
@@ -49,6 +60,20 @@ export interface UseQuotesResult {
49
60
 
50
61
  const POW_DIFFICULTY = 20;
51
62
  const WEI_PER_TNT = 1_000_000_000_000_000_000; // 10^18
63
+ const RESOURCE_KIND_TO_ID = {
64
+ CPU: 0,
65
+ MemoryMB: 1,
66
+ StorageMB: 2,
67
+ NetworkEgressMB: 3,
68
+ NetworkIngressMB: 4,
69
+ GPU: 5,
70
+ } as const;
71
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as Address;
72
+ const DEFAULT_RESOURCE_REQUIREMENTS = [
73
+ { kind: 'CPU', count: 1 },
74
+ { kind: 'MemoryMB', count: 1024 },
75
+ { kind: 'StorageMB', count: 10240 },
76
+ ] as const;
52
77
 
53
78
  function sha256(data: Uint8Array): Uint8Array {
54
79
  return viemSha256(data, 'bytes');
@@ -114,6 +139,35 @@ export function formatCost(totalCost: bigint): string {
114
139
  return `${tnt.toLocaleString(undefined, { maximumFractionDigits: 2 })} TNT`;
115
140
  }
116
141
 
142
+ function quoteConfidentiality(requireTee: boolean): number {
143
+ return requireTee ? 1 : 0;
144
+ }
145
+
146
+ function resourceKindToId(kind: string): number {
147
+ const mapped = RESOURCE_KIND_TO_ID[kind as keyof typeof RESOURCE_KIND_TO_ID];
148
+ if (mapped === undefined) {
149
+ throw new Error(`Unsupported resource kind in quote: ${kind}`);
150
+ }
151
+ return mapped;
152
+ }
153
+
154
+ function mapJsonSecurityCommitment(sc: any): SecurityCommitment {
155
+ return {
156
+ asset: {
157
+ kind: sc.asset?.kind ?? 0,
158
+ token: (sc.asset?.token ?? ZERO_ADDRESS) as Address,
159
+ },
160
+ exposureBps: sc.exposure_bps ?? 0,
161
+ };
162
+ }
163
+
164
+ function mapJsonResourceCommitment(resource: any): ResourceCommitment {
165
+ return {
166
+ kind: resourceKindToId(String(resource.kind ?? 'CPU')),
167
+ count: BigInt(resource.count ?? 0),
168
+ };
169
+ }
170
+
117
171
  // ── Hook ──
118
172
 
119
173
  export function useQuotes(
@@ -121,6 +175,7 @@ export function useQuotes(
121
175
  blueprintId: bigint,
122
176
  ttlBlocks: bigint,
123
177
  enabled: boolean,
178
+ requireTee = false,
124
179
  ): UseQuotesResult {
125
180
  const [quotes, setQuotes] = useState<OperatorQuote[]>([]);
126
181
  const [isLoading, setIsLoading] = useState(false);
@@ -168,6 +223,7 @@ export function useQuotes(
168
223
  ttlBlocks,
169
224
  proofOfWork: proof,
170
225
  challengeTimestamp: timestamp,
226
+ requireTee,
171
227
  });
172
228
 
173
229
  if (!response) throw new Error('No quote returned from operator');
@@ -194,7 +250,7 @@ export function useQuotes(
194
250
  return () => {
195
251
  cancelled = true;
196
252
  };
197
- }, [operators, blueprintId, ttlBlocks, enabled, fetchKey]);
253
+ }, [operators, blueprintId, ttlBlocks, enabled, fetchKey, requireTee]);
198
254
 
199
255
  const totalCost = quotes.reduce((sum, q) => sum + q.totalCost, 0n);
200
256
 
@@ -213,6 +269,7 @@ async function fetchPriceFromOperator(
213
269
  ttlBlocks: bigint;
214
270
  proofOfWork: Uint8Array;
215
271
  challengeTimestamp: bigint;
272
+ requireTee: boolean;
216
273
  },
217
274
  ): Promise<OperatorQuote | null> {
218
275
  // Try JSON endpoint (simpler, no protobuf dependency required)
@@ -225,11 +282,8 @@ async function fetchPriceFromOperator(
225
282
  ttl_blocks: String(params.ttlBlocks),
226
283
  proof_of_work: toHex(params.proofOfWork),
227
284
  challenge_timestamp: String(params.challengeTimestamp),
228
- resource_requirements: [
229
- { kind: 'CPU', count: 1 },
230
- { kind: 'MemoryMB', count: 1024 },
231
- { kind: 'StorageMB', count: 10240 },
232
- ],
285
+ require_tee: params.requireTee,
286
+ resource_requirements: DEFAULT_RESOURCE_REQUIREMENTS,
233
287
  }),
234
288
  signal: AbortSignal.timeout(10_000),
235
289
  });
@@ -242,16 +296,17 @@ async function fetchPriceFromOperator(
242
296
  totalCost: BigInt(data.total_cost ?? '0'),
243
297
  signature: (data.signature ?? '0x') as `0x${string}`,
244
298
  costRate: Number(data.cost_rate ?? 0),
299
+ teeAttested: Boolean(data.tee_attested),
300
+ teeProvider: data.tee_provider || undefined,
245
301
  details: {
246
302
  blueprintId: BigInt(data.details?.blueprint_id ?? params.blueprintId),
247
303
  ttlBlocks: BigInt(data.details?.ttl_blocks ?? params.ttlBlocks),
248
304
  totalCost: BigInt(data.details?.total_cost ?? '0'),
249
305
  timestamp: BigInt(data.details?.timestamp ?? params.challengeTimestamp),
250
306
  expiry: BigInt(data.details?.expiry ?? '0'),
251
- securityCommitments: (data.details?.security_commitments ?? []).map((sc: any) => ({
252
- asset: { kind: sc.asset?.kind ?? 0, token: (sc.asset?.token ?? '0x0000000000000000000000000000000000000000') as Address },
253
- exposureBps: sc.exposure_bps ?? 0,
254
- })),
307
+ confidentiality: Number(data.details?.confidentiality ?? quoteConfidentiality(params.requireTee)),
308
+ securityCommitments: (data.details?.security_commitments ?? []).map(mapJsonSecurityCommitment),
309
+ resourceCommitments: (data.details?.resources ?? []).map(mapJsonResourceCommitment),
255
310
  },
256
311
  };
257
312
  } catch {
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useState } from 'react';
2
2
  import { useSignMessage } from 'wagmi';
3
3
  import { getSession, setSession, removeSession, type SessionEntry } from '../stores/session';
4
+ import { getEnvVar } from '../utils/env';
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // Types
@@ -28,7 +29,7 @@ interface UseSessionAuthOptions {
28
29
  }
29
30
 
30
31
  export function useSessionAuth({ sandboxId, apiUrl }: UseSessionAuthOptions) {
31
- const baseUrl = apiUrl ?? import.meta.env.VITE_OPERATOR_API_URL ?? 'http://localhost:9090';
32
+ const baseUrl = apiUrl ?? getEnvVar('VITE_OPERATOR_API_URL') ?? 'http://localhost:9090';
32
33
  const { signMessageAsync } = useSignMessage();
33
34
 
34
35
  const [isAuthenticating, setIsAuthenticating] = useState(false);
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../../components/ui/card';
3
+ import { Badge } from '../../components/ui/badge';
4
+ import { Button } from '../../components/ui/button';
5
+ import { cn } from '../../utils';
6
+
7
+ type Action = {
8
+ label: string;
9
+ href?: string;
10
+ onClick?: () => void;
11
+ variant?: React.ComponentProps<typeof Button>['variant'];
12
+ disabled?: boolean;
13
+ };
14
+
15
+ export type BlueprintHostHeroProps = {
16
+ title: string;
17
+ tagline?: string;
18
+ description?: string;
19
+ badges?: string[];
20
+ actions?: Action[];
21
+ children?: React.ReactNode;
22
+ className?: string;
23
+ };
24
+
25
+ export function BlueprintHostHero({
26
+ title,
27
+ tagline,
28
+ description,
29
+ badges = [],
30
+ actions = [],
31
+ children,
32
+ className,
33
+ }: BlueprintHostHeroProps) {
34
+ return (
35
+ <Card className={cn('rounded-3xl border-bp-elements-borderColor/70 bg-bp-elements-background-depth-2', className)}>
36
+ <CardHeader className="space-y-4">
37
+ {badges.length > 0 ? (
38
+ <div className="flex flex-wrap gap-2">
39
+ {badges.map((badge) => (
40
+ <Badge key={badge} variant="secondary">
41
+ {badge}
42
+ </Badge>
43
+ ))}
44
+ </div>
45
+ ) : null}
46
+ <div className="space-y-2">
47
+ <CardTitle className="text-3xl">{title}</CardTitle>
48
+ {tagline ? (
49
+ <CardDescription className="text-base text-bp-elements-textPrimary/80">
50
+ {tagline}
51
+ </CardDescription>
52
+ ) : null}
53
+ </div>
54
+ {description ? (
55
+ <p className="max-w-3xl text-sm leading-6 text-bp-elements-textSecondary">
56
+ {description}
57
+ </p>
58
+ ) : null}
59
+ </CardHeader>
60
+ {(actions.length > 0 || children) && (
61
+ <CardContent className="space-y-4">
62
+ {actions.length > 0 ? (
63
+ <div className="flex flex-wrap gap-3">
64
+ {actions.map((action) => {
65
+ const button = (
66
+ <Button
67
+ key={action.label}
68
+ variant={action.variant ?? 'default'}
69
+ onClick={action.onClick}
70
+ disabled={action.disabled}
71
+ >
72
+ {action.label}
73
+ </Button>
74
+ );
75
+
76
+ return action.href ? (
77
+ <a key={action.label} href={action.href}>
78
+ {button}
79
+ </a>
80
+ ) : (
81
+ button
82
+ );
83
+ })}
84
+ </div>
85
+ ) : null}
86
+ {children}
87
+ </CardContent>
88
+ )}
89
+ </Card>
90
+ );
91
+ }
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/card';
3
+ import { cn } from '../../utils';
4
+
5
+ export type BlueprintHostPanelProps = {
6
+ title: string;
7
+ children: React.ReactNode;
8
+ className?: string;
9
+ };
10
+
11
+ export function BlueprintHostPanel({
12
+ title,
13
+ children,
14
+ className,
15
+ }: BlueprintHostPanelProps) {
16
+ return (
17
+ <Card className={cn('rounded-3xl border-bp-elements-borderColor/70 bg-bp-elements-background-depth-2', className)}>
18
+ <CardHeader>
19
+ <CardTitle className="text-xl">{title}</CardTitle>
20
+ </CardHeader>
21
+ <CardContent>{children}</CardContent>
22
+ </Card>
23
+ );
24
+ }
@@ -0,0 +1,42 @@
1
+ export type {
2
+ BlueprintAppVisibility,
3
+ BlueprintPublisherVerification,
4
+ BlueprintExperienceTier,
5
+ BlueprintSlugPolicy,
6
+ BlueprintUiSurface,
7
+ BlueprintResourceRoute,
8
+ BlueprintPermissionScope,
9
+ BlueprintExternalAppMode,
10
+ BlueprintExternalAppTrust,
11
+ BlueprintPublisher,
12
+ BlueprintResourceModel,
13
+ BlueprintPermissionDescriptor,
14
+ BlueprintExternalAppConfig,
15
+ BlueprintUiManifest,
16
+ BlueprintAppModuleBinding,
17
+ BlueprintAppEntry,
18
+ BlueprintAppResolvedView,
19
+ } from './types';
20
+
21
+ export {
22
+ buildCanonicalBlueprintSlug,
23
+ resolveBlueprintAppView,
24
+ toBlueprintAppEntry,
25
+ getBlueprintExperienceTierLabel,
26
+ getBlueprintSlugPolicyLabel,
27
+ getBlueprintSurfaceLabel,
28
+ getBlueprintPublisherVerificationLabel,
29
+ getExternalAppTrustLabel,
30
+ isVerifiedBlueprintPublisher,
31
+ canPublisherClaimSlug,
32
+ isTrustedExternalAppHost,
33
+ getBlueprintPath,
34
+ getBlueprintServicePath,
35
+ sanitizeBlueprintSlugPart,
36
+ deriveBlueprintRequestedSlug,
37
+ } from './resolver';
38
+
39
+ export type { BlueprintHostHeroProps } from './components/BlueprintHostHero';
40
+ export { BlueprintHostHero } from './components/BlueprintHostHero';
41
+ export type { BlueprintHostPanelProps } from './components/BlueprintHostPanel';
42
+ export { BlueprintHostPanel } from './components/BlueprintHostPanel';