├── .eslintrc.cjs
├── .gitignore
├── README.md
├── components
├── Footer.jsx
├── HeaderStat.jsx
└── Navbar.jsx
├── helpers
├── constants.js
└── contracts.js
├── index.html
├── package-lock.json
├── package.json
├── public
├── nft.png
└── vite.svg
├── src
├── App.css
├── App.jsx
├── Home.jsx
├── assets
│ └── react.svg
├── index.css
└── main.jsx
├── styles
├── Home.module.css
└── globals.css
├── vite.config.js
└── yarn.lock
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import { Container, Divider, Stack, Text } from "@chakra-ui/react";
2 |
3 | export const Footer = () => (
4 |
5 |
6 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/components/HeaderStat.jsx:
--------------------------------------------------------------------------------
1 | import { Box, Heading, Stack, Text, useBreakpointValue, useColorModeValue } from "@chakra-ui/react"
2 |
3 | export const HeaderStat = (props) => {
4 | const { label, value, ...boxProps } = props
5 | return (
6 |
20 |
21 |
22 | {label}
23 |
24 |
30 | {value}
31 |
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Container,
3 | Box,
4 | HStack,
5 | Flex,
6 | ButtonGroup,
7 | Button,
8 | useColorModeValue,
9 | } from "@chakra-ui/react";
10 | import { ConnectButton } from "@rainbow-me/rainbowkit";
11 |
12 | export const Navbar = () => (
13 |
14 |
19 |
20 |
21 |
22 |
23 | {["Coin NFT Staking"].map((item) => (
24 |
25 | ))}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 |
--------------------------------------------------------------------------------
/helpers/constants.js:
--------------------------------------------------------------------------------
1 | export const NFT_KEY =
2 | "0000000000000000000000000000000000000000000000000000000000000500";
3 |
--------------------------------------------------------------------------------
/helpers/contracts.js:
--------------------------------------------------------------------------------
1 | export const tokenABI = [
2 | {
3 | inputs: [
4 | {
5 | internalType: "uint256",
6 | name: "initialSupply",
7 | type: "uint256",
8 | },
9 | ],
10 | stateMutability: "nonpayable",
11 | type: "constructor",
12 | },
13 | {
14 | anonymous: false,
15 | inputs: [
16 | {
17 | indexed: true,
18 | internalType: "address",
19 | name: "owner",
20 | type: "address",
21 | },
22 | {
23 | indexed: true,
24 | internalType: "address",
25 | name: "spender",
26 | type: "address",
27 | },
28 | {
29 | indexed: false,
30 | internalType: "uint256",
31 | name: "value",
32 | type: "uint256",
33 | },
34 | ],
35 | name: "Approval",
36 | type: "event",
37 | },
38 | {
39 | anonymous: false,
40 | inputs: [
41 | {
42 | indexed: true,
43 | internalType: "address",
44 | name: "from",
45 | type: "address",
46 | },
47 | {
48 | indexed: true,
49 | internalType: "address",
50 | name: "to",
51 | type: "address",
52 | },
53 | {
54 | indexed: false,
55 | internalType: "uint256",
56 | name: "value",
57 | type: "uint256",
58 | },
59 | ],
60 | name: "Transfer",
61 | type: "event",
62 | },
63 | {
64 | inputs: [
65 | {
66 | internalType: "address",
67 | name: "owner",
68 | type: "address",
69 | },
70 | {
71 | internalType: "address",
72 | name: "spender",
73 | type: "address",
74 | },
75 | ],
76 | name: "allowance",
77 | outputs: [
78 | {
79 | internalType: "uint256",
80 | name: "",
81 | type: "uint256",
82 | },
83 | ],
84 | stateMutability: "view",
85 | type: "function",
86 | },
87 | {
88 | inputs: [
89 | {
90 | internalType: "address",
91 | name: "spender",
92 | type: "address",
93 | },
94 | {
95 | internalType: "uint256",
96 | name: "amount",
97 | type: "uint256",
98 | },
99 | ],
100 | name: "approve",
101 | outputs: [
102 | {
103 | internalType: "bool",
104 | name: "",
105 | type: "bool",
106 | },
107 | ],
108 | stateMutability: "nonpayable",
109 | type: "function",
110 | },
111 | {
112 | inputs: [
113 | {
114 | internalType: "address",
115 | name: "account",
116 | type: "address",
117 | },
118 | ],
119 | name: "balanceOf",
120 | outputs: [
121 | {
122 | internalType: "uint256",
123 | name: "",
124 | type: "uint256",
125 | },
126 | ],
127 | stateMutability: "view",
128 | type: "function",
129 | },
130 | {
131 | inputs: [],
132 | name: "decimals",
133 | outputs: [
134 | {
135 | internalType: "uint8",
136 | name: "",
137 | type: "uint8",
138 | },
139 | ],
140 | stateMutability: "view",
141 | type: "function",
142 | },
143 | {
144 | inputs: [
145 | {
146 | internalType: "address",
147 | name: "spender",
148 | type: "address",
149 | },
150 | {
151 | internalType: "uint256",
152 | name: "subtractedValue",
153 | type: "uint256",
154 | },
155 | ],
156 | name: "decreaseAllowance",
157 | outputs: [
158 | {
159 | internalType: "bool",
160 | name: "",
161 | type: "bool",
162 | },
163 | ],
164 | stateMutability: "nonpayable",
165 | type: "function",
166 | },
167 | {
168 | inputs: [
169 | {
170 | internalType: "address",
171 | name: "spender",
172 | type: "address",
173 | },
174 | {
175 | internalType: "uint256",
176 | name: "addedValue",
177 | type: "uint256",
178 | },
179 | ],
180 | name: "increaseAllowance",
181 | outputs: [
182 | {
183 | internalType: "bool",
184 | name: "",
185 | type: "bool",
186 | },
187 | ],
188 | stateMutability: "nonpayable",
189 | type: "function",
190 | },
191 | {
192 | inputs: [],
193 | name: "name",
194 | outputs: [
195 | {
196 | internalType: "string",
197 | name: "",
198 | type: "string",
199 | },
200 | ],
201 | stateMutability: "view",
202 | type: "function",
203 | },
204 | {
205 | inputs: [],
206 | name: "symbol",
207 | outputs: [
208 | {
209 | internalType: "string",
210 | name: "",
211 | type: "string",
212 | },
213 | ],
214 | stateMutability: "view",
215 | type: "function",
216 | },
217 | {
218 | inputs: [],
219 | name: "totalSupply",
220 | outputs: [
221 | {
222 | internalType: "uint256",
223 | name: "",
224 | type: "uint256",
225 | },
226 | ],
227 | stateMutability: "view",
228 | type: "function",
229 | },
230 | {
231 | inputs: [
232 | {
233 | internalType: "address",
234 | name: "to",
235 | type: "address",
236 | },
237 | {
238 | internalType: "uint256",
239 | name: "amount",
240 | type: "uint256",
241 | },
242 | ],
243 | name: "transfer",
244 | outputs: [
245 | {
246 | internalType: "bool",
247 | name: "",
248 | type: "bool",
249 | },
250 | ],
251 | stateMutability: "nonpayable",
252 | type: "function",
253 | },
254 | {
255 | inputs: [
256 | {
257 | internalType: "address",
258 | name: "from",
259 | type: "address",
260 | },
261 | {
262 | internalType: "address",
263 | name: "to",
264 | type: "address",
265 | },
266 | {
267 | internalType: "uint256",
268 | name: "amount",
269 | type: "uint256",
270 | },
271 | ],
272 | name: "transferFrom",
273 | outputs: [
274 | {
275 | internalType: "bool",
276 | name: "",
277 | type: "bool",
278 | },
279 | ],
280 | stateMutability: "nonpayable",
281 | type: "function",
282 | },
283 | ];
284 | export const nftABI = [
285 | {
286 | inputs: [
287 | {
288 | internalType: "uint256",
289 | name: "_mintPrice",
290 | type: "uint256",
291 | },
292 | ],
293 | stateMutability: "nonpayable",
294 | type: "constructor",
295 | },
296 | {
297 | inputs: [],
298 | name: "ContractPaused",
299 | type: "error",
300 | },
301 | {
302 | inputs: [],
303 | name: "NotOwner",
304 | type: "error",
305 | },
306 | {
307 | anonymous: false,
308 | inputs: [
309 | {
310 | indexed: true,
311 | internalType: "address",
312 | name: "account",
313 | type: "address",
314 | },
315 | {
316 | indexed: true,
317 | internalType: "address",
318 | name: "operator",
319 | type: "address",
320 | },
321 | {
322 | indexed: false,
323 | internalType: "bool",
324 | name: "approved",
325 | type: "bool",
326 | },
327 | ],
328 | name: "ApprovalForAll",
329 | type: "event",
330 | },
331 | {
332 | anonymous: false,
333 | inputs: [
334 | {
335 | indexed: true,
336 | internalType: "address",
337 | name: "operator",
338 | type: "address",
339 | },
340 | {
341 | indexed: true,
342 | internalType: "address",
343 | name: "from",
344 | type: "address",
345 | },
346 | {
347 | indexed: true,
348 | internalType: "address",
349 | name: "to",
350 | type: "address",
351 | },
352 | {
353 | indexed: false,
354 | internalType: "uint256[]",
355 | name: "ids",
356 | type: "uint256[]",
357 | },
358 | {
359 | indexed: false,
360 | internalType: "uint256[]",
361 | name: "values",
362 | type: "uint256[]",
363 | },
364 | ],
365 | name: "TransferBatch",
366 | type: "event",
367 | },
368 | {
369 | anonymous: false,
370 | inputs: [
371 | {
372 | indexed: true,
373 | internalType: "address",
374 | name: "operator",
375 | type: "address",
376 | },
377 | {
378 | indexed: true,
379 | internalType: "address",
380 | name: "from",
381 | type: "address",
382 | },
383 | {
384 | indexed: true,
385 | internalType: "address",
386 | name: "to",
387 | type: "address",
388 | },
389 | {
390 | indexed: false,
391 | internalType: "uint256",
392 | name: "id",
393 | type: "uint256",
394 | },
395 | {
396 | indexed: false,
397 | internalType: "uint256",
398 | name: "value",
399 | type: "uint256",
400 | },
401 | ],
402 | name: "TransferSingle",
403 | type: "event",
404 | },
405 | {
406 | anonymous: false,
407 | inputs: [
408 | {
409 | indexed: false,
410 | internalType: "string",
411 | name: "value",
412 | type: "string",
413 | },
414 | {
415 | indexed: true,
416 | internalType: "uint256",
417 | name: "id",
418 | type: "uint256",
419 | },
420 | ],
421 | name: "URI",
422 | type: "event",
423 | },
424 | {
425 | inputs: [],
426 | name: "_paused",
427 | outputs: [
428 | {
429 | internalType: "bool",
430 | name: "",
431 | type: "bool",
432 | },
433 | ],
434 | stateMutability: "view",
435 | type: "function",
436 | },
437 | {
438 | inputs: [
439 | {
440 | internalType: "address",
441 | name: "account",
442 | type: "address",
443 | },
444 | {
445 | internalType: "uint256",
446 | name: "id",
447 | type: "uint256",
448 | },
449 | ],
450 | name: "balanceOf",
451 | outputs: [
452 | {
453 | internalType: "uint256",
454 | name: "",
455 | type: "uint256",
456 | },
457 | ],
458 | stateMutability: "view",
459 | type: "function",
460 | },
461 | {
462 | inputs: [
463 | {
464 | internalType: "address[]",
465 | name: "accounts",
466 | type: "address[]",
467 | },
468 | {
469 | internalType: "uint256[]",
470 | name: "ids",
471 | type: "uint256[]",
472 | },
473 | ],
474 | name: "balanceOfBatch",
475 | outputs: [
476 | {
477 | internalType: "uint256[]",
478 | name: "",
479 | type: "uint256[]",
480 | },
481 | ],
482 | stateMutability: "view",
483 | type: "function",
484 | },
485 | {
486 | inputs: [],
487 | name: "collectFee",
488 | outputs: [],
489 | stateMutability: "nonpayable",
490 | type: "function",
491 | },
492 | {
493 | inputs: [],
494 | name: "getMintPrice",
495 | outputs: [
496 | {
497 | internalType: "uint256",
498 | name: "",
499 | type: "uint256",
500 | },
501 | ],
502 | stateMutability: "view",
503 | type: "function",
504 | },
505 | {
506 | inputs: [
507 | {
508 | internalType: "address",
509 | name: "account",
510 | type: "address",
511 | },
512 | {
513 | internalType: "address",
514 | name: "operator",
515 | type: "address",
516 | },
517 | ],
518 | name: "isApprovedForAll",
519 | outputs: [
520 | {
521 | internalType: "bool",
522 | name: "",
523 | type: "bool",
524 | },
525 | ],
526 | stateMutability: "view",
527 | type: "function",
528 | },
529 | {
530 | inputs: [
531 | {
532 | internalType: "uint256",
533 | name: "id",
534 | type: "uint256",
535 | },
536 | {
537 | internalType: "uint256",
538 | name: "numberOfTokens",
539 | type: "uint256",
540 | },
541 | ],
542 | name: "mint",
543 | outputs: [],
544 | stateMutability: "payable",
545 | type: "function",
546 | },
547 | {
548 | inputs: [],
549 | name: "mintPrice",
550 | outputs: [
551 | {
552 | internalType: "uint256",
553 | name: "",
554 | type: "uint256",
555 | },
556 | ],
557 | stateMutability: "view",
558 | type: "function",
559 | },
560 | {
561 | inputs: [],
562 | name: "owner",
563 | outputs: [
564 | {
565 | internalType: "address",
566 | name: "",
567 | type: "address",
568 | },
569 | ],
570 | stateMutability: "view",
571 | type: "function",
572 | },
573 | {
574 | inputs: [
575 | {
576 | internalType: "address",
577 | name: "from",
578 | type: "address",
579 | },
580 | {
581 | internalType: "address",
582 | name: "to",
583 | type: "address",
584 | },
585 | {
586 | internalType: "uint256[]",
587 | name: "ids",
588 | type: "uint256[]",
589 | },
590 | {
591 | internalType: "uint256[]",
592 | name: "amounts",
593 | type: "uint256[]",
594 | },
595 | {
596 | internalType: "bytes",
597 | name: "data",
598 | type: "bytes",
599 | },
600 | ],
601 | name: "safeBatchTransferFrom",
602 | outputs: [],
603 | stateMutability: "nonpayable",
604 | type: "function",
605 | },
606 | {
607 | inputs: [
608 | {
609 | internalType: "address",
610 | name: "from",
611 | type: "address",
612 | },
613 | {
614 | internalType: "address",
615 | name: "to",
616 | type: "address",
617 | },
618 | {
619 | internalType: "uint256",
620 | name: "id",
621 | type: "uint256",
622 | },
623 | {
624 | internalType: "uint256",
625 | name: "amount",
626 | type: "uint256",
627 | },
628 | {
629 | internalType: "bytes",
630 | name: "data",
631 | type: "bytes",
632 | },
633 | ],
634 | name: "safeTransferFrom",
635 | outputs: [],
636 | stateMutability: "nonpayable",
637 | type: "function",
638 | },
639 | {
640 | inputs: [
641 | {
642 | internalType: "address",
643 | name: "operator",
644 | type: "address",
645 | },
646 | {
647 | internalType: "bool",
648 | name: "approved",
649 | type: "bool",
650 | },
651 | ],
652 | name: "setApprovalForAll",
653 | outputs: [],
654 | stateMutability: "nonpayable",
655 | type: "function",
656 | },
657 | {
658 | inputs: [
659 | {
660 | internalType: "bool",
661 | name: "val",
662 | type: "bool",
663 | },
664 | ],
665 | name: "setPaused",
666 | outputs: [],
667 | stateMutability: "nonpayable",
668 | type: "function",
669 | },
670 | {
671 | inputs: [
672 | {
673 | internalType: "string",
674 | name: "newuri",
675 | type: "string",
676 | },
677 | ],
678 | name: "setURI",
679 | outputs: [],
680 | stateMutability: "nonpayable",
681 | type: "function",
682 | },
683 | {
684 | inputs: [
685 | {
686 | internalType: "bytes4",
687 | name: "interfaceId",
688 | type: "bytes4",
689 | },
690 | ],
691 | name: "supportsInterface",
692 | outputs: [
693 | {
694 | internalType: "bool",
695 | name: "",
696 | type: "bool",
697 | },
698 | ],
699 | stateMutability: "view",
700 | type: "function",
701 | },
702 | {
703 | inputs: [
704 | {
705 | internalType: "address",
706 | name: "newOwner",
707 | type: "address",
708 | },
709 | ],
710 | name: "transferOwnership",
711 | outputs: [],
712 | stateMutability: "nonpayable",
713 | type: "function",
714 | },
715 | {
716 | inputs: [
717 | {
718 | internalType: "uint256",
719 | name: "",
720 | type: "uint256",
721 | },
722 | ],
723 | name: "uri",
724 | outputs: [
725 | {
726 | internalType: "string",
727 | name: "",
728 | type: "string",
729 | },
730 | ],
731 | stateMutability: "view",
732 | type: "function",
733 | },
734 | ];
735 | export const stakingABI = [
736 | {
737 | inputs: [
738 | {
739 | internalType: "address",
740 | name: "rewardTokenAddress",
741 | type: "address",
742 | },
743 | {
744 | internalType: "address",
745 | name: "nftAddress",
746 | type: "address",
747 | },
748 | {
749 | internalType: "uint256",
750 | name: "_tokenPool",
751 | type: "uint256",
752 | },
753 | {
754 | internalType: "uint256",
755 | name: "_blockReward",
756 | type: "uint256",
757 | },
758 | {
759 | internalType: "uint256",
760 | name: "_key",
761 | type: "uint256",
762 | },
763 | ],
764 | stateMutability: "nonpayable",
765 | type: "constructor",
766 | },
767 | {
768 | inputs: [],
769 | name: "ContractPaused",
770 | type: "error",
771 | },
772 | {
773 | inputs: [],
774 | name: "NotOwner",
775 | type: "error",
776 | },
777 | {
778 | anonymous: false,
779 | inputs: [
780 | {
781 | indexed: false,
782 | internalType: "uint256",
783 | name: "oldID",
784 | type: "uint256",
785 | },
786 | {
787 | indexed: false,
788 | internalType: "uint256",
789 | name: "newID",
790 | type: "uint256",
791 | },
792 | ],
793 | name: "KeyTokenChanged",
794 | type: "event",
795 | },
796 | {
797 | anonymous: false,
798 | inputs: [
799 | {
800 | indexed: false,
801 | internalType: "address",
802 | name: "staker",
803 | type: "address",
804 | },
805 | {
806 | indexed: false,
807 | internalType: "uint256",
808 | name: "amount",
809 | type: "uint256",
810 | },
811 | ],
812 | name: "RewardsClaimed",
813 | type: "event",
814 | },
815 | {
816 | anonymous: false,
817 | inputs: [
818 | {
819 | indexed: true,
820 | internalType: "address",
821 | name: "staker",
822 | type: "address",
823 | },
824 | {
825 | indexed: false,
826 | internalType: "uint256",
827 | name: "amount",
828 | type: "uint256",
829 | },
830 | {
831 | indexed: false,
832 | internalType: "uint256",
833 | name: "totalStaked",
834 | type: "uint256",
835 | },
836 | ],
837 | name: "Staked",
838 | type: "event",
839 | },
840 | {
841 | anonymous: false,
842 | inputs: [
843 | {
844 | indexed: true,
845 | internalType: "address",
846 | name: "staker",
847 | type: "address",
848 | },
849 | {
850 | indexed: false,
851 | internalType: "uint256",
852 | name: "amount",
853 | type: "uint256",
854 | },
855 | {
856 | indexed: false,
857 | internalType: "uint256",
858 | name: "totalStaked",
859 | type: "uint256",
860 | },
861 | ],
862 | name: "Unstaked",
863 | type: "event",
864 | },
865 | {
866 | inputs: [
867 | {
868 | internalType: "uint256",
869 | name: "newBlockReward",
870 | type: "uint256",
871 | },
872 | ],
873 | name: "changeBlockReward",
874 | outputs: [],
875 | stateMutability: "nonpayable",
876 | type: "function",
877 | },
878 | {
879 | inputs: [
880 | {
881 | internalType: "uint256",
882 | name: "id",
883 | type: "uint256",
884 | },
885 | ],
886 | name: "changePoolKeyToken",
887 | outputs: [],
888 | stateMutability: "nonpayable",
889 | type: "function",
890 | },
891 | {
892 | inputs: [],
893 | name: "claim",
894 | outputs: [],
895 | stateMutability: "nonpayable",
896 | type: "function",
897 | },
898 | {
899 | inputs: [
900 | {
901 | internalType: "address",
902 | name: "user",
903 | type: "address",
904 | },
905 | ],
906 | name: "earned",
907 | outputs: [
908 | {
909 | internalType: "uint256",
910 | name: "",
911 | type: "uint256",
912 | },
913 | ],
914 | stateMutability: "view",
915 | type: "function",
916 | },
917 | {
918 | inputs: [],
919 | name: "getPoolKeyToken",
920 | outputs: [
921 | {
922 | internalType: "uint256",
923 | name: "",
924 | type: "uint256",
925 | },
926 | ],
927 | stateMutability: "view",
928 | type: "function",
929 | },
930 | {
931 | inputs: [
932 | {
933 | internalType: "address",
934 | name: "",
935 | type: "address",
936 | },
937 | {
938 | internalType: "address",
939 | name: "",
940 | type: "address",
941 | },
942 | {
943 | internalType: "uint256[]",
944 | name: "",
945 | type: "uint256[]",
946 | },
947 | {
948 | internalType: "uint256[]",
949 | name: "",
950 | type: "uint256[]",
951 | },
952 | {
953 | internalType: "bytes",
954 | name: "",
955 | type: "bytes",
956 | },
957 | ],
958 | name: "onERC1155BatchReceived",
959 | outputs: [
960 | {
961 | internalType: "bytes4",
962 | name: "",
963 | type: "bytes4",
964 | },
965 | ],
966 | stateMutability: "nonpayable",
967 | type: "function",
968 | },
969 | {
970 | inputs: [
971 | {
972 | internalType: "address",
973 | name: "",
974 | type: "address",
975 | },
976 | {
977 | internalType: "address",
978 | name: "",
979 | type: "address",
980 | },
981 | {
982 | internalType: "uint256",
983 | name: "",
984 | type: "uint256",
985 | },
986 | {
987 | internalType: "uint256",
988 | name: "",
989 | type: "uint256",
990 | },
991 | {
992 | internalType: "bytes",
993 | name: "",
994 | type: "bytes",
995 | },
996 | ],
997 | name: "onERC1155Received",
998 | outputs: [
999 | {
1000 | internalType: "bytes4",
1001 | name: "",
1002 | type: "bytes4",
1003 | },
1004 | ],
1005 | stateMutability: "nonpayable",
1006 | type: "function",
1007 | },
1008 | {
1009 | inputs: [],
1010 | name: "owner",
1011 | outputs: [
1012 | {
1013 | internalType: "address",
1014 | name: "",
1015 | type: "address",
1016 | },
1017 | ],
1018 | stateMutability: "view",
1019 | type: "function",
1020 | },
1021 | {
1022 | inputs: [],
1023 | name: "paused",
1024 | outputs: [
1025 | {
1026 | internalType: "bool",
1027 | name: "",
1028 | type: "bool",
1029 | },
1030 | ],
1031 | stateMutability: "view",
1032 | type: "function",
1033 | },
1034 | {
1035 | inputs: [],
1036 | name: "poolKEY",
1037 | outputs: [
1038 | {
1039 | internalType: "uint256",
1040 | name: "",
1041 | type: "uint256",
1042 | },
1043 | ],
1044 | stateMutability: "view",
1045 | type: "function",
1046 | },
1047 | {
1048 | inputs: [
1049 | {
1050 | internalType: "bool",
1051 | name: "val",
1052 | type: "bool",
1053 | },
1054 | ],
1055 | name: "setPaused",
1056 | outputs: [],
1057 | stateMutability: "nonpayable",
1058 | type: "function",
1059 | },
1060 | {
1061 | inputs: [
1062 | {
1063 | internalType: "uint256",
1064 | name: "amount",
1065 | type: "uint256",
1066 | },
1067 | {
1068 | internalType: "bytes",
1069 | name: "data",
1070 | type: "bytes",
1071 | },
1072 | ],
1073 | name: "stake",
1074 | outputs: [],
1075 | stateMutability: "nonpayable",
1076 | type: "function",
1077 | },
1078 | {
1079 | inputs: [
1080 | {
1081 | internalType: "address",
1082 | name: "",
1083 | type: "address",
1084 | },
1085 | ],
1086 | name: "stakers",
1087 | outputs: [
1088 | {
1089 | internalType: "uint256",
1090 | name: "totalNftStaked",
1091 | type: "uint256",
1092 | },
1093 | {
1094 | internalType: "uint256",
1095 | name: "rewardPending",
1096 | type: "uint256",
1097 | },
1098 | {
1099 | internalType: "uint256",
1100 | name: "lastUpdateAt",
1101 | type: "uint256",
1102 | },
1103 | ],
1104 | stateMutability: "view",
1105 | type: "function",
1106 | },
1107 | {
1108 | inputs: [
1109 | {
1110 | internalType: "bytes4",
1111 | name: "interfaceId",
1112 | type: "bytes4",
1113 | },
1114 | ],
1115 | name: "supportsInterface",
1116 | outputs: [
1117 | {
1118 | internalType: "bool",
1119 | name: "",
1120 | type: "bool",
1121 | },
1122 | ],
1123 | stateMutability: "view",
1124 | type: "function",
1125 | },
1126 | {
1127 | inputs: [],
1128 | name: "totalStaked",
1129 | outputs: [
1130 | {
1131 | internalType: "uint256",
1132 | name: "",
1133 | type: "uint256",
1134 | },
1135 | ],
1136 | stateMutability: "view",
1137 | type: "function",
1138 | },
1139 | {
1140 | inputs: [
1141 | {
1142 | internalType: "address",
1143 | name: "addr",
1144 | type: "address",
1145 | },
1146 | ],
1147 | name: "totalStakedFor",
1148 | outputs: [
1149 | {
1150 | internalType: "uint256",
1151 | name: "",
1152 | type: "uint256",
1153 | },
1154 | ],
1155 | stateMutability: "view",
1156 | type: "function",
1157 | },
1158 | {
1159 | inputs: [
1160 | {
1161 | internalType: "address",
1162 | name: "newOwner",
1163 | type: "address",
1164 | },
1165 | ],
1166 | name: "transferOwnership",
1167 | outputs: [],
1168 | stateMutability: "nonpayable",
1169 | type: "function",
1170 | },
1171 | {
1172 | inputs: [
1173 | {
1174 | internalType: "uint256",
1175 | name: "amount",
1176 | type: "uint256",
1177 | },
1178 | {
1179 | internalType: "bytes",
1180 | name: "data",
1181 | type: "bytes",
1182 | },
1183 | ],
1184 | name: "unstake",
1185 | outputs: [],
1186 | stateMutability: "nonpayable",
1187 | type: "function",
1188 | },
1189 | ];
1190 | export const tokenAddress = "0x3C6eeC559d7a8ECfaB88d29e042Aac4e6e0a63a6";
1191 | export const nftAddress = "0xf046E538fff7b03940346FF07E120bbB2dD8bcc1";
1192 | export const stakingAddress = "0x26C596e3d3eFC4a9D2F31b563CE60A4a5922da31";
1193 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nft-staking-app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@chakra-ui/pro-theme": "^0.0.64",
14 | "@chakra-ui/react": "^2.1.2",
15 | "@emotion/react": "^11",
16 | "@emotion/styled": "^11",
17 | "@fontsource/inter": "^4.5.15",
18 | "@rainbow-me/rainbowkit": "^0.5.0",
19 | "dotenv": "^16.0.3",
20 | "ethers": "^5.7.0",
21 | "framer-motion": "^6",
22 | "next": "13.2.3",
23 | "prettier": "^2.8.4",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "react-icons": "^4.8.0",
27 | "wagmi": "^0.6.4"
28 | },
29 | "devDependencies": {
30 | "@types/react": "^18.2.66",
31 | "@types/react-dom": "^18.2.22",
32 | "@vitejs/plugin-react": "^4.2.1",
33 | "eslint": "^8.57.0",
34 | "eslint-plugin-react": "^7.34.1",
35 | "eslint-plugin-react-hooks": "^4.6.0",
36 | "eslint-plugin-react-refresh": "^0.4.6",
37 | "vite": "^5.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/nft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CryptoDev88/nft-staking-app/c03967e6c3a5cf92c6ca6672084f265ddda4701a/public/nft.png
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { ChakraProvider, extendTheme } from "@chakra-ui/react";
2 | import { theme } from "@chakra-ui/pro-theme";
3 | import "@fontsource/inter/variable.css";
4 | import "@rainbow-me/rainbowkit/styles.css";
5 | import { getDefaultWallets, RainbowKitProvider } from "@rainbow-me/rainbowkit";
6 | import { configureChains, createClient, WagmiConfig } from "wagmi";
7 | import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
8 | import Home from "./Home";
9 |
10 | const celoChain = {
11 | id: 44787,
12 | name: "Celo Alfajores Testnet",
13 | network: "alfajores",
14 | nativeCurrency: {
15 | decimals: 18,
16 | name: "Celo",
17 | symbol: "CELO",
18 | },
19 | rpcUrls: {
20 | default: "https://alfajores-forno.celo-testnet.org",
21 | },
22 | blockExplorers: {
23 | default: {
24 | name: "CeloScan",
25 | url: "https://alfajores.celoscan.io",
26 | },
27 | },
28 | testnet: true,
29 | };
30 |
31 | const { chains, provider } = configureChains(
32 | [celoChain],
33 | [
34 | jsonRpcProvider({
35 | rpc: (chain) => {
36 | if (chain.id !== celoChain.id) return null;
37 | return { http: chain.rpcUrls.default };
38 | },
39 | }),
40 | ]
41 | );
42 |
43 | const { connectors } = getDefaultWallets({
44 | appName: "Celo NFT Marketplace",
45 | chains,
46 | });
47 |
48 | const wagmiClient = createClient({
49 | autoConnect: true,
50 | connectors,
51 | provider,
52 | });
53 |
54 | function App() {
55 | const myTheme = extendTheme(
56 | {
57 | colors: { ...theme.colors },
58 | },
59 | theme
60 | );
61 |
62 | return (
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | );
71 | }
72 |
73 | export default App;
74 |
--------------------------------------------------------------------------------
/src/Home.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { ethers } from "ethers";
3 | import {
4 | useSigner,
5 | useProvider,
6 | useAccount,
7 | useBalance,
8 | useContract,
9 | } from "wagmi";
10 |
11 | import { Navbar } from "../components/Navbar";
12 | import { HeaderStat } from "../components/HeaderStat";
13 | import { Footer } from "../components/Footer";
14 |
15 | import {
16 | Container,
17 | SimpleGrid,
18 | Button,
19 | Divider,
20 | Heading,
21 | InputGroup,
22 | InputRightElement,
23 | Input,
24 | Stack,
25 | Text,
26 | Box,
27 | Stat,
28 | StatLabel,
29 | useBreakpointValue,
30 | useColorModeValue,
31 | Skeleton,
32 | useToast,
33 | Image,
34 | Center,
35 | } from "@chakra-ui/react";
36 |
37 | import {
38 | tokenABI,
39 | nftABI,
40 | stakingABI,
41 | tokenAddress,
42 | nftAddress,
43 | stakingAddress,
44 | } from "../helpers/contracts";
45 | import { NFT_KEY } from "../helpers/constants";
46 |
47 | const Home = () => {
48 | const toast = useToast();
49 |
50 | const [address, setAddress] = useState();
51 |
52 | const [totalStakedAmount, setTotalStakedAmount] = useState(); // optional: total staked in contract
53 | const [priceNFT, setPriceNFT] = useState();
54 |
55 | //user balances
56 | const [userCeloBalance, setUserCeloBalance] = useState();
57 | const [userStakedBalance, setUserStakedBalance] = useState();
58 | const [userPendingRewards, setUserPendingRewards] = useState();
59 | const [userNFTBalance, setUserNFTBalance] = useState();
60 | const [userTokenBalance, setUserTokenBalance] = useState();
61 | const [rewardPool, setRewardPool] = useState();
62 |
63 | const [userAllowanceStakeNFT, setUserAllowanceStakeNFT] = useState();
64 |
65 | // Get provider and signer from wagmi
66 | const { data: provider } = useProvider();
67 | const { data: signer } = useSigner();
68 |
69 | // isConnected => address
70 | const { isConnected } = useAccount();
71 |
72 | // contracts
73 | const tokenContract = useContract({
74 | addressOrName: tokenAddress,
75 | contractInterface: tokenABI,
76 | signerOrProvider: signer,
77 | });
78 |
79 | const stakingContract = useContract({
80 | addressOrName: stakingAddress,
81 | contractInterface: stakingABI,
82 | signerOrProvider: signer,
83 | });
84 |
85 | const nftContract = useContract({
86 | addressOrName: nftAddress,
87 | contractInterface: nftABI,
88 | signerOrProvider: signer,
89 | });
90 |
91 | const updateUserBalances = async (address) => {
92 | const celoBalance = ethers.utils.formatEther(await signer.getBalance());
93 |
94 | const tokenBalance = ethers.utils.formatEther(
95 | await tokenContract.balanceOf(address)
96 | );
97 | console.log(tokenBalance);
98 | const stakedTokens = await stakingContract.totalStakedFor(address);
99 |
100 | const pendingRewards = await stakingContract.earned(address);
101 |
102 | const nft = await nftContract.balanceOf(address, NFT_KEY);
103 | const _rewardPool = ethers.utils.formatEther(
104 | await tokenContract.balanceOf(stakingContract.address)
105 | );
106 |
107 | const allowanceStakedNFT = await nftContract.isApprovedForAll(
108 | address,
109 | stakingContract.address
110 | );
111 |
112 | setUserCeloBalance(celoBalance);
113 | setUserTokenBalance(tokenBalance);
114 | setUserStakedBalance(stakedTokens);
115 | setUserPendingRewards(pendingRewards);
116 | setUserNFTBalance(nft);
117 | setRewardPool(_rewardPool);
118 | setUserAllowanceStakeNFT(allowanceStakedNFT);
119 | };
120 |
121 | const loadDefaultValues = async () => {
122 | // Get signer's address
123 | const _address = await signer.getAddress();
124 | const priceNFT = await nftContract.getMintPrice();
125 | const stakedTokens = await stakingContract.totalStaked();
126 |
127 | setAddress(_address);
128 | setPriceNFT(BigInt(priceNFT).toString());
129 | setTotalStakedAmount(BigInt(stakedTokens).toString());
130 | };
131 |
132 | const formatNumber = (number) => {
133 | const amount = ethers.utils.formatEther(number);
134 | const calcDec = Math.pow(10, 0);
135 |
136 | return Math.trunc(amount * calcDec) / calcDec;
137 | };
138 |
139 | const formatNumber6Dec = (number) => {
140 | const amount = ethers.utils.formatEther(number);
141 | const calcDec = Math.pow(10, 6);
142 |
143 | return Math.trunc(amount * calcDec) / calcDec;
144 | };
145 |
146 | const getNFT = async () => {
147 | if (!isConnected) {
148 | toast({
149 | title: "Connect Wallet",
150 | description:
151 | "Please connect your wallet and wait for the page to load.",
152 | status: "error",
153 | duration: 6000,
154 | isClosable: true,
155 | });
156 | return;
157 | }
158 |
159 | const input = document.querySelector("#inputGetNFT");
160 |
161 | if (
162 | !Number.isInteger(parseInt(input.value)) ||
163 | parseInt(input.value) == 0 ||
164 | input.value.length == 0
165 | ) {
166 | toast({
167 | title: "Only Numbers",
168 | description: "Please use only round numbers with no decimal places.",
169 | status: "error",
170 | duration: 6000,
171 | isClosable: true,
172 | });
173 | return;
174 | }
175 |
176 | const inputValue = input.value * ethers.utils.formatEther(priceNFT);
177 | const formattedValue = parseInt(
178 | ethers.utils.parseEther(inputValue.toString())
179 | );
180 |
181 | if (userCeloBalance < inputValue) {
182 | toast({
183 | title: "Not Enough Funds",
184 | description: "Please make sure you have enough funds to proceed.",
185 | status: "error",
186 | duration: 6000,
187 | isClosable: true,
188 | });
189 | return;
190 | }
191 |
192 | const cost = (input.value * priceNFT).toString();
193 | const approvePurchaseTX = await nftContract.mint(NFT_KEY, input.value, {
194 | value: cost,
195 | });
196 |
197 | toast({
198 | title: "Confirming Transaction",
199 | description: "Please wait until the transaction is confirmed.",
200 | status: "info",
201 | duration: 6000,
202 | isClosable: true,
203 | });
204 |
205 | await approvePurchaseTX.wait();
206 |
207 | if (!approvePurchaseTX) {
208 | toast({
209 | title: "Transaction Failed",
210 | description: "Please refresh the page and try again.",
211 | status: "error",
212 | duration: 6000,
213 | isClosable: true,
214 | });
215 | }
216 |
217 | toast({
218 | title: "Transaction Success!",
219 | description: "You have successfully purchased the nfts.",
220 | status: "success",
221 | duration: 6000,
222 | isClosable: true,
223 | });
224 |
225 | input.value = "";
226 | updateUserBalances(address);
227 | };
228 |
229 | const stakeNFT = async () => {
230 | if (!isConnected) {
231 | toast({
232 | title: "Connect Wallet",
233 | description:
234 | "Please connect your wallet and wait for the page to load.",
235 | status: "error",
236 | duration: 6000,
237 | isClosable: true,
238 | });
239 | return;
240 | }
241 |
242 | const input = document.querySelector("#inputStakeNFT");
243 |
244 | if (
245 | !Number.isInteger(parseInt(input.value)) ||
246 | parseInt(input.value) == 0 ||
247 | input.value.length == 0
248 | ) {
249 | toast({
250 | title: "Only Numbers",
251 | description: "Please use only round numbers with no decimal places.",
252 | status: "error",
253 | duration: 6000,
254 | isClosable: true,
255 | });
256 | return;
257 | }
258 |
259 | const inputValue = parseInt(input.value);
260 |
261 | if (parseInt(userNFTBalance) < inputValue) {
262 | toast({
263 | title: "Not Enough Funds",
264 | description: "Please make sure you have enough funds to proceed.",
265 | status: "error",
266 | duration: 6000,
267 | isClosable: true,
268 | });
269 | return;
270 | }
271 |
272 | if (userAllowanceStakeNFT == false) {
273 | toast({
274 | title: "Approve NFTs",
275 | description: "Please approve the staking contract to use your NFTs.",
276 | status: "info",
277 | duration: 6000,
278 | isClosable: true,
279 | });
280 | const approveTX = await nftContract.setApprovalForAll(
281 | stakingAddress,
282 | true
283 | );
284 |
285 | toast({
286 | title: "Approving NFTs",
287 | description: "Please wait until the NFTs are approved.",
288 | status: "info",
289 | duration: 6000,
290 | isClosable: true,
291 | });
292 |
293 | await approveTX.wait();
294 |
295 | if (approveTX) {
296 | toast({
297 | title: "Approve Success!",
298 | description: "You have successfully approved the request.",
299 | status: "success",
300 | duration: 6000,
301 | isClosable: true,
302 | });
303 | setUserAllowanceStakeNFT(true);
304 | }
305 | }
306 |
307 | toast({
308 | title: "Confirm Transaction",
309 | description: "Please confirm the transaction in your wallet to proceed.",
310 | status: "info",
311 | duration: 6000,
312 | isClosable: true,
313 | });
314 |
315 | const approveStakingTX = await stakingContract.stake(inputValue, "0x0000");
316 |
317 | toast({
318 | title: "Confirming Transaction",
319 | description: "Please wait until the transaction is confirmed.",
320 | status: "info",
321 | duration: 6000,
322 | isClosable: true,
323 | });
324 |
325 | await approveStakingTX.wait();
326 |
327 | if (!approveStakingTX) {
328 | toast({
329 | title: "Transaction Failed",
330 | description: "Please refresh the page and try again.",
331 | status: "error",
332 | duration: 6000,
333 | isClosable: true,
334 | });
335 | }
336 |
337 | toast({
338 | title: "NFTs Staked!",
339 | description: "You have successfully staked your nfts.",
340 | status: "success",
341 | duration: 6000,
342 | isClosable: true,
343 | });
344 | input.value = "";
345 | updateUserBalances(address);
346 | };
347 |
348 | const unstakeNFT = async () => {
349 | if (!isConnected || !userNFTBalance || !userStakedBalance) {
350 | toast({
351 | title: "Connect Wallet",
352 | description:
353 | "Please connect your wallet and wait for the page to load.",
354 | status: "error",
355 | duration: 6000,
356 | isClosable: true,
357 | });
358 | return;
359 | }
360 |
361 | const input = document.querySelector("#inputUnstakeNFT");
362 |
363 | if (
364 | !Number.isInteger(parseInt(input.value)) ||
365 | parseInt(input.value) == 0 ||
366 | input.value.length == 0
367 | ) {
368 | toast({
369 | title: "Only Numbers",
370 | description: "Please use only round numbers with no decimal places.",
371 | status: "error",
372 | duration: 6000,
373 | isClosable: true,
374 | });
375 | return;
376 | }
377 |
378 | const inputValue = parseInt(input.value);
379 |
380 | if (inputValue > parseInt(userStakedBalance)) {
381 | toast({
382 | title: "Not Available",
383 | description: "You don't have that amount of NFTs staked.",
384 | status: "error",
385 | duration: 6000,
386 | isClosable: true,
387 | });
388 | return;
389 | }
390 |
391 | toast({
392 | title: "Confirm Transaction",
393 | description: "Please confirm the transaction in your wallet to proceed.",
394 | status: "info",
395 | duration: 6000,
396 | isClosable: true,
397 | });
398 |
399 | const approveStakingTX = await stakingContract.unstake(
400 | inputValue,
401 | "0x0000"
402 | );
403 |
404 | toast({
405 | title: "Confirming Transaction",
406 | description: "Please wait until the transaction is confirmed.",
407 | status: "info",
408 | duration: 6000,
409 | isClosable: true,
410 | });
411 |
412 | await approveStakingTX.wait();
413 |
414 | if (!approveStakingTX) {
415 | toast({
416 | title: "Transaction Failed",
417 | description: "Please refresh the page and try again.",
418 | status: "error",
419 | duration: 6000,
420 | isClosable: true,
421 | });
422 | }
423 |
424 | toast({
425 | title: "NFTs Unstaked!",
426 | description: "You have successfully unstaked the nfts.",
427 | status: "success",
428 | duration: 6000,
429 | isClosable: true,
430 | });
431 | input.value = "";
432 | updateUserBalances(address);
433 | };
434 |
435 | const claimRewards = async () => {
436 | if (!isConnected) {
437 | toast({
438 | title: "Connect Wallet",
439 | description:
440 | "Please connect your wallet and wait for the page to load.",
441 | status: "error",
442 | duration: 6000,
443 | isClosable: true,
444 | });
445 | return;
446 | }
447 |
448 | if (userPendingRewards == 0) {
449 | toast({
450 | title: "Not Available",
451 | description: "You don't have any pending rewards.",
452 | status: "error",
453 | duration: 6000,
454 | isClosable: true,
455 | });
456 | return;
457 | }
458 |
459 | toast({
460 | title: "Confirm Transaction",
461 | description: "Please confirm the transaction in your wallet to proceed.",
462 | status: "info",
463 | duration: 6000,
464 | isClosable: true,
465 | });
466 |
467 | const approveClaimTX = await stakingContract.claim();
468 |
469 | toast({
470 | title: "Confirming Transaction",
471 | description: "Please wait until the transaction is confirmed.",
472 | status: "info",
473 | duration: 6000,
474 | isClosable: true,
475 | });
476 |
477 | await approveClaimTX.wait();
478 |
479 | if (!approveClaimTX) {
480 | toast({
481 | title: "Transaction Failed",
482 | description: "Please refresh the page and try again.",
483 | status: "error",
484 | duration: 6000,
485 | isClosable: true,
486 | });
487 | }
488 |
489 | toast({
490 | title: "Rewards Claimed!",
491 | description: "You have successfully claimed your rewards.",
492 | status: "success",
493 | duration: 6000,
494 | isClosable: true,
495 | });
496 | updateUserBalances(address);
497 | };
498 |
499 | const getMaxNFT = async () => {
500 | if (!isConnected) {
501 | toast({
502 | title: "Connect Wallet",
503 | description:
504 | "Please connect your wallet and wait for the page to load.",
505 | status: "error",
506 | duration: 6000,
507 | isClosable: true,
508 | });
509 | return;
510 | }
511 | document.querySelector("#inputGetNFT").value = parseInt(userCeloBalance);
512 | };
513 |
514 | const getMaxStakeNFT = async () => {
515 | if (!isConnected) {
516 | toast({
517 | title: "Connect Wallet",
518 | description:
519 | "Please connect your wallet and wait for the page to load.",
520 | status: "error",
521 | duration: 6000,
522 | isClosable: true,
523 | });
524 | return;
525 | }
526 |
527 | document.querySelector("#inputStakeNFT").value = parseInt(userNFTBalance);
528 | };
529 |
530 | const getMaxUnstakeNFT = async () => {
531 | if (!isConnected) {
532 | toast({
533 | title: "Connect Wallet",
534 | description:
535 | "Please connect your wallet and wait for the page to load.",
536 | status: "error",
537 | duration: 6000,
538 | isClosable: true,
539 | });
540 | return;
541 | }
542 |
543 | document.querySelector("#inputUnstakeNFT").value =
544 | parseInt(userStakedBalance);
545 | };
546 |
547 | useEffect(() => {
548 | if (isConnected) {
549 | loadDefaultValues();
550 | updateUserBalances(address);
551 | }
552 | });
553 |
554 | useEffect(() => {
555 | const interval = setInterval(async () => {
556 | if (isConnected) {
557 | const pendingRewards = await stakingContract.earned(address);
558 | setUserPendingRewards(pendingRewards);
559 | }
560 | }, 10000);
561 | return () => clearInterval(interval);
562 | });
563 |
564 | return (
565 |
782 | );
783 | };
784 |
785 | export default Home;
786 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 |
5 | ReactDOM.createRoot(document.getElementById("root")).render(
6 |
7 |
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .footer__link {
32 | font-size: 0.9rem;
33 | font-weight: 600;
34 | color: var(--clr-fg);
35 | }
36 |
37 | .title a {
38 | color: #0070f3;
39 | text-decoration: none;
40 | }
41 |
42 | .title a:hover,
43 | .title a:focus,
44 | .title a:active {
45 | text-decoration: underline;
46 | }
47 |
48 | .title {
49 | margin: 0;
50 | line-height: 1.15;
51 | font-size: 4rem;
52 | }
53 |
54 | .title,
55 | .description {
56 | text-align: center;
57 | }
58 |
59 | .description {
60 | margin: 4rem 0;
61 | line-height: 1.5;
62 | font-size: 1.5rem;
63 | }
64 |
65 | .code {
66 | background: #fafafa;
67 | border-radius: 5px;
68 | padding: 0.75rem;
69 | font-size: 1.1rem;
70 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
71 | Bitstream Vera Sans Mono, Courier New, monospace;
72 | }
73 |
74 | .grid {
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | flex-wrap: wrap;
79 | max-width: 800px;
80 | }
81 |
82 | .card {
83 | margin: 1rem;
84 | padding: 1.5rem;
85 | text-align: left;
86 | color: inherit;
87 | text-decoration: none;
88 | border: 1px solid #eaeaea;
89 | border-radius: 10px;
90 | transition: color 0.15s ease, border-color 0.15s ease;
91 | max-width: 300px;
92 | }
93 |
94 | .card:hover,
95 | .card:focus,
96 | .card:active {
97 | color: #0070f3;
98 | border-color: #0070f3;
99 | }
100 |
101 | .card h2 {
102 | margin: 0 0 1rem 0;
103 | font-size: 1.5rem;
104 | }
105 |
106 | .card p {
107 | margin: 0;
108 | font-size: 1.25rem;
109 | line-height: 1.5;
110 | }
111 |
112 | @media (max-width: 600px) {
113 | .grid {
114 | width: 100%;
115 | flex-direction: column;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------