@tangle-network/blueprint-ui 0.5.5 → 0.5.7
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/dist/chunk-37ADATBT.js +55 -0
- package/dist/chunk-37ADATBT.js.map +1 -0
- package/dist/chunk-F2QBCGUW.js +1582 -0
- package/dist/chunk-F2QBCGUW.js.map +1 -0
- package/dist/chunk-TM5ROMDV.js +57 -0
- package/dist/chunk-TM5ROMDV.js.map +1 -0
- package/dist/components.d.ts +195 -0
- package/dist/components.js +1168 -0
- package/dist/components.js.map +1 -0
- package/dist/detectParentOrigin-BYruoIdc.d.ts +26 -0
- package/dist/iframe/index.d.ts +146 -0
- package/dist/iframe/index.js +607 -0
- package/dist/iframe/index.js.map +1 -0
- package/dist/iframe/testing-index.d.ts +82 -0
- package/dist/iframe/testing-index.js +560 -0
- package/dist/iframe/testing-index.js.map +1 -0
- package/dist/index.d.ts +8620 -0
- package/dist/index.js +870 -0
- package/dist/index.js.map +1 -0
- package/dist/parentBridgeProtocol-BSgLXg9g.d.ts +204 -0
- package/dist/preset.d.ts +60 -0
- package/dist/preset.js +7 -0
- package/dist/preset.js.map +1 -0
- package/dist/styles.css +568 -0
- package/dist/tangleIframeClient-C7NFG_Dw.d.ts +133 -0
- package/dist/useRegistrationCommand-Df1mvvwE.d.ts +151 -0
- package/dist/wallet/index.d.ts +134 -0
- package/dist/wallet/index.js +472 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +1 -1
- package/src/components.ts +0 -3
- package/src/index.ts +0 -6
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/utils/resolveOperatorRpc.ts
|
|
9
|
+
function resolveOperatorRpc(raw) {
|
|
10
|
+
if (typeof window === "undefined") return raw;
|
|
11
|
+
const withProto = raw.includes("://") ? raw : `http://${raw}`;
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(withProto);
|
|
14
|
+
const pageHost = window.location.hostname;
|
|
15
|
+
const isNonRoutable = url.hostname.endsWith(".local") || !url.hostname.includes(".") || url.hostname === "127.0.0.1" || url.hostname === "localhost";
|
|
16
|
+
if (isNonRoutable && pageHost !== url.hostname) {
|
|
17
|
+
url.hostname = pageHost;
|
|
18
|
+
}
|
|
19
|
+
return url.toString().replace(/\/$/, "");
|
|
20
|
+
} catch {
|
|
21
|
+
return withProto;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/contracts/chains.ts
|
|
26
|
+
import { defineChain } from "viem";
|
|
27
|
+
import { mainnet } from "viem/chains";
|
|
28
|
+
|
|
29
|
+
// src/utils/env.ts
|
|
30
|
+
function readImportMetaEnv() {
|
|
31
|
+
return import.meta.env ?? {};
|
|
32
|
+
}
|
|
33
|
+
function getEnvVar(key) {
|
|
34
|
+
const value = readImportMetaEnv()[key];
|
|
35
|
+
return typeof value === "string" ? value : void 0;
|
|
36
|
+
}
|
|
37
|
+
function isDevEnv() {
|
|
38
|
+
return Boolean(readImportMetaEnv().DEV);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/contracts/chains.ts
|
|
42
|
+
function resolveRpcUrl(envUrl) {
|
|
43
|
+
const configured = envUrl ?? getEnvVar("VITE_RPC_URL") ?? "http://localhost:8545";
|
|
44
|
+
if (typeof window === "undefined") return configured;
|
|
45
|
+
try {
|
|
46
|
+
const rpc = new URL(configured);
|
|
47
|
+
const isLocalRpc = rpc.hostname === "127.0.0.1" || rpc.hostname === "localhost";
|
|
48
|
+
const pageHost = window.location.hostname;
|
|
49
|
+
const isLocalPage = pageHost === "127.0.0.1" || pageHost === "localhost";
|
|
50
|
+
if (isLocalRpc && !isLocalPage && isDevEnv()) {
|
|
51
|
+
return `${window.location.origin}/rpc-proxy`;
|
|
52
|
+
}
|
|
53
|
+
if (isLocalRpc && !isLocalPage) {
|
|
54
|
+
rpc.hostname = pageHost;
|
|
55
|
+
return rpc.toString().replace(/\/$/, "");
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
return configured;
|
|
60
|
+
}
|
|
61
|
+
var rpcUrl = resolveRpcUrl();
|
|
62
|
+
function createTangleLocalChain(options = {}) {
|
|
63
|
+
const chainId = options.chainId ?? Number(getEnvVar("VITE_CHAIN_ID") ?? 31337);
|
|
64
|
+
const localRpcUrl = resolveRpcUrl(options.rpcUrl);
|
|
65
|
+
return defineChain({
|
|
66
|
+
id: chainId,
|
|
67
|
+
name: "Tangle Local",
|
|
68
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
69
|
+
rpcUrls: { default: { http: [localRpcUrl] } },
|
|
70
|
+
blockExplorers: { default: { name: "Explorer", url: "" } },
|
|
71
|
+
contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
var tangleLocal = createTangleLocalChain();
|
|
75
|
+
var tangleTestnet = defineChain({
|
|
76
|
+
id: 3799,
|
|
77
|
+
name: "Tangle Testnet",
|
|
78
|
+
nativeCurrency: { name: "Tangle", symbol: "tTNT", decimals: 18 },
|
|
79
|
+
rpcUrls: {
|
|
80
|
+
default: {
|
|
81
|
+
http: ["https://testnet-rpc.tangle.tools"],
|
|
82
|
+
webSocket: ["wss://testnet-rpc.tangle.tools"]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
blockExplorers: { default: { name: "Tangle Explorer", url: "https://testnet-explorer.tangle.tools" } },
|
|
86
|
+
contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } }
|
|
87
|
+
});
|
|
88
|
+
var tangleMainnet = defineChain({
|
|
89
|
+
id: 5845,
|
|
90
|
+
name: "Tangle",
|
|
91
|
+
nativeCurrency: { name: "Tangle", symbol: "TNT", decimals: 18 },
|
|
92
|
+
rpcUrls: {
|
|
93
|
+
default: {
|
|
94
|
+
http: ["https://rpc.tangle.tools"],
|
|
95
|
+
webSocket: ["wss://rpc.tangle.tools"]
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
blockExplorers: { default: { name: "Tangle Explorer", url: "https://explorer.tangle.tools" } },
|
|
99
|
+
contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } }
|
|
100
|
+
});
|
|
101
|
+
var _networks = {};
|
|
102
|
+
function configureNetworks(nets) {
|
|
103
|
+
_networks = nets;
|
|
104
|
+
}
|
|
105
|
+
function getNetworks() {
|
|
106
|
+
return _networks;
|
|
107
|
+
}
|
|
108
|
+
var allTangleChains = [tangleLocal, tangleTestnet, tangleMainnet];
|
|
109
|
+
|
|
110
|
+
// src/stores/persistedAtom.ts
|
|
111
|
+
import { atom } from "nanostores";
|
|
112
|
+
function serializeWithBigInt(value) {
|
|
113
|
+
return JSON.stringify(
|
|
114
|
+
value,
|
|
115
|
+
(_key, v) => typeof v === "bigint" ? { __bigint: v.toString() } : v
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
function deserializeWithBigInt(raw) {
|
|
119
|
+
return JSON.parse(raw, (_key, v) => {
|
|
120
|
+
if (v && typeof v === "object" && "__bigint" in v && typeof v.__bigint === "string") {
|
|
121
|
+
return BigInt(v.__bigint);
|
|
122
|
+
}
|
|
123
|
+
return v;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function persistedAtom(opts) {
|
|
127
|
+
const { key, initial, serialize = JSON.stringify, deserialize = JSON.parse } = opts;
|
|
128
|
+
let restored = initial;
|
|
129
|
+
if (typeof window !== "undefined") {
|
|
130
|
+
try {
|
|
131
|
+
const raw = localStorage.getItem(key);
|
|
132
|
+
if (raw !== null) {
|
|
133
|
+
restored = deserialize(raw);
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const store = atom(restored);
|
|
139
|
+
if (typeof window !== "undefined") {
|
|
140
|
+
store.subscribe((value) => {
|
|
141
|
+
try {
|
|
142
|
+
localStorage.setItem(key, serialize(value));
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return store;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/stores/txHistory.ts
|
|
151
|
+
import { computed } from "nanostores";
|
|
152
|
+
var MAX_TXS = 50;
|
|
153
|
+
var txListStore = persistedAtom({
|
|
154
|
+
key: "bp_tx_history",
|
|
155
|
+
initial: [],
|
|
156
|
+
serialize: serializeWithBigInt,
|
|
157
|
+
deserialize: deserializeWithBigInt
|
|
158
|
+
});
|
|
159
|
+
var pendingCount = computed(
|
|
160
|
+
txListStore,
|
|
161
|
+
(txs) => txs.filter((tx) => tx.status === "pending").length
|
|
162
|
+
);
|
|
163
|
+
function addTx(hash, label, chainId) {
|
|
164
|
+
const existing = txListStore.get();
|
|
165
|
+
if (existing.some((tx) => tx.hash === hash)) return;
|
|
166
|
+
const newTx = { hash, label, status: "pending", timestamp: Date.now(), chainId };
|
|
167
|
+
txListStore.set([newTx, ...existing].slice(0, MAX_TXS));
|
|
168
|
+
}
|
|
169
|
+
function updateTx(hash, update) {
|
|
170
|
+
txListStore.set(
|
|
171
|
+
txListStore.get().map(
|
|
172
|
+
(tx) => tx.hash === hash ? { ...tx, ...update } : tx
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
function clearTxs() {
|
|
177
|
+
txListStore.set([]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/stores/infra.ts
|
|
181
|
+
var defaultBlueprintId = getEnvVar("VITE_BLUEPRINT_ID") ?? "0";
|
|
182
|
+
var defaultServiceId = getEnvVar("VITE_SERVICE_ID") ?? getEnvVar("VITE_SERVICE_IDS")?.split(",")[0] ?? "0";
|
|
183
|
+
var infraStore = persistedAtom({
|
|
184
|
+
key: "bp_infra",
|
|
185
|
+
initial: {
|
|
186
|
+
blueprintId: defaultBlueprintId,
|
|
187
|
+
serviceId: defaultServiceId,
|
|
188
|
+
serviceValidated: false
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
function updateInfra(update) {
|
|
192
|
+
infraStore.set({ ...infraStore.get(), ...update });
|
|
193
|
+
}
|
|
194
|
+
function getInfra() {
|
|
195
|
+
return infraStore.get();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/stores/theme.ts
|
|
199
|
+
import { atom as atom2 } from "nanostores";
|
|
200
|
+
var kTheme = "bp_theme";
|
|
201
|
+
var DEFAULT_THEME = "dark";
|
|
202
|
+
var themeStore = atom2(initStore());
|
|
203
|
+
function themeIsDark() {
|
|
204
|
+
return themeStore.get() === "dark";
|
|
205
|
+
}
|
|
206
|
+
function initStore() {
|
|
207
|
+
if (typeof window !== "undefined") {
|
|
208
|
+
const persisted = localStorage.getItem(kTheme);
|
|
209
|
+
const attr = document.querySelector("html")?.getAttribute("data-theme");
|
|
210
|
+
return persisted ?? attr ?? DEFAULT_THEME;
|
|
211
|
+
}
|
|
212
|
+
return DEFAULT_THEME;
|
|
213
|
+
}
|
|
214
|
+
function toggleTheme() {
|
|
215
|
+
const next = themeStore.get() === "dark" ? "light" : "dark";
|
|
216
|
+
themeStore.set(next);
|
|
217
|
+
localStorage.setItem(kTheme, next);
|
|
218
|
+
document.querySelector("html")?.setAttribute("data-theme", next);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/contracts/abi.ts
|
|
222
|
+
var tangleJobsAbi = [
|
|
223
|
+
{
|
|
224
|
+
type: "function",
|
|
225
|
+
name: "submitJob",
|
|
226
|
+
inputs: [
|
|
227
|
+
{ name: "serviceId", type: "uint64" },
|
|
228
|
+
{ name: "job", type: "uint8" },
|
|
229
|
+
{ name: "args", type: "bytes" }
|
|
230
|
+
],
|
|
231
|
+
outputs: [{ name: "callId", type: "uint64" }],
|
|
232
|
+
stateMutability: "payable"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: "event",
|
|
236
|
+
name: "JobCalled",
|
|
237
|
+
inputs: [
|
|
238
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
239
|
+
{ name: "job", type: "uint8", indexed: true },
|
|
240
|
+
{ name: "callId", type: "uint64", indexed: true },
|
|
241
|
+
{ name: "caller", type: "address", indexed: false },
|
|
242
|
+
{ name: "args", type: "bytes", indexed: false }
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
type: "event",
|
|
247
|
+
name: "JobResultReceived",
|
|
248
|
+
inputs: [
|
|
249
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
250
|
+
{ name: "job", type: "uint8", indexed: true },
|
|
251
|
+
{ name: "callId", type: "uint64", indexed: true },
|
|
252
|
+
{ name: "operator", type: "address", indexed: false },
|
|
253
|
+
{ name: "outputs", type: "bytes", indexed: false }
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
type: "event",
|
|
258
|
+
name: "JobSubmitted",
|
|
259
|
+
inputs: [
|
|
260
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
261
|
+
{ name: "callId", type: "uint64", indexed: true },
|
|
262
|
+
{ name: "jobIndex", type: "uint8", indexed: false },
|
|
263
|
+
{ name: "caller", type: "address", indexed: false },
|
|
264
|
+
{ name: "inputs", type: "bytes", indexed: false }
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: "event",
|
|
269
|
+
name: "JobCompleted",
|
|
270
|
+
inputs: [
|
|
271
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
272
|
+
{ name: "callId", type: "uint64", indexed: true }
|
|
273
|
+
]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: "event",
|
|
277
|
+
name: "JobResultSubmitted",
|
|
278
|
+
inputs: [
|
|
279
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
280
|
+
{ name: "callId", type: "uint64", indexed: true },
|
|
281
|
+
{ name: "operator", type: "address", indexed: true },
|
|
282
|
+
{ name: "output", type: "bytes", indexed: false }
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
];
|
|
286
|
+
var tangleServicesAbi = [
|
|
287
|
+
{
|
|
288
|
+
type: "function",
|
|
289
|
+
name: "requestService",
|
|
290
|
+
inputs: [
|
|
291
|
+
{ name: "blueprintId", type: "uint64" },
|
|
292
|
+
{ name: "operators", type: "address[]" },
|
|
293
|
+
{ name: "config", type: "bytes" },
|
|
294
|
+
{ name: "permittedCallers", type: "address[]" },
|
|
295
|
+
{ name: "ttl", type: "uint64" },
|
|
296
|
+
{ name: "paymentToken", type: "address" },
|
|
297
|
+
{ name: "paymentAmount", type: "uint256" }
|
|
298
|
+
],
|
|
299
|
+
outputs: [{ name: "requestId", type: "uint64" }],
|
|
300
|
+
stateMutability: "payable"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
type: "function",
|
|
304
|
+
name: "createServiceFromQuotes",
|
|
305
|
+
inputs: [
|
|
306
|
+
{ name: "blueprintId", type: "uint64" },
|
|
307
|
+
{
|
|
308
|
+
name: "quotes",
|
|
309
|
+
type: "tuple[]",
|
|
310
|
+
components: [
|
|
311
|
+
{
|
|
312
|
+
name: "details",
|
|
313
|
+
type: "tuple",
|
|
314
|
+
components: [
|
|
315
|
+
// tnt-core v0.13.0: `requester` is the first field of QuoteDetails.
|
|
316
|
+
// The contract enforces `requester == msg.sender` and rejects address(0).
|
|
317
|
+
{ name: "requester", type: "address" },
|
|
318
|
+
{ name: "blueprintId", type: "uint64" },
|
|
319
|
+
{ name: "ttlBlocks", type: "uint64" },
|
|
320
|
+
{ name: "totalCost", type: "uint256" },
|
|
321
|
+
{ name: "timestamp", type: "uint64" },
|
|
322
|
+
{ name: "expiry", type: "uint64" },
|
|
323
|
+
{ name: "confidentiality", type: "uint8" },
|
|
324
|
+
{
|
|
325
|
+
name: "securityCommitments",
|
|
326
|
+
type: "tuple[]",
|
|
327
|
+
components: [
|
|
328
|
+
{
|
|
329
|
+
name: "asset",
|
|
330
|
+
type: "tuple",
|
|
331
|
+
components: [
|
|
332
|
+
{ name: "kind", type: "uint8" },
|
|
333
|
+
{ name: "token", type: "address" }
|
|
334
|
+
]
|
|
335
|
+
},
|
|
336
|
+
{ name: "exposureBps", type: "uint16" }
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "resourceCommitments",
|
|
341
|
+
type: "tuple[]",
|
|
342
|
+
components: [
|
|
343
|
+
{ name: "kind", type: "uint8" },
|
|
344
|
+
{ name: "count", type: "uint64" }
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
{ name: "signature", type: "bytes" },
|
|
350
|
+
{ name: "operator", type: "address" }
|
|
351
|
+
]
|
|
352
|
+
},
|
|
353
|
+
{ name: "config", type: "bytes" },
|
|
354
|
+
{ name: "permittedCallers", type: "address[]" },
|
|
355
|
+
{ name: "ttl", type: "uint64" }
|
|
356
|
+
],
|
|
357
|
+
outputs: [{ name: "serviceId", type: "uint64" }],
|
|
358
|
+
stateMutability: "payable"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
type: "function",
|
|
362
|
+
name: "getService",
|
|
363
|
+
inputs: [{ name: "serviceId", type: "uint64" }],
|
|
364
|
+
outputs: [
|
|
365
|
+
{
|
|
366
|
+
name: "",
|
|
367
|
+
type: "tuple",
|
|
368
|
+
components: [
|
|
369
|
+
{ name: "blueprintId", type: "uint64" },
|
|
370
|
+
{ name: "owner", type: "address" },
|
|
371
|
+
{ name: "createdAt", type: "uint64" },
|
|
372
|
+
{ name: "ttl", type: "uint64" },
|
|
373
|
+
{ name: "terminatedAt", type: "uint64" },
|
|
374
|
+
{ name: "lastPaymentAt", type: "uint64" },
|
|
375
|
+
{ name: "operatorCount", type: "uint32" },
|
|
376
|
+
{ name: "minOperators", type: "uint32" },
|
|
377
|
+
{ name: "maxOperators", type: "uint32" },
|
|
378
|
+
{ name: "membership", type: "uint8" },
|
|
379
|
+
{ name: "pricing", type: "uint8" },
|
|
380
|
+
{ name: "status", type: "uint8" }
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
stateMutability: "view"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
type: "function",
|
|
388
|
+
name: "isServiceActive",
|
|
389
|
+
inputs: [{ name: "serviceId", type: "uint64" }],
|
|
390
|
+
outputs: [{ name: "", type: "bool" }],
|
|
391
|
+
stateMutability: "view"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: "function",
|
|
395
|
+
name: "getServiceOperators",
|
|
396
|
+
inputs: [{ name: "serviceId", type: "uint64" }],
|
|
397
|
+
outputs: [{ name: "", type: "address[]" }],
|
|
398
|
+
stateMutability: "view"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
type: "function",
|
|
402
|
+
name: "isPermittedCaller",
|
|
403
|
+
inputs: [
|
|
404
|
+
{ name: "serviceId", type: "uint64" },
|
|
405
|
+
{ name: "caller", type: "address" }
|
|
406
|
+
],
|
|
407
|
+
outputs: [{ name: "", type: "bool" }],
|
|
408
|
+
stateMutability: "view"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
type: "event",
|
|
412
|
+
name: "ServiceRequested",
|
|
413
|
+
inputs: [
|
|
414
|
+
{ name: "requestId", type: "uint64", indexed: true },
|
|
415
|
+
{ name: "blueprintId", type: "uint64", indexed: true },
|
|
416
|
+
{ name: "requester", type: "address", indexed: true }
|
|
417
|
+
]
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
type: "event",
|
|
421
|
+
name: "ServiceActivated",
|
|
422
|
+
inputs: [
|
|
423
|
+
{ name: "serviceId", type: "uint64", indexed: true },
|
|
424
|
+
{ name: "requestId", type: "uint64", indexed: true },
|
|
425
|
+
{ name: "blueprintId", type: "uint64", indexed: true }
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
var tangleOperatorsAbi = [
|
|
430
|
+
{
|
|
431
|
+
type: "function",
|
|
432
|
+
name: "blueprintOperatorCount",
|
|
433
|
+
inputs: [{ name: "blueprintId", type: "uint64" }],
|
|
434
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
435
|
+
stateMutability: "view"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: "function",
|
|
439
|
+
name: "isOperatorRegistered",
|
|
440
|
+
inputs: [
|
|
441
|
+
{ name: "blueprintId", type: "uint64" },
|
|
442
|
+
{ name: "operator", type: "address" }
|
|
443
|
+
],
|
|
444
|
+
outputs: [{ name: "", type: "bool" }],
|
|
445
|
+
stateMutability: "view"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
type: "function",
|
|
449
|
+
name: "getOperatorPreferences",
|
|
450
|
+
inputs: [
|
|
451
|
+
{ name: "blueprintId", type: "uint64" },
|
|
452
|
+
{ name: "operator", type: "address" }
|
|
453
|
+
],
|
|
454
|
+
outputs: [
|
|
455
|
+
{
|
|
456
|
+
name: "preferences",
|
|
457
|
+
type: "tuple",
|
|
458
|
+
components: [
|
|
459
|
+
{ name: "ecdsaPublicKey", type: "bytes" },
|
|
460
|
+
{ name: "rpcAddress", type: "string" }
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
],
|
|
464
|
+
stateMutability: "view"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
type: "event",
|
|
468
|
+
name: "OperatorRegistered",
|
|
469
|
+
inputs: [
|
|
470
|
+
{ name: "blueprintId", type: "uint64", indexed: true },
|
|
471
|
+
{ name: "operator", type: "address", indexed: true },
|
|
472
|
+
{ name: "ecdsaPublicKey", type: "bytes", indexed: false },
|
|
473
|
+
{ name: "rpcAddress", type: "string", indexed: false }
|
|
474
|
+
]
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
type: "event",
|
|
478
|
+
name: "OperatorUnregistered",
|
|
479
|
+
inputs: [
|
|
480
|
+
{ name: "blueprintId", type: "uint64", indexed: true },
|
|
481
|
+
{ name: "operator", type: "address", indexed: true }
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
// src/contracts/publicClient.ts
|
|
487
|
+
import { createPublicClient, http } from "viem";
|
|
488
|
+
import { atom as atom3 } from "nanostores";
|
|
489
|
+
var defaultChainId = Number(getEnvVar("VITE_CHAIN_ID") ?? tangleLocal.id);
|
|
490
|
+
var selectedChainIdStore = persistedAtom({
|
|
491
|
+
key: "bp_selected_chain",
|
|
492
|
+
initial: defaultChainId
|
|
493
|
+
});
|
|
494
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
495
|
+
function configuredDefaultChainId() {
|
|
496
|
+
const networks = getNetworks();
|
|
497
|
+
if (networks[defaultChainId]) return defaultChainId;
|
|
498
|
+
for (const [chainId, net] of Object.entries(networks)) {
|
|
499
|
+
if (net?.shortLabel === "Local" || net?.label === "Tangle Local" || net?.chain?.name === "Tangle Local") {
|
|
500
|
+
return Number(chainId);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const [firstConfigured] = Object.keys(networks);
|
|
504
|
+
return firstConfigured ? Number(firstConfigured) : defaultChainId;
|
|
505
|
+
}
|
|
506
|
+
function normalizeSelectedChainId(chainId) {
|
|
507
|
+
const networks = getNetworks();
|
|
508
|
+
if (!Object.keys(networks).length) return chainId;
|
|
509
|
+
return networks[chainId] ? chainId : configuredDefaultChainId();
|
|
510
|
+
}
|
|
511
|
+
function sanitizeSelectedChainId() {
|
|
512
|
+
const normalized = normalizeSelectedChainId(selectedChainIdStore.get());
|
|
513
|
+
if (normalized !== selectedChainIdStore.get()) {
|
|
514
|
+
selectedChainIdStore.set(normalized);
|
|
515
|
+
}
|
|
516
|
+
return normalized;
|
|
517
|
+
}
|
|
518
|
+
function getOrCreateClient(chainId) {
|
|
519
|
+
const normalizedChainId = normalizeSelectedChainId(chainId);
|
|
520
|
+
const cached = clientCache.get(normalizedChainId);
|
|
521
|
+
if (cached) return cached;
|
|
522
|
+
const networks = getNetworks();
|
|
523
|
+
const net = networks[normalizedChainId];
|
|
524
|
+
if (!net) {
|
|
525
|
+
const fallback = networks[configuredDefaultChainId()];
|
|
526
|
+
if (!fallback) {
|
|
527
|
+
return createPublicClient({ chain: tangleLocal, transport: http() });
|
|
528
|
+
}
|
|
529
|
+
return createPublicClient({ chain: fallback.chain, transport: http(fallback.rpcUrl) });
|
|
530
|
+
}
|
|
531
|
+
const client = createPublicClient({ chain: net.chain, transport: http(net.rpcUrl) });
|
|
532
|
+
clientCache.set(normalizedChainId, client);
|
|
533
|
+
return client;
|
|
534
|
+
}
|
|
535
|
+
var publicClientStore = atom3(getOrCreateClient(sanitizeSelectedChainId()));
|
|
536
|
+
selectedChainIdStore.subscribe((chainId) => {
|
|
537
|
+
const normalized = normalizeSelectedChainId(chainId);
|
|
538
|
+
if (normalized !== chainId) {
|
|
539
|
+
selectedChainIdStore.set(normalized);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
publicClientStore.set(getOrCreateClient(normalized));
|
|
543
|
+
});
|
|
544
|
+
function getPublicClient() {
|
|
545
|
+
sanitizeSelectedChainId();
|
|
546
|
+
return publicClientStore.get();
|
|
547
|
+
}
|
|
548
|
+
var publicClient = new Proxy({}, {
|
|
549
|
+
get(_target, prop) {
|
|
550
|
+
const client = getOrCreateClient(sanitizeSelectedChainId());
|
|
551
|
+
const value = client[prop];
|
|
552
|
+
return typeof value === "function" ? value.bind(client) : value;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
function getAddresses() {
|
|
556
|
+
const networks = getNetworks();
|
|
557
|
+
const selectedChainId = sanitizeSelectedChainId();
|
|
558
|
+
const net = networks[selectedChainId];
|
|
559
|
+
return net?.addresses ?? networks[configuredDefaultChainId()]?.addresses ?? {};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/contracts/generic-encoder.ts
|
|
563
|
+
import { encodeAbiParameters } from "viem";
|
|
564
|
+
function encodeJobArgs(job, formValues, context) {
|
|
565
|
+
if (job.customEncoder) {
|
|
566
|
+
return job.customEncoder(formValues, context);
|
|
567
|
+
}
|
|
568
|
+
const abiDefs = [];
|
|
569
|
+
const values = [];
|
|
570
|
+
if (job.contextParams) {
|
|
571
|
+
for (const cp of job.contextParams) {
|
|
572
|
+
abiDefs.push({ name: cp.abiName, type: cp.abiType });
|
|
573
|
+
values.push(coerceValue(context?.[cp.abiName], cp.abiType));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const field of job.fields) {
|
|
577
|
+
if (!field.abiType) continue;
|
|
578
|
+
abiDefs.push({ name: field.abiParam ?? field.name, type: field.abiType });
|
|
579
|
+
values.push(coerceValue(formValues[field.name], field.abiType));
|
|
580
|
+
}
|
|
581
|
+
return encodeAbiParameters(abiDefs, values);
|
|
582
|
+
}
|
|
583
|
+
function coerceValue(value, abiType) {
|
|
584
|
+
switch (abiType) {
|
|
585
|
+
case "bool":
|
|
586
|
+
return Boolean(value);
|
|
587
|
+
case "uint8":
|
|
588
|
+
case "uint16":
|
|
589
|
+
case "uint32":
|
|
590
|
+
return Number(value) || 0;
|
|
591
|
+
case "uint64":
|
|
592
|
+
case "uint128":
|
|
593
|
+
case "uint256":
|
|
594
|
+
return BigInt(Number(value) || 0);
|
|
595
|
+
case "string":
|
|
596
|
+
return String(value ?? "");
|
|
597
|
+
case "string[]":
|
|
598
|
+
if (Array.isArray(value)) return value.map(String);
|
|
599
|
+
return String(value ?? "").split("\n").filter(Boolean);
|
|
600
|
+
case "address[]":
|
|
601
|
+
if (Array.isArray(value)) return value;
|
|
602
|
+
return String(value ?? "").split("\n").map((s) => s.trim()).filter((s) => /^0x[a-fA-F0-9]{40}$/.test(s));
|
|
603
|
+
default:
|
|
604
|
+
return value;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/host/resolver.ts
|
|
609
|
+
var EXPERIENCE_TIER_LABELS = {
|
|
610
|
+
generic: "Protocol fallback",
|
|
611
|
+
declarative: "Declarative blueprint UI",
|
|
612
|
+
"curated-module": "Curated app module",
|
|
613
|
+
"external-app": "External app handoff"
|
|
614
|
+
};
|
|
615
|
+
var SLUG_POLICY_LABELS = {
|
|
616
|
+
reserved: "Reserved slug",
|
|
617
|
+
"publisher-scoped": "Publisher-scoped slug",
|
|
618
|
+
"public-requested": "Public requested slug"
|
|
619
|
+
};
|
|
620
|
+
var SURFACE_LABELS = {
|
|
621
|
+
"generic-overview": "Generic overview",
|
|
622
|
+
"service-explorer": "Service explorer",
|
|
623
|
+
"service-console": "Service console",
|
|
624
|
+
"actions-panel": "Actions panel",
|
|
625
|
+
resources: "Resources",
|
|
626
|
+
chat: "Chat",
|
|
627
|
+
vaults: "Vaults",
|
|
628
|
+
metrics: "Metrics",
|
|
629
|
+
permissions: "Permissions"
|
|
630
|
+
};
|
|
631
|
+
var PUBLISHER_VERIFICATION_LABELS = {
|
|
632
|
+
"first-party": "First-party publisher",
|
|
633
|
+
verified: "Verified publisher",
|
|
634
|
+
unverified: "Unverified publisher"
|
|
635
|
+
};
|
|
636
|
+
var EXTERNAL_APP_TRUST_LABELS = {
|
|
637
|
+
trusted: "Trusted external app",
|
|
638
|
+
restricted: "Restricted external app"
|
|
639
|
+
};
|
|
640
|
+
function buildCanonicalBlueprintSlug(entry) {
|
|
641
|
+
if (entry.canonicalSlug) {
|
|
642
|
+
return entry.canonicalSlug;
|
|
643
|
+
}
|
|
644
|
+
if (entry.slugPolicy === "reserved" || !entry.publisher.namespace) {
|
|
645
|
+
return entry.slug;
|
|
646
|
+
}
|
|
647
|
+
return `@${entry.publisher.namespace}/${entry.slug}`;
|
|
648
|
+
}
|
|
649
|
+
function resolveBlueprintAppView(entry) {
|
|
650
|
+
return {
|
|
651
|
+
slug: entry.slug,
|
|
652
|
+
canonicalSlug: buildCanonicalBlueprintSlug(entry),
|
|
653
|
+
blueprintId: entry.blueprintId,
|
|
654
|
+
publisher: entry.publisher,
|
|
655
|
+
tier: entry.tier,
|
|
656
|
+
slugPolicy: entry.slugPolicy,
|
|
657
|
+
manifest: entry.manifest,
|
|
658
|
+
module: entry.module,
|
|
659
|
+
fallbackEnabled: true
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function toBlueprintAppEntry(view) {
|
|
663
|
+
return {
|
|
664
|
+
slug: view.slug,
|
|
665
|
+
canonicalSlug: view.canonicalSlug,
|
|
666
|
+
blueprintId: view.blueprintId,
|
|
667
|
+
publisher: view.publisher,
|
|
668
|
+
tier: view.tier,
|
|
669
|
+
slugPolicy: view.slugPolicy,
|
|
670
|
+
manifest: view.manifest,
|
|
671
|
+
module: view.module
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function getBlueprintExperienceTierLabel(tier) {
|
|
675
|
+
return EXPERIENCE_TIER_LABELS[tier];
|
|
676
|
+
}
|
|
677
|
+
function getBlueprintSlugPolicyLabel(policy) {
|
|
678
|
+
return SLUG_POLICY_LABELS[policy];
|
|
679
|
+
}
|
|
680
|
+
function getBlueprintSurfaceLabel(surface) {
|
|
681
|
+
return SURFACE_LABELS[surface];
|
|
682
|
+
}
|
|
683
|
+
function getBlueprintPublisherVerificationLabel(verification) {
|
|
684
|
+
return PUBLISHER_VERIFICATION_LABELS[verification];
|
|
685
|
+
}
|
|
686
|
+
function getExternalAppTrustLabel(trust) {
|
|
687
|
+
return EXTERNAL_APP_TRUST_LABELS[trust];
|
|
688
|
+
}
|
|
689
|
+
function isVerifiedBlueprintPublisher(publisher) {
|
|
690
|
+
return publisher.verification === "first-party" || publisher.verification === "verified";
|
|
691
|
+
}
|
|
692
|
+
function canPublisherClaimSlug(slug, publisher, reservedSlugs = /* @__PURE__ */ new Set()) {
|
|
693
|
+
if (reservedSlugs.has(slug)) {
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
return publisher?.namespace !== void 0 && publisher.namespace.trim().length > 0 && (publisher.verification === "verified" || publisher.verification === "first-party");
|
|
697
|
+
}
|
|
698
|
+
function isTrustedExternalAppHost(host, trustedHosts = []) {
|
|
699
|
+
const normalizedHost = host.trim().toLowerCase();
|
|
700
|
+
return trustedHosts.some(
|
|
701
|
+
(trustedHost) => normalizedHost === trustedHost || normalizedHost.endsWith(`.${trustedHost}`)
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
function getBlueprintPath(view) {
|
|
705
|
+
if (view.tier === "curated-module" || view.tier === "external-app") {
|
|
706
|
+
return `/blueprints/${view.canonicalSlug}`;
|
|
707
|
+
}
|
|
708
|
+
if (view.blueprintId !== void 0) {
|
|
709
|
+
return `/blueprints/${view.blueprintId.toString()}`;
|
|
710
|
+
}
|
|
711
|
+
return `/blueprints/${view.slug}`;
|
|
712
|
+
}
|
|
713
|
+
function getBlueprintServicePath(view, serviceId) {
|
|
714
|
+
if (view.tier === "curated-module" || view.tier === "external-app") {
|
|
715
|
+
return `${getBlueprintPath(view)}/${serviceId}`;
|
|
716
|
+
}
|
|
717
|
+
return `${getBlueprintPath(view)}/services/${serviceId}`;
|
|
718
|
+
}
|
|
719
|
+
function sanitizeBlueprintSlugPart(value) {
|
|
720
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
721
|
+
}
|
|
722
|
+
function deriveBlueprintRequestedSlug(blueprint) {
|
|
723
|
+
const fromName = sanitizeBlueprintSlugPart(blueprint.name);
|
|
724
|
+
if (fromName.length > 0) {
|
|
725
|
+
return fromName;
|
|
726
|
+
}
|
|
727
|
+
const fromAuthor = sanitizeBlueprintSlugPart(blueprint.author);
|
|
728
|
+
if (fromAuthor.length > 0) {
|
|
729
|
+
return `${fromAuthor}-${blueprint.id.toString()}`;
|
|
730
|
+
}
|
|
731
|
+
return `blueprint-${blueprint.id.toString()}`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/components/ui/card.tsx
|
|
735
|
+
import { jsx } from "react/jsx-runtime";
|
|
736
|
+
function Card({ className, ...props }) {
|
|
737
|
+
return /* @__PURE__ */ jsx(
|
|
738
|
+
"div",
|
|
739
|
+
{
|
|
740
|
+
"data-slot": "card",
|
|
741
|
+
className: cn("glass-card rounded-xl text-bp-elements-textPrimary", className),
|
|
742
|
+
...props
|
|
743
|
+
}
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
function CardHeader({ className, ...props }) {
|
|
747
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "card-header", className: cn("flex flex-col gap-1.5 p-6", className), ...props });
|
|
748
|
+
}
|
|
749
|
+
function CardTitle({ className, ...props }) {
|
|
750
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "card-title", className: cn("leading-none font-semibold font-display", className), ...props });
|
|
751
|
+
}
|
|
752
|
+
function CardDescription({ className, ...props }) {
|
|
753
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "card-description", className: cn("text-bp-elements-textSecondary text-sm", className), ...props });
|
|
754
|
+
}
|
|
755
|
+
function CardContent({ className, ...props }) {
|
|
756
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "card-content", className: cn("px-6 pb-6", className), ...props });
|
|
757
|
+
}
|
|
758
|
+
function CardFooter({ className, ...props }) {
|
|
759
|
+
return /* @__PURE__ */ jsx("div", { "data-slot": "card-footer", className: cn("flex items-center px-6 pb-6", className), ...props });
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/components/ui/badge.tsx
|
|
763
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
764
|
+
import { cva } from "class-variance-authority";
|
|
765
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
766
|
+
var badgeVariants = cva(
|
|
767
|
+
"inline-flex items-center justify-center rounded-md border px-2.5 py-0.5 text-xs font-semibold font-data uppercase tracking-wider w-fit whitespace-nowrap shrink-0 gap-1 transition-colors",
|
|
768
|
+
{
|
|
769
|
+
variants: {
|
|
770
|
+
variant: {
|
|
771
|
+
default: "border-bp-elements-borderColor bg-bp-elements-background-depth-3 text-bp-elements-textPrimary",
|
|
772
|
+
secondary: "border-bp-elements-dividerColor bg-bp-elements-background-depth-2 text-bp-elements-textSecondary",
|
|
773
|
+
destructive: "border-crimson-500/20 bg-crimson-500/10 text-bp-elements-icon-error",
|
|
774
|
+
success: "border-teal-500/20 bg-teal-500/10 text-bp-elements-icon-success",
|
|
775
|
+
outline: "text-bp-elements-textPrimary border-bp-elements-borderColor bg-transparent",
|
|
776
|
+
accent: "border-violet-500/20 bg-violet-500/10 text-violet-700 dark:text-violet-400",
|
|
777
|
+
amber: "border-amber-500/20 bg-amber-500/10 text-amber-700 dark:text-amber-400",
|
|
778
|
+
running: "border-teal-500/20 bg-teal-500/10 text-teal-600 dark:text-teal-400",
|
|
779
|
+
stopped: "border-amber-500/20 bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
780
|
+
cold: "border-blue-500/20 bg-blue-500/10 text-blue-600 dark:text-blue-400"
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
defaultVariants: { variant: "default" }
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
function Badge({
|
|
787
|
+
className,
|
|
788
|
+
variant,
|
|
789
|
+
asChild = false,
|
|
790
|
+
...props
|
|
791
|
+
}) {
|
|
792
|
+
const Comp = asChild ? Slot : "span";
|
|
793
|
+
return /* @__PURE__ */ jsx2(Comp, { "data-slot": "badge", className: cn(badgeVariants({ variant }), className), ...props });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/components/ui/button.tsx
|
|
797
|
+
import { Slot as Slot2 } from "@radix-ui/react-slot";
|
|
798
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
799
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
800
|
+
var buttonVariants = cva2(
|
|
801
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium font-display transition-all duration-200 disabled:pointer-events-none disabled:opacity-40 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-violet-400/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bp-elements-background-depth-1',
|
|
802
|
+
{
|
|
803
|
+
variants: {
|
|
804
|
+
variant: {
|
|
805
|
+
default: "bg-violet-600 text-white font-semibold hover:bg-violet-500 shadow-[0_0_20px_rgba(142,89,255,0.25)] hover:shadow-[0_0_30px_rgba(142,89,255,0.35)]",
|
|
806
|
+
destructive: "bg-crimson-500/15 text-crimson-400 border border-crimson-500/20 hover:bg-crimson-500/25 hover:border-crimson-500/30",
|
|
807
|
+
outline: "glass glass-hover text-bp-elements-textPrimary",
|
|
808
|
+
secondary: "bg-bp-elements-background-depth-3 text-bp-elements-textSecondary border border-bp-elements-borderColor hover:bg-bp-elements-background-depth-4 hover:text-bp-elements-textPrimary",
|
|
809
|
+
ghost: "text-bp-elements-textSecondary hover:text-bp-elements-textPrimary hover:bg-bp-elements-item-backgroundHover",
|
|
810
|
+
link: "text-violet-700 dark:text-violet-400 underline-offset-4 hover:underline",
|
|
811
|
+
success: "bg-teal-600/15 text-teal-400 border border-teal-500/20 hover:bg-teal-600/25"
|
|
812
|
+
},
|
|
813
|
+
size: {
|
|
814
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
815
|
+
sm: "h-8 rounded-md gap-1.5 px-3 text-xs has-[>svg]:px-2.5",
|
|
816
|
+
lg: "h-11 rounded-xl px-7 text-base has-[>svg]:px-5",
|
|
817
|
+
icon: "size-9",
|
|
818
|
+
"icon-sm": "size-8"
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
defaultVariants: {
|
|
822
|
+
variant: "default",
|
|
823
|
+
size: "default"
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
);
|
|
827
|
+
function Button({
|
|
828
|
+
className,
|
|
829
|
+
variant = "default",
|
|
830
|
+
size = "default",
|
|
831
|
+
asChild = false,
|
|
832
|
+
...props
|
|
833
|
+
}) {
|
|
834
|
+
const Comp = asChild ? Slot2 : "button";
|
|
835
|
+
return /* @__PURE__ */ jsx3(
|
|
836
|
+
Comp,
|
|
837
|
+
{
|
|
838
|
+
"data-slot": "button",
|
|
839
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
840
|
+
...props
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/host/components/BlueprintHostHero.tsx
|
|
846
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
847
|
+
function BlueprintHostHero({
|
|
848
|
+
title,
|
|
849
|
+
tagline,
|
|
850
|
+
description,
|
|
851
|
+
badges = [],
|
|
852
|
+
actions = [],
|
|
853
|
+
children,
|
|
854
|
+
className
|
|
855
|
+
}) {
|
|
856
|
+
return /* @__PURE__ */ jsxs(Card, { className: cn("rounded-3xl border-bp-elements-borderColor/70 bg-bp-elements-background-depth-2", className), children: [
|
|
857
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "space-y-4", children: [
|
|
858
|
+
badges.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap gap-2", children: badges.map((badge) => /* @__PURE__ */ jsx4(Badge, { variant: "secondary", children: badge }, badge)) }) : null,
|
|
859
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
860
|
+
/* @__PURE__ */ jsx4(CardTitle, { className: "text-3xl", children: title }),
|
|
861
|
+
tagline ? /* @__PURE__ */ jsx4(CardDescription, { className: "text-base text-bp-elements-textPrimary/80", children: tagline }) : null
|
|
862
|
+
] }),
|
|
863
|
+
description ? /* @__PURE__ */ jsx4("p", { className: "max-w-3xl text-sm leading-6 text-bp-elements-textSecondary", children: description }) : null
|
|
864
|
+
] }),
|
|
865
|
+
(actions.length > 0 || children) && /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
|
|
866
|
+
actions.length > 0 ? /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap gap-3", children: actions.map((action) => {
|
|
867
|
+
const button = /* @__PURE__ */ jsx4(
|
|
868
|
+
Button,
|
|
869
|
+
{
|
|
870
|
+
variant: action.variant ?? "default",
|
|
871
|
+
onClick: action.onClick,
|
|
872
|
+
disabled: action.disabled,
|
|
873
|
+
children: action.label
|
|
874
|
+
},
|
|
875
|
+
action.label
|
|
876
|
+
);
|
|
877
|
+
return action.href ? /* @__PURE__ */ jsx4("a", { href: action.href, children: button }, action.label) : button;
|
|
878
|
+
}) }) : null,
|
|
879
|
+
children
|
|
880
|
+
] })
|
|
881
|
+
] });
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/host/components/BlueprintHostPanel.tsx
|
|
885
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
886
|
+
function BlueprintHostPanel({
|
|
887
|
+
title,
|
|
888
|
+
children,
|
|
889
|
+
className
|
|
890
|
+
}) {
|
|
891
|
+
return /* @__PURE__ */ jsxs2(Card, { className: cn("rounded-3xl border-bp-elements-borderColor/70 bg-bp-elements-background-depth-2", className), children: [
|
|
892
|
+
/* @__PURE__ */ jsx5(CardHeader, { children: /* @__PURE__ */ jsx5(CardTitle, { className: "text-xl", children: title }) }),
|
|
893
|
+
/* @__PURE__ */ jsx5(CardContent, { children })
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/hooks/useJobForm.ts
|
|
898
|
+
import { useState, useCallback, useEffect, useMemo } from "react";
|
|
899
|
+
function buildDefaults(job) {
|
|
900
|
+
const init = {};
|
|
901
|
+
for (const f of job.fields) {
|
|
902
|
+
if (f.internal) continue;
|
|
903
|
+
if (f.defaultValue !== void 0) {
|
|
904
|
+
init[f.name] = f.defaultValue;
|
|
905
|
+
} else if (f.type === "boolean") {
|
|
906
|
+
init[f.name] = false;
|
|
907
|
+
} else if (f.type === "number") {
|
|
908
|
+
init[f.name] = f.min ?? 0;
|
|
909
|
+
} else {
|
|
910
|
+
init[f.name] = "";
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return init;
|
|
914
|
+
}
|
|
915
|
+
function useJobForm(job) {
|
|
916
|
+
const defaults = useMemo(() => job ? buildDefaults(job) : {}, [job]);
|
|
917
|
+
const [values, setValues] = useState(defaults);
|
|
918
|
+
const [errors, setErrors] = useState({});
|
|
919
|
+
useEffect(() => {
|
|
920
|
+
setValues(defaults);
|
|
921
|
+
setErrors({});
|
|
922
|
+
}, [defaults]);
|
|
923
|
+
const onChange = useCallback((name, value) => {
|
|
924
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
925
|
+
setErrors((prev) => {
|
|
926
|
+
if (!prev[name]) return prev;
|
|
927
|
+
const next = { ...prev };
|
|
928
|
+
delete next[name];
|
|
929
|
+
return next;
|
|
930
|
+
});
|
|
931
|
+
}, []);
|
|
932
|
+
const validate = useCallback(() => {
|
|
933
|
+
if (!job) return false;
|
|
934
|
+
const errs = {};
|
|
935
|
+
for (const f of job.fields) {
|
|
936
|
+
if (f.internal) continue;
|
|
937
|
+
const v = values[f.name];
|
|
938
|
+
if (f.required && (v === void 0 || v === null || v === "")) {
|
|
939
|
+
errs[f.name] = `${f.label} is required`;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (f.type === "number" && typeof v === "number") {
|
|
943
|
+
if (f.min != null && v < f.min) {
|
|
944
|
+
errs[f.name] = `${f.label} must be at least ${f.min}`;
|
|
945
|
+
} else if (f.max != null && v > f.max) {
|
|
946
|
+
errs[f.name] = `${f.label} must be at most ${f.max}`;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
setErrors(errs);
|
|
951
|
+
return Object.keys(errs).length === 0;
|
|
952
|
+
}, [job, values]);
|
|
953
|
+
const reset = useCallback(() => {
|
|
954
|
+
setValues(defaults);
|
|
955
|
+
setErrors({});
|
|
956
|
+
}, [defaults]);
|
|
957
|
+
return { values, errors, onChange, validate, reset };
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/hooks/useQuotes.ts
|
|
961
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
|
|
962
|
+
import { sha256 as viemSha256, toHex } from "viem";
|
|
963
|
+
var POW_DIFFICULTY = 20;
|
|
964
|
+
var WEI_PER_TNT = 1e18;
|
|
965
|
+
var RESOURCE_KIND_TO_ID = {
|
|
966
|
+
CPU: 0,
|
|
967
|
+
MemoryMB: 1,
|
|
968
|
+
StorageMB: 2,
|
|
969
|
+
NetworkEgressMB: 3,
|
|
970
|
+
NetworkIngressMB: 4,
|
|
971
|
+
GPU: 5
|
|
972
|
+
};
|
|
973
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
974
|
+
var DEFAULT_RESOURCE_REQUIREMENTS = [
|
|
975
|
+
{ kind: "CPU", count: 1 },
|
|
976
|
+
{ kind: "MemoryMB", count: 1024 },
|
|
977
|
+
{ kind: "StorageMB", count: 10240 }
|
|
978
|
+
];
|
|
979
|
+
function sha256(data) {
|
|
980
|
+
return viemSha256(data, "bytes");
|
|
981
|
+
}
|
|
982
|
+
function generateChallenge(blueprintId, timestamp) {
|
|
983
|
+
const input = new Uint8Array(16);
|
|
984
|
+
const view = new DataView(input.buffer);
|
|
985
|
+
view.setBigUint64(0, blueprintId, false);
|
|
986
|
+
view.setBigUint64(8, timestamp, false);
|
|
987
|
+
return sha256(input);
|
|
988
|
+
}
|
|
989
|
+
function checkDifficulty(hash, difficulty) {
|
|
990
|
+
const zeroBytes = Math.floor(difficulty / 8);
|
|
991
|
+
const zeroBits = difficulty % 8;
|
|
992
|
+
for (let i = 0; i < zeroBytes; i++) {
|
|
993
|
+
if (hash[i] !== 0) return false;
|
|
994
|
+
}
|
|
995
|
+
if (zeroBits > 0) {
|
|
996
|
+
const mask = 255 << 8 - zeroBits;
|
|
997
|
+
if ((hash[zeroBytes] & mask) !== 0) return false;
|
|
998
|
+
}
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
async function solvePoW(blueprintId, timestamp) {
|
|
1002
|
+
const challenge = generateChallenge(blueprintId, timestamp);
|
|
1003
|
+
const buf = new Uint8Array(challenge.length + 8);
|
|
1004
|
+
buf.set(challenge, 0);
|
|
1005
|
+
const view = new DataView(buf.buffer);
|
|
1006
|
+
for (let nonce = 0; nonce < 4294967296; nonce++) {
|
|
1007
|
+
view.setBigUint64(challenge.length, BigInt(nonce), false);
|
|
1008
|
+
const hash = sha256(buf);
|
|
1009
|
+
if (checkDifficulty(hash, POW_DIFFICULTY)) {
|
|
1010
|
+
const proof = new Uint8Array(8 + 32 + 8);
|
|
1011
|
+
const pv = new DataView(proof.buffer);
|
|
1012
|
+
pv.setBigUint64(0, 32n, true);
|
|
1013
|
+
proof.set(hash, 8);
|
|
1014
|
+
pv.setBigUint64(40, BigInt(nonce), true);
|
|
1015
|
+
return { hash, nonce, proof };
|
|
1016
|
+
}
|
|
1017
|
+
if (nonce % 5e3 === 0 && nonce > 0) {
|
|
1018
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
throw new Error("PoW: exhausted nonce space");
|
|
1022
|
+
}
|
|
1023
|
+
function formatCost(totalCost) {
|
|
1024
|
+
const tnt = Number(totalCost) / WEI_PER_TNT;
|
|
1025
|
+
if (tnt === 0) return "0 TNT";
|
|
1026
|
+
if (tnt < 1e-3) return `${(tnt * 1e6).toFixed(2)} \u03BCTNT`;
|
|
1027
|
+
if (tnt < 0.01) return `${(tnt * 1e3).toFixed(2)} mTNT`;
|
|
1028
|
+
if (tnt < 1e3) return `${tnt.toFixed(4)} TNT`;
|
|
1029
|
+
return `${tnt.toLocaleString(void 0, { maximumFractionDigits: 2 })} TNT`;
|
|
1030
|
+
}
|
|
1031
|
+
function quoteConfidentiality(requireTee) {
|
|
1032
|
+
return requireTee ? 1 : 0;
|
|
1033
|
+
}
|
|
1034
|
+
function resourceKindToId(kind) {
|
|
1035
|
+
const mapped = RESOURCE_KIND_TO_ID[kind];
|
|
1036
|
+
if (mapped === void 0) {
|
|
1037
|
+
throw new Error(`Unsupported resource kind in quote: ${kind}`);
|
|
1038
|
+
}
|
|
1039
|
+
return mapped;
|
|
1040
|
+
}
|
|
1041
|
+
function mapJsonSecurityCommitment(sc) {
|
|
1042
|
+
return {
|
|
1043
|
+
asset: {
|
|
1044
|
+
kind: sc.asset?.kind ?? 0,
|
|
1045
|
+
token: sc.asset?.token ?? ZERO_ADDRESS
|
|
1046
|
+
},
|
|
1047
|
+
exposureBps: sc.exposure_bps ?? 0
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function mapJsonResourceCommitment(resource) {
|
|
1051
|
+
return {
|
|
1052
|
+
kind: resourceKindToId(String(resource.kind ?? "CPU")),
|
|
1053
|
+
count: BigInt(resource.count ?? 0)
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
var ZERO_ADDRESS_LOWER = ZERO_ADDRESS.toLowerCase();
|
|
1057
|
+
function useQuotes(operators, blueprintId, ttlBlocks, enabled, requester, requireTee = false) {
|
|
1058
|
+
if (enabled && (!requester || requester.toLowerCase() === ZERO_ADDRESS_LOWER)) {
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
"useQuotes: `requester` is required and must be a non-zero address when `enabled=true`. Pass `useAccount().address` from wagmi. tnt-core v0.13.0 contracts reject quotes whose requester is address(0) or != msg.sender."
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
const [quotes, setQuotes] = useState2([]);
|
|
1064
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
1065
|
+
const [isSolvingPow, setIsSolvingPow] = useState2(false);
|
|
1066
|
+
const [errors, setErrors] = useState2(/* @__PURE__ */ new Map());
|
|
1067
|
+
const [fetchKey, setFetchKey] = useState2(0);
|
|
1068
|
+
const refetch = useCallback2(() => setFetchKey((k) => k + 1), []);
|
|
1069
|
+
useEffect2(() => {
|
|
1070
|
+
if (!enabled || operators.length === 0) {
|
|
1071
|
+
setQuotes((prev) => prev.length === 0 ? prev : []);
|
|
1072
|
+
setErrors((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
let cancelled = false;
|
|
1076
|
+
setIsLoading(true);
|
|
1077
|
+
setIsSolvingPow(true);
|
|
1078
|
+
setQuotes([]);
|
|
1079
|
+
setErrors(/* @__PURE__ */ new Map());
|
|
1080
|
+
async function fetchQuotes() {
|
|
1081
|
+
const results = [];
|
|
1082
|
+
const errs = /* @__PURE__ */ new Map();
|
|
1083
|
+
const promises = operators.map(async (op) => {
|
|
1084
|
+
try {
|
|
1085
|
+
if (!op.rpcAddress) throw new Error("No RPC address registered");
|
|
1086
|
+
const rpcUrl2 = resolveOperatorRpc(op.rpcAddress);
|
|
1087
|
+
const timestamp = BigInt(Math.floor(Date.now() / 1e3));
|
|
1088
|
+
if (!cancelled) setIsSolvingPow(true);
|
|
1089
|
+
const { proof } = await solvePoW(blueprintId, timestamp);
|
|
1090
|
+
if (!cancelled) setIsSolvingPow(false);
|
|
1091
|
+
const response = await fetchPriceFromOperator(rpcUrl2, {
|
|
1092
|
+
blueprintId,
|
|
1093
|
+
ttlBlocks,
|
|
1094
|
+
proofOfWork: proof,
|
|
1095
|
+
challengeTimestamp: timestamp,
|
|
1096
|
+
requireTee,
|
|
1097
|
+
requester
|
|
1098
|
+
});
|
|
1099
|
+
if (!response) throw new Error("No quote returned from operator");
|
|
1100
|
+
if (!cancelled) results.push(response);
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
if (!cancelled) {
|
|
1103
|
+
errs.set(op.address, err instanceof Error ? err.message : String(err));
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
await Promise.allSettled(promises);
|
|
1108
|
+
if (!cancelled) {
|
|
1109
|
+
setQuotes(results);
|
|
1110
|
+
setErrors(errs);
|
|
1111
|
+
setIsLoading(false);
|
|
1112
|
+
setIsSolvingPow(false);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
fetchQuotes();
|
|
1116
|
+
return () => {
|
|
1117
|
+
cancelled = true;
|
|
1118
|
+
};
|
|
1119
|
+
}, [operators, blueprintId, ttlBlocks, enabled, fetchKey, requireTee, requester]);
|
|
1120
|
+
const totalCost = quotes.reduce((sum, q) => sum + q.totalCost, 0n);
|
|
1121
|
+
return { quotes, isLoading, isSolvingPow, errors, totalCost, refetch };
|
|
1122
|
+
}
|
|
1123
|
+
async function fetchPriceFromOperator(rpcUrl2, params) {
|
|
1124
|
+
try {
|
|
1125
|
+
const response = await fetch(`${rpcUrl2}/pricing/quote`, {
|
|
1126
|
+
method: "POST",
|
|
1127
|
+
headers: { "Content-Type": "application/json" },
|
|
1128
|
+
body: JSON.stringify({
|
|
1129
|
+
blueprint_id: String(params.blueprintId),
|
|
1130
|
+
ttl_blocks: String(params.ttlBlocks),
|
|
1131
|
+
proof_of_work: toHex(params.proofOfWork),
|
|
1132
|
+
challenge_timestamp: String(params.challengeTimestamp),
|
|
1133
|
+
require_tee: params.requireTee,
|
|
1134
|
+
// tnt-core v0.13.0: bind the quote to the future caller. Operators
|
|
1135
|
+
// sign this address into QuoteDetails; the contract enforces
|
|
1136
|
+
// `requester == msg.sender`.
|
|
1137
|
+
requester: params.requester,
|
|
1138
|
+
resource_requirements: DEFAULT_RESOURCE_REQUIREMENTS
|
|
1139
|
+
}),
|
|
1140
|
+
signal: AbortSignal.timeout(1e4)
|
|
1141
|
+
});
|
|
1142
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1143
|
+
const data = await response.json();
|
|
1144
|
+
return {
|
|
1145
|
+
operator: data.operator,
|
|
1146
|
+
totalCost: BigInt(data.total_cost ?? "0"),
|
|
1147
|
+
signature: data.signature ?? "0x",
|
|
1148
|
+
costRate: Number(data.cost_rate ?? 0),
|
|
1149
|
+
teeAttested: Boolean(data.tee_attested),
|
|
1150
|
+
teeProvider: data.tee_provider || void 0,
|
|
1151
|
+
details: {
|
|
1152
|
+
// Prefer the operator-signed value; fall back to the hook's input.
|
|
1153
|
+
// If the operator returns a mismatched requester the contract will
|
|
1154
|
+
// revert at submission, so callers should still verify equality.
|
|
1155
|
+
requester: data.details?.requester ?? params.requester,
|
|
1156
|
+
blueprintId: BigInt(data.details?.blueprint_id ?? params.blueprintId),
|
|
1157
|
+
ttlBlocks: BigInt(data.details?.ttl_blocks ?? params.ttlBlocks),
|
|
1158
|
+
totalCost: BigInt(data.details?.total_cost ?? "0"),
|
|
1159
|
+
timestamp: BigInt(data.details?.timestamp ?? params.challengeTimestamp),
|
|
1160
|
+
expiry: BigInt(data.details?.expiry ?? "0"),
|
|
1161
|
+
confidentiality: Number(data.details?.confidentiality ?? quoteConfidentiality(params.requireTee)),
|
|
1162
|
+
securityCommitments: (data.details?.security_commitments ?? []).map(mapJsonSecurityCommitment),
|
|
1163
|
+
resourceCommitments: (data.details?.resources ?? []).map(mapJsonResourceCommitment)
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
} catch {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/hooks/useJobPrice.ts
|
|
1172
|
+
import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef } from "react";
|
|
1173
|
+
import { toHex as toHex2 } from "viem";
|
|
1174
|
+
function resolveOperatorRpc2(raw) {
|
|
1175
|
+
if (typeof window === "undefined") return raw;
|
|
1176
|
+
const withProto = raw.includes("://") ? raw : `http://${raw}`;
|
|
1177
|
+
try {
|
|
1178
|
+
const url = new URL(withProto);
|
|
1179
|
+
const pageHost = window.location.hostname;
|
|
1180
|
+
const isNonRoutable = url.hostname.endsWith(".local") || !url.hostname.includes(".") || url.hostname === "127.0.0.1" || url.hostname === "localhost";
|
|
1181
|
+
if (isNonRoutable && pageHost !== url.hostname) {
|
|
1182
|
+
url.hostname = pageHost;
|
|
1183
|
+
}
|
|
1184
|
+
return url.toString().replace(/\/$/, "");
|
|
1185
|
+
} catch {
|
|
1186
|
+
return withProto;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
var ZERO_ADDRESS_LOWER2 = "0x0000000000000000000000000000000000000000";
|
|
1190
|
+
function assertRequester(requester, hookName, enabled) {
|
|
1191
|
+
if (!enabled) return;
|
|
1192
|
+
if (!requester || requester.toLowerCase() === ZERO_ADDRESS_LOWER2) {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
`${hookName}: \`requester\` is required and must be a non-zero address when \`enabled=true\`. Pass \`useAccount().address\` from wagmi. tnt-core v0.13.0 contracts reject quotes whose requester is address(0) or != msg.sender.`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function useJobPrice(operatorRpcUrl, serviceId, jobIndex, blueprintId, enabled, requester) {
|
|
1199
|
+
assertRequester(requester, "useJobPrice", enabled);
|
|
1200
|
+
const [quote, setQuote] = useState3(null);
|
|
1201
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1202
|
+
const [isSolvingPow, setIsSolvingPow] = useState3(false);
|
|
1203
|
+
const [error, setError] = useState3(null);
|
|
1204
|
+
const [fetchKey, setFetchKey] = useState3(0);
|
|
1205
|
+
const cancelledRef = useRef(false);
|
|
1206
|
+
const refetch = useCallback3(() => setFetchKey((k) => k + 1), []);
|
|
1207
|
+
useEffect3(() => {
|
|
1208
|
+
if (!enabled || !operatorRpcUrl || serviceId === 0n) {
|
|
1209
|
+
setQuote(null);
|
|
1210
|
+
setError(null);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
cancelledRef.current = false;
|
|
1214
|
+
setIsLoading(true);
|
|
1215
|
+
setError(null);
|
|
1216
|
+
setQuote(null);
|
|
1217
|
+
async function fetchJobQuote() {
|
|
1218
|
+
try {
|
|
1219
|
+
const rpcUrl2 = resolveOperatorRpc2(operatorRpcUrl);
|
|
1220
|
+
const timestamp = BigInt(Math.floor(Date.now() / 1e3));
|
|
1221
|
+
setIsSolvingPow(true);
|
|
1222
|
+
const { proof } = await solvePoW(blueprintId, timestamp);
|
|
1223
|
+
if (cancelledRef.current) return;
|
|
1224
|
+
setIsSolvingPow(false);
|
|
1225
|
+
const response = await fetch(`${rpcUrl2}/pricing/job-quote`, {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: { "Content-Type": "application/json" },
|
|
1228
|
+
body: JSON.stringify({
|
|
1229
|
+
service_id: String(serviceId),
|
|
1230
|
+
job_index: jobIndex,
|
|
1231
|
+
proof_of_work: toHex2(proof),
|
|
1232
|
+
challenge_timestamp: String(timestamp),
|
|
1233
|
+
// tnt-core v0.13.0: operators sign `requester` into JobQuoteDetails;
|
|
1234
|
+
// the contract enforces `requester == msg.sender` on submission.
|
|
1235
|
+
requester
|
|
1236
|
+
}),
|
|
1237
|
+
signal: AbortSignal.timeout(1e4)
|
|
1238
|
+
});
|
|
1239
|
+
if (!response.ok) {
|
|
1240
|
+
throw new Error(`HTTP ${response.status}: ${await response.text().catch(() => "Unknown error")}`);
|
|
1241
|
+
}
|
|
1242
|
+
const data = await response.json();
|
|
1243
|
+
if (cancelledRef.current) return;
|
|
1244
|
+
setQuote({
|
|
1245
|
+
requester: data.requester ?? requester,
|
|
1246
|
+
serviceId: BigInt(data.service_id ?? serviceId),
|
|
1247
|
+
jobIndex: data.job_index ?? jobIndex,
|
|
1248
|
+
price: BigInt(data.price ?? "0"),
|
|
1249
|
+
timestamp: BigInt(data.timestamp ?? timestamp),
|
|
1250
|
+
expiry: BigInt(data.expiry ?? "0"),
|
|
1251
|
+
signature: data.signature ?? "0x",
|
|
1252
|
+
operatorAddress: data.operator
|
|
1253
|
+
});
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
if (!cancelledRef.current) {
|
|
1256
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1257
|
+
}
|
|
1258
|
+
} finally {
|
|
1259
|
+
if (!cancelledRef.current) {
|
|
1260
|
+
setIsLoading(false);
|
|
1261
|
+
setIsSolvingPow(false);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
fetchJobQuote();
|
|
1266
|
+
return () => {
|
|
1267
|
+
cancelledRef.current = true;
|
|
1268
|
+
};
|
|
1269
|
+
}, [operatorRpcUrl, serviceId, jobIndex, blueprintId, enabled, fetchKey, requester]);
|
|
1270
|
+
const formattedPrice = quote ? formatCost(quote.price) : "--";
|
|
1271
|
+
return { quote, isLoading, isSolvingPow, error, formattedPrice, refetch };
|
|
1272
|
+
}
|
|
1273
|
+
function useJobPrices(operatorRpcUrl, serviceId, blueprintId, jobIndexes, enabled, requester) {
|
|
1274
|
+
assertRequester(requester, "useJobPrices", enabled);
|
|
1275
|
+
const [prices, setPrices] = useState3([]);
|
|
1276
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1277
|
+
const [error, setError] = useState3(null);
|
|
1278
|
+
const [fetchKey, setFetchKey] = useState3(0);
|
|
1279
|
+
const refetch = useCallback3(() => setFetchKey((k) => k + 1), []);
|
|
1280
|
+
useEffect3(() => {
|
|
1281
|
+
if (!enabled || !operatorRpcUrl || serviceId === 0n || jobIndexes.length === 0) {
|
|
1282
|
+
setPrices([]);
|
|
1283
|
+
setError(null);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
let cancelled = false;
|
|
1287
|
+
setIsLoading(true);
|
|
1288
|
+
setError(null);
|
|
1289
|
+
async function fetchAllPrices() {
|
|
1290
|
+
try {
|
|
1291
|
+
const rpcUrl2 = resolveOperatorRpc2(operatorRpcUrl);
|
|
1292
|
+
const timestamp = BigInt(Math.floor(Date.now() / 1e3));
|
|
1293
|
+
const { proof } = await solvePoW(blueprintId, timestamp);
|
|
1294
|
+
if (cancelled) return;
|
|
1295
|
+
const results = await Promise.allSettled(
|
|
1296
|
+
jobIndexes.map(async (job) => {
|
|
1297
|
+
const response = await fetch(`${rpcUrl2}/pricing/job-quote`, {
|
|
1298
|
+
method: "POST",
|
|
1299
|
+
headers: { "Content-Type": "application/json" },
|
|
1300
|
+
body: JSON.stringify({
|
|
1301
|
+
service_id: String(serviceId),
|
|
1302
|
+
job_index: job.index,
|
|
1303
|
+
proof_of_work: toHex2(proof),
|
|
1304
|
+
challenge_timestamp: String(timestamp),
|
|
1305
|
+
// tnt-core v0.13.0: see useJobPrice notes.
|
|
1306
|
+
requester
|
|
1307
|
+
}),
|
|
1308
|
+
signal: AbortSignal.timeout(1e4)
|
|
1309
|
+
});
|
|
1310
|
+
if (!response.ok) return null;
|
|
1311
|
+
return response.json();
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
if (cancelled) return;
|
|
1315
|
+
const entries = jobIndexes.map((job, i) => {
|
|
1316
|
+
const result = results[i];
|
|
1317
|
+
if (result.status === "fulfilled" && result.value) {
|
|
1318
|
+
const data = result.value;
|
|
1319
|
+
const price = BigInt(data.price ?? "0");
|
|
1320
|
+
return {
|
|
1321
|
+
jobIndex: job.index,
|
|
1322
|
+
jobName: job.name,
|
|
1323
|
+
price,
|
|
1324
|
+
formattedPrice: formatCost(price),
|
|
1325
|
+
mode: data.mode ?? "flat",
|
|
1326
|
+
quote: {
|
|
1327
|
+
requester: data.requester ?? requester,
|
|
1328
|
+
serviceId: BigInt(data.service_id ?? serviceId),
|
|
1329
|
+
jobIndex: job.index,
|
|
1330
|
+
price,
|
|
1331
|
+
timestamp: BigInt(data.timestamp ?? timestamp),
|
|
1332
|
+
expiry: BigInt(data.expiry ?? "0"),
|
|
1333
|
+
signature: data.signature ?? "0x",
|
|
1334
|
+
operatorAddress: data.operator
|
|
1335
|
+
},
|
|
1336
|
+
error: null
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
const estimatedPrice = BigInt(job.multiplier) * 1000000000000000n;
|
|
1340
|
+
return {
|
|
1341
|
+
jobIndex: job.index,
|
|
1342
|
+
jobName: job.name,
|
|
1343
|
+
price: estimatedPrice,
|
|
1344
|
+
formattedPrice: `~${formatCost(estimatedPrice)}`,
|
|
1345
|
+
mode: "flat",
|
|
1346
|
+
quote: null,
|
|
1347
|
+
error: "No RFQ response \u2014 showing estimate"
|
|
1348
|
+
};
|
|
1349
|
+
});
|
|
1350
|
+
setPrices(entries);
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
if (!cancelled) {
|
|
1353
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1354
|
+
}
|
|
1355
|
+
} finally {
|
|
1356
|
+
if (!cancelled) {
|
|
1357
|
+
setIsLoading(false);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
fetchAllPrices();
|
|
1362
|
+
return () => {
|
|
1363
|
+
cancelled = true;
|
|
1364
|
+
};
|
|
1365
|
+
}, [operatorRpcUrl, serviceId, blueprintId, jobIndexes, enabled, fetchKey, requester]);
|
|
1366
|
+
return { prices, isLoading, error, refetch };
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/hooks/useSubmitJob.ts
|
|
1370
|
+
import { useCallback as useCallback4, useState as useState4, useMemo as useMemo2 } from "react";
|
|
1371
|
+
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
|
|
1372
|
+
import { decodeEventLog } from "viem";
|
|
1373
|
+
import { useEffect as useEffect4 } from "react";
|
|
1374
|
+
function useSubmitJob() {
|
|
1375
|
+
const { address } = useAccount();
|
|
1376
|
+
const { writeContractAsync, data: hash, isPending: isSigning } = useWriteContract();
|
|
1377
|
+
const [status, setStatus] = useState4("idle");
|
|
1378
|
+
const [error, setError] = useState4(null);
|
|
1379
|
+
const [txHash, setTxHash] = useState4();
|
|
1380
|
+
const { data: receipt, isSuccess, isError } = useWaitForTransactionReceipt({
|
|
1381
|
+
hash: txHash
|
|
1382
|
+
});
|
|
1383
|
+
const callId = useMemo2(() => {
|
|
1384
|
+
if (!receipt?.logs) return null;
|
|
1385
|
+
for (const log of receipt.logs) {
|
|
1386
|
+
try {
|
|
1387
|
+
const decoded = decodeEventLog({
|
|
1388
|
+
abi: tangleJobsAbi,
|
|
1389
|
+
data: log.data,
|
|
1390
|
+
topics: log.topics
|
|
1391
|
+
});
|
|
1392
|
+
if (decoded.eventName === "JobCalled" && "callId" in decoded.args) {
|
|
1393
|
+
return Number(decoded.args.callId);
|
|
1394
|
+
}
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
}, [receipt]);
|
|
1400
|
+
useEffect4(() => {
|
|
1401
|
+
if (isSuccess && txHash) {
|
|
1402
|
+
setStatus("confirmed");
|
|
1403
|
+
updateTx(txHash, { status: "confirmed" });
|
|
1404
|
+
}
|
|
1405
|
+
if (isError && txHash) {
|
|
1406
|
+
setStatus("failed");
|
|
1407
|
+
updateTx(txHash, { status: "failed" });
|
|
1408
|
+
}
|
|
1409
|
+
}, [isSuccess, isError, txHash]);
|
|
1410
|
+
const submitJob = useCallback4(
|
|
1411
|
+
async (opts) => {
|
|
1412
|
+
if (!address) {
|
|
1413
|
+
setError("Wallet not connected");
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
const addrs = getAddresses();
|
|
1417
|
+
const label = opts.label ?? `Job #${opts.jobId}`;
|
|
1418
|
+
try {
|
|
1419
|
+
setStatus("signing");
|
|
1420
|
+
setError(null);
|
|
1421
|
+
const result = await writeContractAsync({
|
|
1422
|
+
address: addrs.jobs,
|
|
1423
|
+
abi: tangleJobsAbi,
|
|
1424
|
+
functionName: "submitJob",
|
|
1425
|
+
args: [opts.serviceId, opts.jobId, opts.args],
|
|
1426
|
+
value: opts.value
|
|
1427
|
+
});
|
|
1428
|
+
setTxHash(result);
|
|
1429
|
+
setStatus("pending");
|
|
1430
|
+
addTx(result, label, selectedChainIdStore.get());
|
|
1431
|
+
return result;
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
setStatus("failed");
|
|
1434
|
+
const msg = err?.shortMessage ?? err?.message ?? "Transaction failed";
|
|
1435
|
+
setError(msg);
|
|
1436
|
+
return void 0;
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
[address, writeContractAsync]
|
|
1440
|
+
);
|
|
1441
|
+
const reset = useCallback4(() => {
|
|
1442
|
+
setStatus("idle");
|
|
1443
|
+
setError(null);
|
|
1444
|
+
setTxHash(void 0);
|
|
1445
|
+
}, []);
|
|
1446
|
+
return {
|
|
1447
|
+
submitJob,
|
|
1448
|
+
reset,
|
|
1449
|
+
status,
|
|
1450
|
+
error,
|
|
1451
|
+
txHash,
|
|
1452
|
+
callId,
|
|
1453
|
+
isSigning,
|
|
1454
|
+
isConnected: !!address
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/hooks/useThemeValue.ts
|
|
1459
|
+
import { useStore } from "@nanostores/react";
|
|
1460
|
+
function useThemeValue() {
|
|
1461
|
+
return useStore(themeStore);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// src/hooks/useRegistrationCommand.ts
|
|
1465
|
+
import { useMemo as useMemo3 } from "react";
|
|
1466
|
+
function useRegistrationCommand(options) {
|
|
1467
|
+
const {
|
|
1468
|
+
blueprintId,
|
|
1469
|
+
rpcAddress,
|
|
1470
|
+
ecdsaPublicKey,
|
|
1471
|
+
rpcUrl: rpcUrl2,
|
|
1472
|
+
registrationInputs = "0x",
|
|
1473
|
+
mode = "cargo-tangle",
|
|
1474
|
+
servicesAddress = "TANGLE_CORE"
|
|
1475
|
+
} = options;
|
|
1476
|
+
const blueprintIdNumber = Number(blueprintId);
|
|
1477
|
+
const command = useMemo3(() => {
|
|
1478
|
+
if (mode === "cast") {
|
|
1479
|
+
return [
|
|
1480
|
+
`cast send ${servicesAddress} \\`,
|
|
1481
|
+
` "registerOperator(uint64,bytes,string,bytes)" \\`,
|
|
1482
|
+
` ${blueprintIdNumber} \\`,
|
|
1483
|
+
` ${ecdsaPublicKey} \\`,
|
|
1484
|
+
` ${rpcAddress} \\`,
|
|
1485
|
+
` ${registrationInputs} \\`,
|
|
1486
|
+
` --rpc-url ${rpcUrl2} \\`,
|
|
1487
|
+
` --private-key <YOUR_OPERATOR_KEY>`
|
|
1488
|
+
].join("\n");
|
|
1489
|
+
}
|
|
1490
|
+
const wsUrl = rpcUrl2.replace(/^http/, "ws");
|
|
1491
|
+
return [
|
|
1492
|
+
`cargo tangle blueprint register \\`,
|
|
1493
|
+
` --blueprint-id ${blueprintIdNumber} \\`,
|
|
1494
|
+
` --http-rpc-url ${rpcUrl2} \\`,
|
|
1495
|
+
` --ws-rpc-url ${wsUrl} \\`,
|
|
1496
|
+
` --keystore-uri <YOUR_KEYSTORE> \\`,
|
|
1497
|
+
` --rpc-address ${rpcAddress} \\`,
|
|
1498
|
+
` --ecdsa-public-key ${ecdsaPublicKey}`
|
|
1499
|
+
].join("\n");
|
|
1500
|
+
}, [blueprintIdNumber, ecdsaPublicKey, mode, registrationInputs, rpcAddress, rpcUrl2, servicesAddress]);
|
|
1501
|
+
const label = mode === "cast" ? "cast send (raw)" : "cargo-tangle (canonical)";
|
|
1502
|
+
return { command, label, blueprintIdNumber };
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
export {
|
|
1506
|
+
cn,
|
|
1507
|
+
resolveOperatorRpc,
|
|
1508
|
+
getEnvVar,
|
|
1509
|
+
mainnet,
|
|
1510
|
+
resolveRpcUrl,
|
|
1511
|
+
rpcUrl,
|
|
1512
|
+
createTangleLocalChain,
|
|
1513
|
+
tangleLocal,
|
|
1514
|
+
tangleTestnet,
|
|
1515
|
+
tangleMainnet,
|
|
1516
|
+
configureNetworks,
|
|
1517
|
+
getNetworks,
|
|
1518
|
+
allTangleChains,
|
|
1519
|
+
serializeWithBigInt,
|
|
1520
|
+
deserializeWithBigInt,
|
|
1521
|
+
persistedAtom,
|
|
1522
|
+
txListStore,
|
|
1523
|
+
pendingCount,
|
|
1524
|
+
addTx,
|
|
1525
|
+
updateTx,
|
|
1526
|
+
clearTxs,
|
|
1527
|
+
infraStore,
|
|
1528
|
+
updateInfra,
|
|
1529
|
+
getInfra,
|
|
1530
|
+
kTheme,
|
|
1531
|
+
DEFAULT_THEME,
|
|
1532
|
+
themeStore,
|
|
1533
|
+
themeIsDark,
|
|
1534
|
+
toggleTheme,
|
|
1535
|
+
tangleJobsAbi,
|
|
1536
|
+
tangleServicesAbi,
|
|
1537
|
+
tangleOperatorsAbi,
|
|
1538
|
+
selectedChainIdStore,
|
|
1539
|
+
sanitizeSelectedChainId,
|
|
1540
|
+
publicClientStore,
|
|
1541
|
+
getPublicClient,
|
|
1542
|
+
publicClient,
|
|
1543
|
+
getAddresses,
|
|
1544
|
+
encodeJobArgs,
|
|
1545
|
+
buildCanonicalBlueprintSlug,
|
|
1546
|
+
resolveBlueprintAppView,
|
|
1547
|
+
toBlueprintAppEntry,
|
|
1548
|
+
getBlueprintExperienceTierLabel,
|
|
1549
|
+
getBlueprintSlugPolicyLabel,
|
|
1550
|
+
getBlueprintSurfaceLabel,
|
|
1551
|
+
getBlueprintPublisherVerificationLabel,
|
|
1552
|
+
getExternalAppTrustLabel,
|
|
1553
|
+
isVerifiedBlueprintPublisher,
|
|
1554
|
+
canPublisherClaimSlug,
|
|
1555
|
+
isTrustedExternalAppHost,
|
|
1556
|
+
getBlueprintPath,
|
|
1557
|
+
getBlueprintServicePath,
|
|
1558
|
+
sanitizeBlueprintSlugPart,
|
|
1559
|
+
deriveBlueprintRequestedSlug,
|
|
1560
|
+
Card,
|
|
1561
|
+
CardHeader,
|
|
1562
|
+
CardTitle,
|
|
1563
|
+
CardDescription,
|
|
1564
|
+
CardContent,
|
|
1565
|
+
CardFooter,
|
|
1566
|
+
badgeVariants,
|
|
1567
|
+
Badge,
|
|
1568
|
+
buttonVariants,
|
|
1569
|
+
Button,
|
|
1570
|
+
BlueprintHostHero,
|
|
1571
|
+
BlueprintHostPanel,
|
|
1572
|
+
useJobForm,
|
|
1573
|
+
solvePoW,
|
|
1574
|
+
formatCost,
|
|
1575
|
+
useQuotes,
|
|
1576
|
+
useJobPrice,
|
|
1577
|
+
useJobPrices,
|
|
1578
|
+
useSubmitJob,
|
|
1579
|
+
useThemeValue,
|
|
1580
|
+
useRegistrationCommand
|
|
1581
|
+
};
|
|
1582
|
+
//# sourceMappingURL=chunk-F2QBCGUW.js.map
|