@haneullabs/create-dapp 0.1.0

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +1159 -0
  2. package/README.md +33 -0
  3. package/bin/index.js +4 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +102 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +52 -0
  8. package/src/index.ts +130 -0
  9. package/templates/react-client-dapp/README.md +35 -0
  10. package/templates/react-client-dapp/index.html +59 -0
  11. package/templates/react-client-dapp/package.json +35 -0
  12. package/templates/react-client-dapp/prettier.config.cjs +4 -0
  13. package/templates/react-client-dapp/src/App.tsx +39 -0
  14. package/templates/react-client-dapp/src/OwnedObjects.tsx +42 -0
  15. package/templates/react-client-dapp/src/WalletStatus.tsx +23 -0
  16. package/templates/react-client-dapp/src/main.tsx +26 -0
  17. package/templates/react-client-dapp/src/networkConfig.ts +17 -0
  18. package/templates/react-client-dapp/src/vite-env.d.ts +1 -0
  19. package/templates/react-client-dapp/tsconfig.json +25 -0
  20. package/templates/react-client-dapp/tsconfig.node.json +10 -0
  21. package/templates/react-client-dapp/vite.config.mts +7 -0
  22. package/templates/react-e2e-counter/README.md +92 -0
  23. package/templates/react-e2e-counter/index.html +59 -0
  24. package/templates/react-e2e-counter/move/counter/Move.toml +10 -0
  25. package/templates/react-e2e-counter/move/counter/sources/counter.move +36 -0
  26. package/templates/react-e2e-counter/package.json +36 -0
  27. package/templates/react-e2e-counter/prettier.config.cjs +4 -0
  28. package/templates/react-e2e-counter/src/App.tsx +61 -0
  29. package/templates/react-e2e-counter/src/Counter.tsx +106 -0
  30. package/templates/react-e2e-counter/src/CreateCounter.tsx +60 -0
  31. package/templates/react-e2e-counter/src/constants.ts +3 -0
  32. package/templates/react-e2e-counter/src/main.tsx +26 -0
  33. package/templates/react-e2e-counter/src/networkConfig.ts +31 -0
  34. package/templates/react-e2e-counter/src/vite-env.d.ts +1 -0
  35. package/templates/react-e2e-counter/tsconfig.json +25 -0
  36. package/templates/react-e2e-counter/tsconfig.node.json +10 -0
  37. package/templates/react-e2e-counter/vite.config.mts +7 -0
