feat: mvp of call-contract.js 1fe86238
Steve · 2025-09-20 22:12 4 file(s) · +1075 −9
README.md +1 −1
45 45
46 46
**Components**
47 47
48 -
- [ ] Connect Wallet
48 +
- [x] Connect Wallet
49 49
- [ ] Interact with Contract
50 50
- [ ] TX Toasts?
51 51
- [ ] Contract State and Functions
site/index.html +27 −8
38 38
      stevedylandev/norns
39 39
    </a>
40 40
  </div>
41 -
  <div class="flex flex-col items-start gap-4">
42 -
    <div class="border border-white rounded-md sm:w-[400px] sm:h-[400px] h-[300px] w-[300px] flex items-center justify-center">
43 -
      <connect-wallet
44 -
        chain-id="0xaa36a7"
45 -
      ></connect-wallet>
41 +
  <div class="flex flex-col items-start gap-8">
42 +
    <div class="flex flex-col items-start gap-4">
43 +
      <div class="border border-white rounded-md sm:w-[400px] sm:h-[400px] h-[300px] w-[300px] flex items-center justify-center">
44 +
        <connect-wallet
45 +
          chain-id="0xaa36a7"
46 +
        ></connect-wallet>
47 +
      </div>
48 +
      <div class="text-xl text-[#ccc]">connect-wallet</div>
49 +
      <div class="bg-[#1e1e1e] border border-[#333] rounded-sm p-2 text-xs text-[#888] mt-2 text-center">
50 +
        npx norns-ui@latest add connect-wallet
51 +
      </div>
46 52
    </div>
47 -
    <div class="text-xl text-[#ccc]">connect-wallet</div>
48 -
    <div class="bg-[#1e1e1e] border border-[#333] rounded-sm p-2 text-xs text-[#888] mt-2 text-center">
49 -
      npx norns-ui@latest add connect-wallet
53 +
54 +
    <div class="flex flex-col items-start gap-4">
55 +
      <div class="border border-white rounded-md sm:w-[400px] sm:h-[400px] h-[300px] w-[300px] p-4 flex items-center justify-center">
56 +
        <contract-call
57 +
          contract-address="0x8C9EC9c13812C7F9F26AB934d4bF36206240dDA8"
58 +
          chain-id="0xaa36a7"
59 +
          method-name="increment"
60 +
          abi='[{"inputs":[],"name":"increment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"number","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newNumber","type":"uint256"}],"name":"setNumber","outputs":[],"stateMutability":"nonpayable","type":"function"}]'
61 +
          button-text="Increment"
62 +
        ></contract-call>
63 +
      </div>
64 +
      <div class="text-xl text-[#ccc]">contract-call</div>
65 +
      <div class="bg-[#1e1e1e] border border-[#333] rounded-sm p-2 text-xs text-[#888] mt-2 text-center">
66 +
        npx norns-ui@latest add contract-call
67 +
      </div>
50 68
    </div>
51 69
  </div>
52 70
  </main>
53 71
54 72
  <script src="../src/components/connect-wallet.js"></script>
73 +
  <script src="../src/components/contract-call.js"></script>
55 74
</body>
56 75
</html>
src/components/contract-call.js (added) +1045 −0
1 +
class ContractCall extends HTMLElement {
2 +
	// Constructor and lifecycle methods
3 +
	constructor() {
4 +
		super();
5 +
		this.attachShadow({ mode: "open" });
6 +
		this.loading = false;
7 +
		this.result = null;
8 +
		this.error = null;
9 +
		this.abi = null;
10 +
		this.methodData = null;
11 +
		this.contractAddress = "";
12 +
		this.chainId = "0x1";
13 +
		this.methodName = "";
14 +
		this.methodArgs = [];
15 +
		this.abiUrl = "";
16 +
		this.buttonText = "Call Contract";
17 +
		this.isReadOnly = false;
18 +
	}
19 +
20 +
	static get observedAttributes() {
21 +
		return [
22 +
			"contract-address",
23 +
			"chain-id",
24 +
			"method-name",
25 +
			"method-args",
26 +
			"abi-url",
27 +
			"abi",
28 +
			"button-text",
29 +
			"background",
30 +
			"foreground",
31 +
			"primary",
32 +
			"secondary",
33 +
			"border-radius",
34 +
		];
35 +
	}
36 +
37 +
	attributeChangedCallback(name, oldValue, newValue) {
38 +
		if (oldValue === newValue) return;
39 +
40 +
		switch (name) {
41 +
			case "contract-address":
42 +
				this.contractAddress = newValue || "";
43 +
				break;
44 +
			case "chain-id":
45 +
				this.chainId = newValue || "0x1";
46 +
				break;
47 +
			case "method-name":
48 +
				this.methodName = newValue || "";
49 +
				this.parseMethodFromAbi();
50 +
				break;
51 +
			case "method-args":
52 +
				try {
53 +
					this.methodArgs = newValue ? JSON.parse(newValue) : [];
54 +
				} catch (e) {
55 +
					console.error("Invalid method-args JSON:", e);
56 +
					this.methodArgs = [];
57 +
				}
58 +
				break;
59 +
			case "abi-url":
60 +
				this.abiUrl = newValue || "";
61 +
				if (this.abiUrl) {
62 +
					this.fetchAbi();
63 +
				}
64 +
				break;
65 +
			case "abi":
66 +
				try {
67 +
					this.abi = newValue ? JSON.parse(newValue) : null;
68 +
					this.parseMethodFromAbi();
69 +
				} catch (e) {
70 +
					console.error("Invalid ABI JSON:", e);
71 +
					this.abi = null;
72 +
				}
73 +
				break;
74 +
			case "button-text":
75 +
				this.buttonText = newValue || "Call Contract";
76 +
				break;
77 +
			default:
78 +
				if (
79 +
					[
80 +
						"background",
81 +
						"foreground",
82 +
						"primary",
83 +
						"secondary",
84 +
						"border-radius",
85 +
					].includes(name)
86 +
				) {
87 +
					this.render();
88 +
				}
89 +
		}
90 +
	}
91 +
92 +
	connectedCallback() {
93 +
		this.contractAddress = this.getAttribute("contract-address") || "";
94 +
		this.chainId = this.getAttribute("chain-id") || "0x1";
95 +
		this.methodName = this.getAttribute("method-name") || "";
96 +
		this.buttonText = this.getAttribute("button-text") || "Call Contract";
97 +
		this.abiUrl = this.getAttribute("abi-url") || "";
98 +
99 +
		try {
100 +
			const methodArgsAttr = this.getAttribute("method-args");
101 +
			this.methodArgs = methodArgsAttr ? JSON.parse(methodArgsAttr) : [];
102 +
		} catch (e) {
103 +
			console.error("Invalid method-args JSON:", e);
104 +
			this.methodArgs = [];
105 +
		}
106 +
107 +
		try {
108 +
			const abiAttr = this.getAttribute("abi");
109 +
			this.abi = abiAttr ? JSON.parse(abiAttr) : null;
110 +
		} catch (e) {
111 +
			console.error("Invalid ABI JSON:", e);
112 +
			this.abi = null;
113 +
		}
114 +
115 +
		if (this.abiUrl && !this.abi) {
116 +
			this.fetchAbi();
117 +
		} else if (this.abi) {
118 +
			this.parseMethodFromAbi();
119 +
		}
120 +
121 +
		this.render();
122 +
	}
123 +
124 +
	// ABI and method parsing
125 +
	async fetchAbi() {
126 +
		if (!this.abiUrl) return;
127 +
128 +
		try {
129 +
			this.loading = true;
130 +
			this.render();
131 +
132 +
			const response = await fetch(this.abiUrl);
133 +
			if (!response.ok) {
134 +
				throw new Error(`Failed to fetch ABI: ${response.statusText}`);
135 +
			}
136 +
137 +
			this.abi = await response.json();
138 +
			this.parseMethodFromAbi();
139 +
			this.loading = false;
140 +
			this.render();
141 +
142 +
			this.dispatchEvent(
143 +
				new CustomEvent("abi-loaded", {
144 +
					detail: { abi: this.abi },
145 +
				}),
146 +
			);
147 +
		} catch (error) {
148 +
			console.error("Failed to fetch ABI:", error);
149 +
			this.error = error.message;
150 +
			this.loading = false;
151 +
			this.render();
152 +
153 +
			this.dispatchEvent(
154 +
				new CustomEvent("abi-error", {
155 +
					detail: { error: error.message },
156 +
				}),
157 +
			);
158 +
		}
159 +
	}
160 +
161 +
	parseMethodFromAbi() {
162 +
		if (!this.abi || !this.methodName) return;
163 +
164 +
		const method = this.abi.find(
165 +
			(item) => item.type === "function" && item.name === this.methodName,
166 +
		);
167 +
168 +
		if (!method) {
169 +
			this.error = `Method '${this.methodName}' not found in ABI`;
170 +
			this.render();
171 +
			return;
172 +
		}
173 +
174 +
		this.methodData = method;
175 +
		this.isReadOnly =
176 +
			method.stateMutability === "view" || method.stateMutability === "pure";
177 +
		this.error = null;
178 +
		this.render();
179 +
	}
180 +
181 +
	// Contract interaction methods
182 +
	async callContract() {
183 +
		if (!window.ethereum) {
184 +
			this.error = "Please install a wallet extension like MetaMask";
185 +
			this.render();
186 +
			return;
187 +
		}
188 +
189 +
		if (!this.contractAddress || !this.methodName || !this.methodData) {
190 +
			this.error = "Missing required contract information";
191 +
			this.render();
192 +
			return;
193 +
		}
194 +
195 +
		try {
196 +
			this.loading = true;
197 +
			this.result = null;
198 +
			this.error = null;
199 +
			this.render();
200 +
201 +
			// Check if wallet is connected
202 +
			const accounts = await window.ethereum.request({
203 +
				method: "eth_accounts",
204 +
			});
205 +
206 +
			if (accounts.length === 0) {
207 +
				throw new Error("Please connect your wallet first");
208 +
			}
209 +
210 +
			// Check chain
211 +
			const currentChainId = await window.ethereum.request({
212 +
				method: "eth_chainId",
213 +
			});
214 +
215 +
			if (currentChainId !== this.chainId) {
216 +
				await this.switchChain();
217 +
			}
218 +
219 +
			// Encode method call
220 +
			const methodSignature = this.encodeMethodSignature();
221 +
			const encodedArgs = this.encodeArguments();
222 +
			const data = methodSignature + encodedArgs;
223 +
224 +
			let result;
225 +
226 +
			if (this.isReadOnly) {
227 +
				// For read-only methods, use eth_call
228 +
				result = await window.ethereum.request({
229 +
					method: "eth_call",
230 +
					params: [
231 +
						{
232 +
							to: this.contractAddress,
233 +
							data: data,
234 +
						},
235 +
						"latest",
236 +
					],
237 +
				});
238 +
				this.result = this.decodeResult(result);
239 +
			} else {
240 +
				// For write methods, send transaction
241 +
				const txHash = await window.ethereum.request({
242 +
					method: "eth_sendTransaction",
243 +
					params: [
244 +
						{
245 +
							from: accounts[0],
246 +
							to: this.contractAddress,
247 +
							data: data,
248 +
							gas: "0x30D40", // 200000 in hex - higher gas limit for contract calls
249 +
							gasPrice: "0x4A817C800", // 20 gwei in hex
250 +
						},
251 +
					],
252 +
				});
253 +
				this.result = { transactionHash: txHash };
254 +
			}
255 +
256 +
			this.loading = false;
257 +
			this.render();
258 +
259 +
			this.dispatchEvent(
260 +
				new CustomEvent("contract-call-success", {
261 +
					detail: {
262 +
						result: this.result,
263 +
						method: this.methodName,
264 +
						args: this.methodArgs,
265 +
						isReadOnly: this.isReadOnly,
266 +
					},
267 +
				}),
268 +
			);
269 +
		} catch (error) {
270 +
			console.error("Contract call failed:", error);
271 +
			this.error = error.message;
272 +
			this.loading = false;
273 +
			this.render();
274 +
275 +
			this.dispatchEvent(
276 +
				new CustomEvent("contract-call-error", {
277 +
					detail: { error: error.message },
278 +
				}),
279 +
			);
280 +
		}
281 +
	}
282 +
283 +
	async switchChain() {
284 +
		try {
285 +
			await window.ethereum.request({
286 +
				method: "wallet_switchEthereumChain",
287 +
				params: [{ chainId: this.chainId }],
288 +
			});
289 +
		} catch (switchError) {
290 +
			throw new Error(`Failed to switch chain: ${switchError.message}`);
291 +
		}
292 +
	}
293 +
294 +
	// Encoding and decoding methods
295 +
	encodeMethodSignature() {
296 +
		const inputs = this.methodData.inputs || [];
297 +
		const types = inputs.map((input) => input.type).join(",");
298 +
		const signature = `${this.methodName}(${types})`;
299 +
300 +
		// Use proper keccak256 hash - for production use a crypto library
301 +
		const hash = this.keccak256(signature);
302 +
		return hash.slice(0, 10); // First 4 bytes (function selector)
303 +
	}
304 +
305 +
	encodeArguments() {
306 +
		const inputs = this.methodData.inputs || [];
307 +
308 +
		// If method has no inputs or no args provided, return empty string
309 +
		if (!inputs.length || !this.methodArgs.length) return "";
310 +
311 +
		// This is a simplified encoding - in production, use ethers.js or web3.js
312 +
		let encoded = "";
313 +
314 +
		for (let i = 0; i < this.methodArgs.length; i++) {
315 +
			const arg = this.methodArgs[i];
316 +
			const input = inputs[i];
317 +
318 +
			if (!input) continue;
319 +
320 +
			encoded += this.encodeValue(arg, input.type);
321 +
		}
322 +
323 +
		return encoded;
324 +
	}
325 +
326 +
	encodeValue(value, type) {
327 +
		// Simplified encoding - use proper ABI encoding library in production
328 +
		if (type === "uint256" || type === "uint") {
329 +
			return BigInt(value).toString(16).padStart(64, "0");
330 +
		} else if (type === "address") {
331 +
			return value.toLowerCase().replace("0x", "").padStart(64, "0");
332 +
		} else if (type === "string") {
333 +
			const hex = Array.from(new TextEncoder().encode(value))
334 +
				.map((b) => b.toString(16).padStart(2, "0"))
335 +
				.join("");
336 +
			return hex.padEnd(64, "0");
337 +
		}
338 +
		return "".padStart(64, "0");
339 +
	}
340 +
341 +
	decodeResult(result) {
342 +
		if (!result || result === "0x") return null;
343 +
344 +
		// Simplified decoding - use proper ABI decoding library in production
345 +
		const outputs = this.methodData.outputs || [];
346 +
		if (outputs.length === 0) return result;
347 +
348 +
		const output = outputs[0];
349 +
		const data = result.replace("0x", "");
350 +
351 +
		if (output.type === "uint256" || output.type === "uint") {
352 +
			return BigInt("0x" + data).toString();
353 +
		} else if (output.type === "address") {
354 +
			return "0x" + data.slice(-40);
355 +
		} else if (output.type === "string") {
356 +
			// Simplified string decoding
357 +
			try {
358 +
				const bytes =
359 +
					data.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) || [];
360 +
				return new TextDecoder().decode(new Uint8Array(bytes));
361 +
			} catch (e) {
362 +
				return data;
363 +
			}
364 +
		}
365 +
366 +
		return result;
367 +
	}
368 +
369 +
	keccak256(input) {
370 +
		// Use the keccak256 function defined at the bottom of this file
371 +
		return keccak256(input);
372 +
	}
373 +
374 +
	// UI helper methods
375 +
	getCSSVariable(name, defaultValue) {
376 +
		return this.getAttribute(name) || defaultValue;
377 +
	}
378 +
379 +
	getStatusColor() {
380 +
		if (this.error) return "#ef4444";
381 +
		if (this.result) return "#22c55e";
382 +
		return this.getCSSVariable("primary", "#5F8787");
383 +
	}
384 +
385 +
	getStatusText() {
386 +
		if (this.loading) return "Processing...";
387 +
		if (this.error) return `Error: ${this.error}`;
388 +
		if (this.result) {
389 +
			if (this.isReadOnly) {
390 +
				return `Result: ${JSON.stringify(this.result)}`;
391 +
			} else {
392 +
				return `Transaction: ${this.result.transactionHash}`;
393 +
			}
394 +
		}
395 +
		return "";
396 +
	}
397 +
398 +
	// Render methods
399 +
	render() {
400 +
		const background = this.getCSSVariable("background", "#232323");
401 +
		const foreground = this.getCSSVariable("foreground", "#ffffff");
402 +
		const primary = this.getCSSVariable("primary", "#5F8787");
403 +
		const secondary = this.getCSSVariable("secondary", "#6F9797");
404 +
		const borderRadius = this.getCSSVariable("border-radius", "4px");
405 +
406 +
		this.shadowRoot.innerHTML = `
407 +
			<style>
408 +
				:host {
409 +
					--color-background: ${background};
410 +
					--color-foreground: ${foreground};
411 +
					--color-primary: ${primary};
412 +
					--color-secondary: ${secondary};
413 +
					--border-radius: ${borderRadius};
414 +
					display: inline-block;
415 +
					font-family: sans-serif;
416 +
				}
417 +
418 +
				.container {
419 +
					display: flex;
420 +
					flex-direction: column;
421 +
					gap: 12px;
422 +
					padding: 16px;
423 +
					background: var(--color-background);
424 +
					border: 1px solid rgba(255, 255, 255, 0.1);
425 +
					border-radius: var(--border-radius);
426 +
					color: var(--color-foreground);
427 +
					min-width: 300px;
428 +
				}
429 +
430 +
				.contract-info {
431 +
					display: flex;
432 +
					flex-direction: column;
433 +
					gap: 8px;
434 +
					font-size: 14px;
435 +
					opacity: 0.8;
436 +
				}
437 +
438 +
				.info-row {
439 +
					display: flex;
440 +
					justify-content: space-between;
441 +
					align-items: center;
442 +
				}
443 +
444 +
				.info-label {
445 +
					font-weight: 600;
446 +
					min-width: 80px;
447 +
				}
448 +
449 +
				.info-value {
450 +
					font-family: monospace;
451 +
					word-break: break-all;
452 +
					text-align: right;
453 +
					flex: 1;
454 +
					margin-left: 12px;
455 +
				}
456 +
457 +
				.method-info {
458 +
					background: rgba(255, 255, 255, 0.05);
459 +
					border-radius: calc(var(--border-radius) / 2);
460 +
					padding: 12px;
461 +
				}
462 +
463 +
				.method-signature {
464 +
					font-family: monospace;
465 +
					font-size: 13px;
466 +
					color: var(--color-primary);
467 +
					margin-bottom: 8px;
468 +
				}
469 +
470 +
				button {
471 +
					padding: 12px 20px;
472 +
					background: var(--color-primary);
473 +
					color: var(--color-foreground);
474 +
					border: none;
475 +
					border-radius: var(--border-radius);
476 +
					cursor: pointer;
477 +
					font-size: 16px;
478 +
					font-weight: 600;
479 +
					transition: background-color 0.3s ease;
480 +
				}
481 +
482 +
				button:hover:not(:disabled) {
483 +
					background: var(--color-secondary);
484 +
				}
485 +
486 +
				button:disabled {
487 +
					opacity: 0.7;
488 +
					cursor: not-allowed;
489 +
				}
490 +
491 +
				.status {
492 +
					padding: 12px;
493 +
					border-radius: calc(var(--border-radius) / 2);
494 +
					font-size: 14px;
495 +
					word-break: break-all;
496 +
					border: 1px solid;
497 +
					border-color: ${this.getStatusColor()};
498 +
					background: ${this.getStatusColor()}20;
499 +
					color: ${this.getStatusColor()};
500 +
				}
501 +
502 +
				.loading {
503 +
					display: flex;
504 +
					align-items: center;
505 +
					gap: 8px;
506 +
				}
507 +
508 +
				.spinner {
509 +
					width: 16px;
510 +
					height: 16px;
511 +
					border: 2px solid rgba(255, 255, 255, 0.3);
512 +
					border-top: 2px solid var(--color-foreground);
513 +
					border-radius: 50%;
514 +
					animation: spin 1s linear infinite;
515 +
				}
516 +
517 +
				@keyframes spin {
518 +
					from { transform: rotate(0deg); }
519 +
					to { transform: rotate(360deg); }
520 +
				}
521 +
522 +
				.error {
523 +
					color: #ef4444;
524 +
				}
525 +
526 +
				.success {
527 +
					color: #22c55e;
528 +
				}
529 +
			</style>
530 +
		`;
531 +
532 +
		const container = document.createElement("div");
533 +
		container.className = "container";
534 +
535 +
		// Contract info section
536 +
		const contractInfo = document.createElement("div");
537 +
		contractInfo.className = "contract-info";
538 +
539 +
		if (this.contractAddress) {
540 +
			contractInfo.innerHTML = `
541 +
				<div class="info-row">
542 +
					<span class="info-label">Contract:</span>
543 +
					<span class="info-value">${this.contractAddress}</span>
544 +
				</div>
545 +
			`;
546 +
		}
547 +
548 +
		// Method info section
549 +
		if (this.methodData) {
550 +
			const methodInfo = document.createElement("div");
551 +
			methodInfo.className = "method-info";
552 +
553 +
			const inputs = this.methodData.inputs || [];
554 +
			const inputTypes =
555 +
				inputs.length > 0
556 +
					? inputs.map((input) => `${input.type} ${input.name}`).join(", ")
557 +
					: "";
558 +
			const signature = `${this.methodName}(${inputTypes})`;
559 +
560 +
			methodInfo.innerHTML = `
561 +
				<div class="method-signature">${signature}</div>
562 +
			`;
563 +
564 +
			container.appendChild(methodInfo);
565 +
		}
566 +
567 +
		container.appendChild(contractInfo);
568 +
569 +
		// Button
570 +
		const button = document.createElement("button");
571 +
		button.disabled = this.loading || !this.contractAddress || !this.methodData;
572 +
573 +
		if (this.loading) {
574 +
			button.innerHTML = `
575 +
				<div class="loading">
576 +
					<div class="spinner"></div>
577 +
					<span>Processing...</span>
578 +
				</div>
579 +
			`;
580 +
		} else {
581 +
			button.textContent = this.buttonText;
582 +
		}
583 +
584 +
		button.addEventListener("click", () => this.callContract());
585 +
		container.appendChild(button);
586 +
587 +
		// Status section
588 +
		const statusText = this.getStatusText();
589 +
		if (statusText) {
590 +
			const status = document.createElement("div");
591 +
			status.className = "status";
592 +
			status.textContent = statusText;
593 +
			container.appendChild(status);
594 +
		}
595 +
596 +
		this.shadowRoot.appendChild(container);
597 +
	}
598 +
}
599 +
600 +
customElements.define("contract-call", ContractCall);
601 +
602 +
/**
603 +
 * Minimal Keccak256 implementation for browser use
604 +
 * Based on js-sha3 by emn178
605 +
 * https://github.com/emn178/js-sha3
606 +
 */
607 +
608 +
// Keccak constants
609 +
const KECCAK_PADDING = [1, 256, 65536, 16777216];
610 +
const SHIFT = [0, 8, 16, 24];
611 +
const RC = [
612 +
	1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0,
613 +
	2147483649, 0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0,
614 +
	2147516425, 0, 2147483658, 0, 2147516555, 0, 139, 2147483648, 32905,
615 +
	2147483648, 32771, 2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0,
616 +
	2147483658, 2147483648, 2147516545, 2147483648, 32896, 2147483648, 2147483649,
617 +
	0, 2147516424, 2147483648,
618 +
];
619 +
620 +
function Keccak(bits) {
621 +
	this.blocks = [];
622 +
	this.s = [];
623 +
	this.padding = KECCAK_PADDING;
624 +
	this.outputBits = bits;
625 +
	this.reset = true;
626 +
	this.finalized = false;
627 +
	this.block = 0;
628 +
	this.start = 0;
629 +
	this.blockCount = (1600 - (bits << 1)) >> 5;
630 +
	this.byteCount = this.blockCount << 2;
631 +
	this.outputBlocks = bits >> 5;
632 +
	this.extraBytes = (bits & 31) >> 3;
633 +
634 +
	for (var i = 0; i < 50; ++i) {
635 +
		this.s[i] = 0;
636 +
	}
637 +
}
638 +
639 +
Keccak.prototype.update = function (message) {
640 +
	if (this.finalized) {
641 +
		throw new Error("finalized");
642 +
	}
643 +
	var notString,
644 +
		type = typeof message;
645 +
	if (type !== "string") {
646 +
		if (type === "object") {
647 +
			if (message === null) {
648 +
				throw new Error("message is null");
649 +
			} else if (
650 +
				Array.isArray(message) ||
651 +
				(typeof ArrayBuffer !== "undefined" &&
652 +
					message.constructor === ArrayBuffer)
653 +
			) {
654 +
				message = new Uint8Array(message);
655 +
			} else if (!ArrayBuffer.isView(message)) {
656 +
				throw new Error("invalid message type");
657 +
			}
658 +
		} else {
659 +
			throw new Error("invalid message type");
660 +
		}
661 +
		notString = true;
662 +
	}
663 +
	var blocks = this.blocks,
664 +
		byteCount = this.byteCount,
665 +
		length = message.length,
666 +
		blockCount = this.blockCount,
667 +
		index = 0,
668 +
		s = this.s,
669 +
		i,
670 +
		code;
671 +
672 +
	while (index < length) {
673 +
		if (this.reset) {
674 +
			this.reset = false;
675 +
			blocks[0] = this.block;
676 +
			for (i = 1; i < blockCount + 1; ++i) {
677 +
				blocks[i] = 0;
678 +
			}
679 +
		}
680 +
		if (notString) {
681 +
			for (i = this.start; index < length && i < byteCount; ++index) {
682 +
				blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
683 +
			}
684 +
		} else {
685 +
			for (i = this.start; index < length && i < byteCount; ++index) {
686 +
				code = message.charCodeAt(index);
687 +
				if (code < 0x80) {
688 +
					blocks[i >> 2] |= code << SHIFT[i++ & 3];
689 +
				} else if (code < 0x800) {
690 +
					blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
691 +
					blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
692 +
				} else if (code < 0xd800 || code >= 0xe000) {
693 +
					blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
694 +
					blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
695 +
					blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
696 +
				} else {
697 +
					code =
698 +
						0x10000 +
699 +
						(((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
700 +
					blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
701 +
					blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
702 +
					blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
703 +
					blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
704 +
				}
705 +
			}
706 +
		}
707 +
708 +
		this.lastByteIndex = i;
709 +
		if (i >= byteCount) {
710 +
			this.start = i - byteCount;
711 +
			this.block = blocks[blockCount];
712 +
			for (i = 0; i < blockCount; ++i) {
713 +
				s[i] ^= blocks[i];
714 +
			}
715 +
			f(s);
716 +
			this.reset = true;
717 +
		} else {
718 +
			this.start = i;
719 +
		}
720 +
	}
721 +
	return this;
722 +
};
723 +
724 +
Keccak.prototype.finalize = function () {
725 +
	if (this.finalized) {
726 +
		return;
727 +
	}
728 +
	this.finalized = true;
729 +
	var blocks = this.blocks,
730 +
		i = this.lastByteIndex,
731 +
		blockCount = this.blockCount,
732 +
		s = this.s;
733 +
	blocks[i >> 2] |= this.padding[i & 3];
734 +
	if (this.lastByteIndex === this.byteCount) {
735 +
		blocks[0] = blocks[blockCount];
736 +
		for (i = 1; i < blockCount + 1; ++i) {
737 +
			blocks[i] = 0;
738 +
		}
739 +
	}
740 +
	blocks[blockCount - 1] |= 0x80000000;
741 +
	for (i = 0; i < blockCount; ++i) {
742 +
		s[i] ^= blocks[i];
743 +
	}
744 +
	f(s);
745 +
};
746 +
747 +
Keccak.prototype.toString = Keccak.prototype.hex = function () {
748 +
	this.finalize();
749 +
750 +
	var blockCount = this.blockCount,
751 +
		s = this.s,
752 +
		outputBlocks = this.outputBlocks,
753 +
		extraBytes = this.extraBytes,
754 +
		i = 0,
755 +
		j = 0;
756 +
	var hex = "",
757 +
		block;
758 +
	while (j < outputBlocks) {
759 +
		for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
760 +
			block = s[i];
761 +
			hex +=
762 +
				HEX_CHARS[(block >> 4) & 0x0f] +
763 +
				HEX_CHARS[block & 0x0f] +
764 +
				HEX_CHARS[(block >> 12) & 0x0f] +
765 +
				HEX_CHARS[(block >> 8) & 0x0f] +
766 +
				HEX_CHARS[(block >> 20) & 0x0f] +
767 +
				HEX_CHARS[(block >> 16) & 0x0f] +
768 +
				HEX_CHARS[(block >> 28) & 0x0f] +
769 +
				HEX_CHARS[(block >> 24) & 0x0f];
770 +
		}
771 +
		if (j % blockCount === 0) {
772 +
			f(s);
773 +
			i = 0;
774 +
		}
775 +
	}
776 +
	if (extraBytes) {
777 +
		block = s[i];
778 +
		hex += HEX_CHARS[(block >> 4) & 0x0f] + HEX_CHARS[block & 0x0f];
779 +
		if (extraBytes > 1) {
780 +
			hex += HEX_CHARS[(block >> 12) & 0x0f] + HEX_CHARS[(block >> 8) & 0x0f];
781 +
		}
782 +
		if (extraBytes > 2) {
783 +
			hex += HEX_CHARS[(block >> 20) & 0x0f] + HEX_CHARS[(block >> 16) & 0x0f];
784 +
		}
785 +
	}
786 +
	return hex;
787 +
};
788 +
789 +
const HEX_CHARS = "0123456789abcdef".split("");
790 +
791 +
var f = function (s) {
792 +
	var h,
793 +
		l,
794 +
		n,
795 +
		c0,
796 +
		c1,
797 +
		c2,
798 +
		c3,
799 +
		c4,
800 +
		c5,
801 +
		c6,
802 +
		c7,
803 +
		c8,
804 +
		c9,
805 +
		b0,
806 +
		b1,
807 +
		b2,
808 +
		b3,
809 +
		b4,
810 +
		b5,
811 +
		b6,
812 +
		b7,
813 +
		b8,
814 +
		b9,
815 +
		b10,
816 +
		b11,
817 +
		b12,
818 +
		b13,
819 +
		b14,
820 +
		b15,
821 +
		b16,
822 +
		b17,
823 +
		b18,
824 +
		b19,
825 +
		b20,
826 +
		b21,
827 +
		b22,
828 +
		b23,
829 +
		b24,
830 +
		b25,
831 +
		b26,
832 +
		b27,
833 +
		b28,
834 +
		b29,
835 +
		b30,
836 +
		b31,
837 +
		b32,
838 +
		b33,
839 +
		b34,
840 +
		b35,
841 +
		b36,
842 +
		b37,
843 +
		b38,
844 +
		b39,
845 +
		b40,
846 +
		b41,
847 +
		b42,
848 +
		b43,
849 +
		b44,
850 +
		b45,
851 +
		b46,
852 +
		b47,
853 +
		b48,
854 +
		b49;
855 +
	for (n = 0; n < 48; n += 2) {
856 +
		c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
857 +
		c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
858 +
		c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
859 +
		c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
860 +
		c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
861 +
		c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
862 +
		c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
863 +
		c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
864 +
		c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
865 +
		c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
866 +
867 +
		h = c8 ^ ((c2 << 1) | (c3 >>> 31));
868 +
		l = c9 ^ ((c3 << 1) | (c2 >>> 31));
869 +
		s[0] ^= h;
870 +
		s[1] ^= l;
871 +
		s[10] ^= h;
872 +
		s[11] ^= l;
873 +
		s[20] ^= h;
874 +
		s[21] ^= l;
875 +
		s[30] ^= h;
876 +
		s[31] ^= l;
877 +
		s[40] ^= h;
878 +
		s[41] ^= l;
879 +
		h = c0 ^ ((c4 << 1) | (c5 >>> 31));
880 +
		l = c1 ^ ((c5 << 1) | (c4 >>> 31));
881 +
		s[2] ^= h;
882 +
		s[3] ^= l;
883 +
		s[12] ^= h;
884 +
		s[13] ^= l;
885 +
		s[22] ^= h;
886 +
		s[23] ^= l;
887 +
		s[32] ^= h;
888 +
		s[33] ^= l;
889 +
		s[42] ^= h;
890 +
		s[43] ^= l;
891 +
		h = c2 ^ ((c6 << 1) | (c7 >>> 31));
892 +
		l = c3 ^ ((c7 << 1) | (c6 >>> 31));
893 +
		s[4] ^= h;
894 +
		s[5] ^= l;
895 +
		s[14] ^= h;
896 +
		s[15] ^= l;
897 +
		s[24] ^= h;
898 +
		s[25] ^= l;
899 +
		s[34] ^= h;
900 +
		s[35] ^= l;
901 +
		s[44] ^= h;
902 +
		s[45] ^= l;
903 +
		h = c4 ^ ((c8 << 1) | (c9 >>> 31));
904 +
		l = c5 ^ ((c9 << 1) | (c8 >>> 31));
905 +
		s[6] ^= h;
906 +
		s[7] ^= l;
907 +
		s[16] ^= h;
908 +
		s[17] ^= l;
909 +
		s[26] ^= h;
910 +
		s[27] ^= l;
911 +
		s[36] ^= h;
912 +
		s[37] ^= l;
913 +
		s[46] ^= h;
914 +
		s[47] ^= l;
915 +
		h = c6 ^ ((c0 << 1) | (c1 >>> 31));
916 +
		l = c7 ^ ((c1 << 1) | (c0 >>> 31));
917 +
		s[8] ^= h;
918 +
		s[9] ^= l;
919 +
		s[18] ^= h;
920 +
		s[19] ^= l;
921 +
		s[28] ^= h;
922 +
		s[29] ^= l;
923 +
		s[38] ^= h;
924 +
		s[39] ^= l;
925 +
		s[48] ^= h;
926 +
		s[49] ^= l;
927 +
928 +
		b0 = s[0];
929 +
		b1 = s[1];
930 +
		b32 = (s[11] << 4) | (s[10] >>> 28);
931 +
		b33 = (s[10] << 4) | (s[11] >>> 28);
932 +
		b14 = (s[20] << 3) | (s[21] >>> 29);
933 +
		b15 = (s[21] << 3) | (s[20] >>> 29);
934 +
		b46 = (s[31] << 9) | (s[30] >>> 23);
935 +
		b47 = (s[30] << 9) | (s[31] >>> 23);
936 +
		b28 = (s[40] << 18) | (s[41] >>> 14);
937 +
		b29 = (s[41] << 18) | (s[40] >>> 14);
938 +
		b20 = (s[2] << 1) | (s[3] >>> 31);
939 +
		b21 = (s[3] << 1) | (s[2] >>> 31);
940 +
		b2 = (s[13] << 12) | (s[12] >>> 20);
941 +
		b3 = (s[12] << 12) | (s[13] >>> 20);
942 +
		b34 = (s[22] << 10) | (s[23] >>> 22);
943 +
		b35 = (s[23] << 10) | (s[22] >>> 22);
944 +
		b16 = (s[33] << 13) | (s[32] >>> 19);
945 +
		b17 = (s[32] << 13) | (s[33] >>> 19);
946 +
		b48 = (s[42] << 2) | (s[43] >>> 30);
947 +
		b49 = (s[43] << 2) | (s[42] >>> 30);
948 +
		b40 = (s[5] << 30) | (s[4] >>> 2);
949 +
		b41 = (s[4] << 30) | (s[5] >>> 2);
950 +
		b22 = (s[14] << 6) | (s[15] >>> 26);
951 +
		b23 = (s[15] << 6) | (s[14] >>> 26);
952 +
		b4 = (s[25] << 11) | (s[24] >>> 21);
953 +
		b5 = (s[24] << 11) | (s[25] >>> 21);
954 +
		b36 = (s[34] << 15) | (s[35] >>> 17);
955 +
		b37 = (s[35] << 15) | (s[34] >>> 17);
956 +
		b18 = (s[45] << 29) | (s[44] >>> 3);
957 +
		b19 = (s[44] << 29) | (s[45] >>> 3);
958 +
		b10 = (s[6] << 28) | (s[7] >>> 4);
959 +
		b11 = (s[7] << 28) | (s[6] >>> 4);
960 +
		b42 = (s[17] << 23) | (s[16] >>> 9);
961 +
		b43 = (s[16] << 23) | (s[17] >>> 9);
962 +
		b24 = (s[26] << 25) | (s[27] >>> 7);
963 +
		b25 = (s[27] << 25) | (s[26] >>> 7);
964 +
		b6 = (s[36] << 21) | (s[37] >>> 11);
965 +
		b7 = (s[37] << 21) | (s[36] >>> 11);
966 +
		b38 = (s[47] << 24) | (s[46] >>> 8);
967 +
		b39 = (s[46] << 24) | (s[47] >>> 8);
968 +
		b30 = (s[8] << 27) | (s[9] >>> 5);
969 +
		b31 = (s[9] << 27) | (s[8] >>> 5);
970 +
		b12 = (s[18] << 20) | (s[19] >>> 12);
971 +
		b13 = (s[19] << 20) | (s[18] >>> 12);
972 +
		b44 = (s[29] << 7) | (s[28] >>> 25);
973 +
		b45 = (s[28] << 7) | (s[29] >>> 25);
974 +
		b26 = (s[38] << 8) | (s[39] >>> 24);
975 +
		b27 = (s[39] << 8) | (s[38] >>> 24);
976 +
		b8 = (s[48] << 14) | (s[49] >>> 18);
977 +
		b9 = (s[49] << 14) | (s[48] >>> 18);
978 +
979 +
		s[0] = b0 ^ (~b2 & b4);
980 +
		s[1] = b1 ^ (~b3 & b5);
981 +
		s[10] = b10 ^ (~b12 & b14);
982 +
		s[11] = b11 ^ (~b13 & b15);
983 +
		s[20] = b20 ^ (~b22 & b24);
984 +
		s[21] = b21 ^ (~b23 & b25);
985 +
		s[30] = b30 ^ (~b32 & b34);
986 +
		s[31] = b31 ^ (~b33 & b35);
987 +
		s[40] = b40 ^ (~b42 & b44);
988 +
		s[41] = b41 ^ (~b43 & b45);
989 +
		s[2] = b2 ^ (~b4 & b6);
990 +
		s[3] = b3 ^ (~b5 & b7);
991 +
		s[12] = b12 ^ (~b14 & b16);
992 +
		s[13] = b13 ^ (~b15 & b17);
993 +
		s[22] = b22 ^ (~b24 & b26);
994 +
		s[23] = b23 ^ (~b25 & b27);
995 +
		s[32] = b32 ^ (~b34 & b36);
996 +
		s[33] = b33 ^ (~b35 & b37);
997 +
		s[42] = b42 ^ (~b44 & b46);
998 +
		s[43] = b43 ^ (~b45 & b47);
999 +
		s[4] = b4 ^ (~b6 & b8);
1000 +
		s[5] = b5 ^ (~b7 & b9);
1001 +
		s[14] = b14 ^ (~b16 & b18);
1002 +
		s[15] = b15 ^ (~b17 & b19);
1003 +
		s[24] = b24 ^ (~b26 & b28);
1004 +
		s[25] = b25 ^ (~b27 & b29);
1005 +
		s[34] = b34 ^ (~b36 & b38);
1006 +
		s[35] = b35 ^ (~b37 & b39);
1007 +
		s[44] = b44 ^ (~b46 & b48);
1008 +
		s[45] = b45 ^ (~b47 & b49);
1009 +
		s[6] = b6 ^ (~b8 & b0);
1010 +
		s[7] = b7 ^ (~b9 & b1);
1011 +
		s[16] = b16 ^ (~b18 & b10);
1012 +
		s[17] = b17 ^ (~b19 & b11);
1013 +
		s[26] = b26 ^ (~b28 & b20);
1014 +
		s[27] = b27 ^ (~b29 & b21);
1015 +
		s[36] = b36 ^ (~b38 & b30);
1016 +
		s[37] = b37 ^ (~b39 & b31);
1017 +
		s[46] = b46 ^ (~b48 & b40);
1018 +
		s[47] = b47 ^ (~b49 & b41);
1019 +
		s[8] = b8 ^ (~b0 & b2);
1020 +
		s[9] = b9 ^ (~b1 & b3);
1021 +
		s[18] = b18 ^ (~b10 & b12);
1022 +
		s[19] = b19 ^ (~b11 & b13);
1023 +
		s[28] = b28 ^ (~b20 & b22);
1024 +
		s[29] = b29 ^ (~b21 & b23);
1025 +
		s[38] = b38 ^ (~b30 & b32);
1026 +
		s[39] = b39 ^ (~b31 & b33);
1027 +
		s[48] = b48 ^ (~b40 & b42);
1028 +
		s[49] = b49 ^ (~b41 & b43);
1029 +
1030 +
		s[0] ^= RC[n];
1031 +
		s[1] ^= RC[n + 1];
1032 +
	}
1033 +
};
1034 +
1035 +
// Export keccak256 function
1036 +
export function keccak256(message) {
1037 +
	const keccak = new Keccak(256);
1038 +
	keccak.update(message);
1039 +
	return "0x" + keccak.hex();
1040 +
}
1041 +
1042 +
// For CommonJS compatibility
1043 +
if (typeof module !== "undefined" && module.exports) {
1044 +
	module.exports = { keccak256 };
1045 +
}
src/index.ts +2 −0
51 51
			console.error(`❌ Component '${componentName}' not found`);
52 52
			console.log("Available components:");
53 53
			console.log("  - connect-wallet");
54 +
			console.log("  - contract-call");
54 55
			process.exit(1);
55 56
		}
56 57
84 85
85 86
Available Components:
86 87
  - connect-wallet    A Web3 wallet connection component
88 +
  - contract-call     A Web3 contract interaction component
87 89
`);
88 90
}
89 91