@@ -0,0 +1,92 @@
1
+ # Sui dApp Starter Template
2
+
3
+ This dApp was created using `@haneullabs/create-dapp` that sets up a basic React
4
+ Client dApp using the following tools:
5
+
6
+ - [React](https://react.dev/) as the UI framework
7
+ - [TypeScript](https://www.typescriptlang.org/) for type checking
8
+ - [Vite](https://vitejs.dev/) for build tooling
9
+ - [Radix UI](https://www.radix-ui.com/) for pre-built UI components
10
+ - [ESLint](https://eslint.org/) for linting
11
+ - [`@haneullabs/dapp-kit`](https://sdk.haneullabs.com/dapp-kit) for connecting to
12
+ wallets and loading data
13
+ - [pnpm](https://pnpm.io/) for package management
14
+
15
+ For a full guide on how to build this dApp from scratch, visit this
16
+ [guide](http://docs.sui.io/guides/developer/app-examples/e2e-counter#frontend).
17
+
18
+ ## Deploying your Move code
19
+
20
+ ### Install Sui cli
21
+
22
+ Before deploying your move code, ensure that you have installed the Haneul CLI. You
23
+ can follow the [Sui installation instruction](https://docs.sui.io/build/install)
24
+ to get everything set up.
25
+
26
+ This template uses `testnet` by default, so we'll need to set up a testnet
27
+ environment in the CLI:
28
+
29
+ ```bash
30
+ sui client new-env --alias testnet --rpc https://fullnode.testnet.sui.io:443
31
+ sui client switch --env testnet
32
+ ```
33
+
34
+ If you haven't set up an address in the sui client yet, you can use the
35
+ following command to get a new address:
36
+
37
+ ```bash
38
+ sui client new-address secp256k1
39
+ ```
40
+
41
+ This well generate a new address and recover phrase for you. You can mark a
42
+ newly created address as you active address by running the following command
43
+ with your new address:
44
+
45
+ ```bash
46
+ sui client switch --address 0xYOUR_ADDRESS...
47
+ ```
48
+
49
+ We can ensure we have some Sui in our new wallet by requesting Sui from the
50
+ faucet `https://faucet.sui.io`.
51
+
52
+ ### Publishing the move package
53
+
54
+ The move code for this template is located in the `move` directory. To publish
55
+ it, you can enter the `move` directory, and publish it with the Haneul CLI:
56
+
57
+ ```bash
58
+ cd move
59
+ sui client publish --gas-budget 100000000 counter
60
+ ```
61
+
62
+ In the output there will be an object with a `"packageId"` property. You'll want
63
+ to save that package ID to the `src/constants.ts` file as `PACKAGE_ID`:
64
+
65
+ ```ts
66
+ export const TESTNET_COUNTER_PACKAGE_ID = "<YOUR_PACKAGE_ID>";
67
+ ```
68
+
69
+ Now that we have published the move code, and update the package ID, we can
70
+ start the app.
71
+
72
+ ## Starting your dApp
73
+
74
+ To install dependencies you can run
75
+
76
+ ```bash
77
+ pnpm install
78
+ ```
79
+
80
+ To start your dApp in development mode run
81
+
82
+ ```bash
83
+ pnpm dev
84
+ ```
85
+
86
+ ## Building
87
+
88
+ To build your app for deployment you can run
89
+
90
+ ```bash
91
+ pnpm build
92
+ ```
@@ -0,0 +1,59 @@
1
+ <!doctype html>
2
+ <html lang="en" class="dark-theme" style="color-scheme: dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Sui dApp Starter</title>
8
+
9
+ <style>
10
+ /*
11
+ Josh's Custom CSS Reset
12
+ https://www.joshwcomeau.com/css/custom-css-reset/
13
+ */
14
+ *,
15
+ *::before,
16
+ *::after {
17
+ box-sizing: border-box;
18
+ }
19
+ * {
20
+ margin: 0;
21
+ }
22
+ body {
23
+ line-height: 1.5;
24
+ -webkit-font-smoothing: antialiased;
25
+ }
26
+ img,
27
+ picture,
28
+ video,
29
+ canvas,
30
+ svg {
31
+ display: block;
32
+ max-width: 100%;
33
+ }
34
+ input,
35
+ button,
36
+ textarea,
37
+ select {
38
+ font: inherit;
39
+ }
40
+ p,
41
+ h1,
42
+ h2,
43
+ h3,
44
+ h4,
45
+ h5,
46
+ h6 {
47
+ overflow-wrap: break-word;
48
+ }
49
+ #root,
50
+ #__next {
51
+ isolation: isolate;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div id="root"></div>
57
+ <script type="module" src="/src/main.tsx"></script>
58
+ </body>
59
+ </html>
@@ -0,0 +1,10 @@
1
+ [package]
2
+ name = "counter"
3
+ version = "0.0.1"
4
+ edition = "2024.beta"
5
+
6
+ [dependencies]
7
+ Haneul = { git = "https://github.com/GeunhwaJeong/haneul.git", subdir = "crates/haneul-framework/packages/haneul-framework", rev = "framework/testnet" }
8
+
9
+ [addresses]
10
+ counter = "0x0"
@@ -0,0 +1,36 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /// This example demonstrates a basic use of a shared object.
5
+ /// Rules:
6
+ /// - anyone can create and share a counter
7
+ /// - everyone can increment a counter by 1
8
+ /// - the owner of the counter can reset it to any value
9
+ module counter::counter {
10
+ /// A shared counter.
11
+ public struct Counter has key {
12
+ id: UID,
13
+ owner: address,
14
+ value: u64
15
+ }
16
+
17
+ /// Create and share a Counter object.
18
+ public fun create(ctx: &mut TxContext) {
19
+ transfer::share_object(Counter {
20
+ id: object::new(ctx),
21
+ owner: ctx.sender(),
22
+ value: 0
23
+ })
24
+ }
25
+
26
+ /// Increment a counter by 1.
27
+ public fun increment(counter: &mut Counter) {
28
+ counter.value = counter.value + 1;
29
+ }
30
+
31
+ /// Set value (only runnable by the Counter owner)
32
+ public fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
33
+ assert!(counter.owner == ctx.sender(), 0);
34
+ counter.value = value;
35
+ }
36
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@haneullabs/template-react-e2e-coinflip",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@haneullabs/dapp-kit": "workspace:*",
14
+ "@haneullabs/haneul": "workspace:*",
15
+ "@radix-ui/colors": "^3.0.0",
16
+ "@radix-ui/react-icons": "^1.3.0",
17
+ "@radix-ui/themes": "^3.2.1",
18
+ "@tanstack/react-query": "^5.90.11",
19
+ "react": "^19.2.1",
20
+ "react-dom": "^19.2.1",
21
+ "react-spinners": "^0.17.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/react": "^19.2.7",
25
+ "@types/react-dom": "^19.2.3",
26
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
27
+ "@typescript-eslint/parser": "^8.48.1",
28
+ "@vitejs/plugin-react-swc": "^4.2.2",
29
+ "eslint": "^9.17.0",
30
+ "eslint-plugin-react-hooks": "^5.2.0",
31
+ "eslint-plugin-react-refresh": "^0.4.20",
32
+ "prettier": "^3.7.3",
33
+ "typescript": "^5.9.3",
34
+ "vite": "^7.2.6"
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ // eslint-disable-next-line no-undef
2
+ module.exports = {
3
+ proseWrap: "always",
4
+ };
@@ -0,0 +1,61 @@
1
+ import { ConnectButton, useCurrentAccount } from "@haneullabs/dapp-kit";
2
+ import { isValidHaneulObjectId } from "@haneullabs/haneul/utils";
3
+ import { Box, Container, Flex, Heading } from "@radix-ui/themes";
4
+ import { useState } from "react";
5
+ import { Counter } from "./Counter";
6
+ import { CreateCounter } from "./CreateCounter";
7
+
8
+ function App() {
9
+ const currentAccount = useCurrentAccount();
10
+ const [counterId, setCounter] = useState(() => {
11
+ const hash = window.location.hash.slice(1);
12
+ return isValidHaneulObjectId(hash) ? hash : null;
13
+ });
14
+
15
+ return (
16
+ <>
17
+ <Flex
18
+ position="sticky"
19
+ px="4"
20
+ py="2"
21
+ justify="between"
22
+ style={{
23
+ borderBottom: "1px solid var(--gray-a2)",
24
+ }}
25
+ >
26
+ <Box>
27
+ <Heading>dApp Starter Template</Heading>
28
+ </Box>
29
+
30
+ <Box>
31
+ <ConnectButton />
32
+ </Box>
33
+ </Flex>
34
+ <Container>
35
+ <Container
36
+ mt="5"
37
+ pt="2"
38
+ px="4"
39
+ style={{ background: "var(--gray-a2)", minHeight: 500 }}
40
+ >
41
+ {currentAccount ? (
42
+ counterId ? (
43
+ <Counter id={counterId} />
44
+ ) : (
45
+ <CreateCounter
46
+ onCreated={(id) => {
47
+ window.location.hash = id;
48
+ setCounter(id);
49
+ }}
50
+ />
51
+ )
52
+ ) : (
53
+ <Heading>Please connect your wallet</Heading>
54
+ )}
55
+ </Container>
56
+ </Container>
57
+ </>
58
+ );
59
+ }
60
+
61
+ export default App;
@@ -0,0 +1,106 @@
1
+ import {
2
+ useCurrentAccount,
3
+ useSignAndExecuteTransaction,
4
+ useHaneulClient,
5
+ useHaneulClientQuery,
6
+ } from "@haneullabs/dapp-kit";
7
+ import type { HaneulObjectData } from "@haneullabs/haneul/client";
8
+ import { Transaction } from "@haneullabs/haneul/transactions";
9
+ import { Button, Flex, Heading, Text } from "@radix-ui/themes";
10
+ import { useNetworkVariable } from "./networkConfig";
11
+ import { useState } from "react";
12
+ import ClipLoader from "react-spinners/ClipLoader";
13
+
14
+ export function Counter({ id }: { id: string }) {
15
+ const counterPackageId = useNetworkVariable("counterPackageId");
16
+ const haneulClient = useHaneulClient();
17
+ const currentAccount = useCurrentAccount();
18
+ const { mutate: signAndExecute } = useSignAndExecuteTransaction();
19
+ const { data, isPending, error, refetch } = useHaneulClientQuery("getObject", {
20
+ id,
21
+ options: {
22
+ showContent: true,
23
+ showOwner: true,
24
+ },
25
+ });
26
+
27
+ const [waitingForTxn, setWaitingForTxn] = useState("");
28
+
29
+ const executeMoveCall = (method: "increment" | "reset") => {
30
+ setWaitingForTxn(method);
31
+
32
+ const tx = new Transaction();
33
+
34
+ if (method === "reset") {
35
+ tx.moveCall({
36
+ arguments: [tx.object(id), tx.pure.u64(0)],
37
+ target: `${counterPackageId}::counter::set_value`,
38
+ });
39
+ } else {
40
+ tx.moveCall({
41
+ arguments: [tx.object(id)],
42
+ target: `${counterPackageId}::counter::increment`,
43
+ });
44
+ }
45
+
46
+ signAndExecute(
47
+ {
48
+ transaction: tx,
49
+ },
50
+ {
51
+ onSuccess: (tx) => {
52
+ haneulClient.waitForTransaction({ digest: tx.digest }).then(async () => {
53
+ await refetch();
54
+ setWaitingForTxn("");
55
+ });
56
+ },
57
+ },
58
+ );
59
+ };
60
+
61
+ if (isPending) return <Text>Loading...</Text>;
62
+
63
+ if (error) return <Text>Error: {error.message}</Text>;
64
+
65
+ if (!data.data) return <Text>Not found</Text>;
66
+
67
+ const ownedByCurrentAccount =
68
+ getCounterFields(data.data)?.owner === currentAccount?.address;
69
+
70
+ return (
71
+ <>
72
+ <Heading size="3">Counter {id}</Heading>
73
+
74
+ <Flex direction="column" gap="2">
75
+ <Text>Count: {getCounterFields(data.data)?.value}</Text>
76
+ <Flex direction="row" gap="2">
77
+ <Button
78
+ onClick={() => executeMoveCall("increment")}
79
+ disabled={waitingForTxn !== ""}
80
+ >
81
+ {waitingForTxn === "increment" ? (
82
+ <ClipLoader size={20} />
83
+ ) : (
84
+ "Increment"
85
+ )}
86
+ </Button>
87
+ {ownedByCurrentAccount ? (
88
+ <Button
89
+ onClick={() => executeMoveCall("reset")}
90
+ disabled={waitingForTxn !== ""}
91
+ >
92
+ {waitingForTxn === "reset" ? <ClipLoader size={20} /> : "Reset"}
93
+ </Button>
94
+ ) : null}
95
+ </Flex>
96
+ </Flex>
97
+ </>
98
+ );
99
+ }
100
+ function getCounterFields(data: HaneulObjectData) {
101
+ if (data.content?.dataType !== "moveObject") {
102
+ return null;
103
+ }
104
+
105
+ return data.content.fields as { value: number; owner: string };
106
+ }
@@ -0,0 +1,60 @@
1
+ import { Transaction } from "@haneullabs/haneul/transactions";
2
+ import { Button, Container } from "@radix-ui/themes";
3
+ import { useSignAndExecuteTransaction, useHaneulClient } from "@haneullabs/dapp-kit";
4
+ import { useNetworkVariable } from "./networkConfig";
5
+ import ClipLoader from "react-spinners/ClipLoader";
6
+
7
+ export function CreateCounter({
8
+ onCreated,
9
+ }: {
10
+ onCreated: (id: string) => void;
11
+ }) {
12
+ const counterPackageId = useNetworkVariable("counterPackageId");
13
+ const haneulClient = useHaneulClient();
14
+ const {
15
+ mutate: signAndExecute,
16
+ isSuccess,
17
+ isPending,
18
+ } = useSignAndExecuteTransaction();
19
+
20
+ function create() {
21
+ const tx = new Transaction();
22
+
23
+ tx.moveCall({
24
+ arguments: [],
25
+ target: `${counterPackageId}::counter::create`,
26
+ });
27
+
28
+ signAndExecute(
29
+ {
30
+ transaction: tx,
31
+ },
32
+ {
33
+ onSuccess: async ({ digest }) => {
34
+ const { effects } = await haneulClient.waitForTransaction({
35
+ digest: digest,
36
+ options: {
37
+ showEffects: true,
38
+ },
39
+ });
40
+
41
+ onCreated(effects?.created?.[0]?.reference?.objectId!);
42
+ },
43
+ },
44
+ );
45
+ }
46
+
47
+ return (
48
+ <Container>
49
+ <Button
50
+ size="3"
51
+ onClick={() => {
52
+ create();
53
+ }}
54
+ disabled={isSuccess || isPending}
55
+ >
56
+ {isSuccess || isPending ? <ClipLoader size={20} /> : "Create Counter"}
57
+ </Button>
58
+ </Container>
59
+ );
60
+ }
@@ -0,0 +1,3 @@
1
+ export const DEVNET_COUNTER_PACKAGE_ID = "0xTODO";
2
+ export const TESTNET_COUNTER_PACKAGE_ID = "0xTODO";
3
+ export const MAINNET_COUNTER_PACKAGE_ID = "0xTODO";
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import "@haneullabs/dapp-kit/dist/index.css";
4
+ import "@radix-ui/themes/styles.css";
5
+
6
+ import { HaneulClientProvider, WalletProvider } from "@haneullabs/dapp-kit";
7
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8
+ import { Theme } from "@radix-ui/themes";
9
+ import App from "./App.tsx";
10
+ import { networkConfig } from "./networkConfig.ts";
11
+
12
+ const queryClient = new QueryClient();
13
+
14
+ ReactDOM.createRoot(document.getElementById("root")!).render(
15
+ <React.StrictMode>
16
+ <Theme appearance="dark">
17
+ <QueryClientProvider client={queryClient}>
18
+ <HaneulClientProvider networks={networkConfig} defaultNetwork="testnet">
19
+ <WalletProvider autoConnect>
20
+ <App />
21
+ </WalletProvider>
22
+ </HaneulClientProvider>
23
+ </QueryClientProvider>
24
+ </Theme>
25
+ </React.StrictMode>,
26
+ );
@@ -0,0 +1,31 @@
1
+ import { getFullnodeUrl } from "@haneullabs/haneul/client";
2
+ import {
3
+ DEVNET_COUNTER_PACKAGE_ID,
4
+ TESTNET_COUNTER_PACKAGE_ID,
5
+ MAINNET_COUNTER_PACKAGE_ID,
6
+ } from "./constants.ts";
7
+ import { createNetworkConfig } from "@haneullabs/dapp-kit";
8
+
9
+ const { networkConfig, useNetworkVariable, useNetworkVariables } =
10
+ createNetworkConfig({
11
+ devnet: {
12
+ url: getFullnodeUrl("devnet"),
13
+ variables: {
14
+ counterPackageId: DEVNET_COUNTER_PACKAGE_ID,
15
+ },
16
+ },
17
+ testnet: {
18
+ url: getFullnodeUrl("testnet"),
19
+ variables: {
20
+ counterPackageId: TESTNET_COUNTER_PACKAGE_ID,
21
+ },
22
+ },
23
+ mainnet: {
24
+ url: getFullnodeUrl("mainnet"),
25
+ variables: {
26
+ counterPackageId: MAINNET_COUNTER_PACKAGE_ID,
27
+ },
28
+ },
29
+ });
30
+
31
+ export { useNetworkVariable, useNetworkVariables, networkConfig };
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["src"],
24
+ "references": [{ "path": "./tsconfig.node.json" }]
25
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.mts"]
10
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react-swc";
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ });