create-qorechain-dapp 0.3.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.
- package/README.md +49 -0
- package/dist/index.js +1083 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/templates/README.md +32 -0
- package/templates/evm-solidity/.env.example +9 -0
- package/templates/evm-solidity/README.md +81 -0
- package/templates/evm-solidity/contracts/Counter.artifact.ts +49 -0
- package/templates/evm-solidity/contracts/Counter.sol +33 -0
- package/templates/evm-solidity/package.json +20 -0
- package/templates/evm-solidity/scripts/deploy.ts +98 -0
- package/templates/evm-solidity/tsconfig.json +17 -0
- package/templates/fullstack-web/.env.example +5 -0
- package/templates/fullstack-web/README.md +53 -0
- package/templates/fullstack-web/index.html +12 -0
- package/templates/fullstack-web/package.json +25 -0
- package/templates/fullstack-web/src/App.tsx +134 -0
- package/templates/fullstack-web/src/main.tsx +15 -0
- package/templates/fullstack-web/src/vite-env.d.ts +10 -0
- package/templates/fullstack-web/tsconfig.json +18 -0
- package/templates/fullstack-web/vite.config.ts +10 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
/// @title Counter
|
|
5
|
+
/// @notice A minimal storage contract: a single number you can read, set, and
|
|
6
|
+
/// increment. Used as the smallest useful example of deploying and
|
|
7
|
+
/// interacting with a contract on the QoreChain EVM Engine.
|
|
8
|
+
contract Counter {
|
|
9
|
+
/// @notice The current count.
|
|
10
|
+
uint256 public count;
|
|
11
|
+
|
|
12
|
+
/// @notice Emitted whenever the count changes.
|
|
13
|
+
event CountChanged(uint256 newCount);
|
|
14
|
+
|
|
15
|
+
/// @param initial The starting value for the counter.
|
|
16
|
+
constructor(uint256 initial) {
|
|
17
|
+
count = initial;
|
|
18
|
+
emit CountChanged(initial);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// @notice Increase the count by one.
|
|
22
|
+
function increment() external {
|
|
23
|
+
count += 1;
|
|
24
|
+
emit CountChanged(count);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// @notice Set the count to an explicit value.
|
|
28
|
+
/// @param value The new count.
|
|
29
|
+
function set(uint256 value) external {
|
|
30
|
+
count = value;
|
|
31
|
+
emit CountChanged(value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qorechain-evm-solidity-dapp",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "A QoreChain EVM dApp: a Solidity Counter contract deployed and exercised with viem via @qorechain/evm.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"deploy": "tsx scripts/deploy.ts",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@qorechain/evm": "^0.3.0",
|
|
13
|
+
"viem": "^2.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.7.5",
|
|
17
|
+
"tsx": "^4.19.0",
|
|
18
|
+
"typescript": "^5.6.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deploy.ts — deploy the Counter contract to the QoreChain EVM Engine and
|
|
3
|
+
* exercise it: read the initial count, increment it, and read it back.
|
|
4
|
+
*
|
|
5
|
+
* Uses @qorechain/evm:
|
|
6
|
+
* - createEvmClient({ endpoints }) → a viem-backed client (chain id auto-detected)
|
|
7
|
+
* - deployContract(walletClient, …) → deploy and get the tx hash
|
|
8
|
+
* - readContract / writeContract → typed reads and writes
|
|
9
|
+
*
|
|
10
|
+
* Requirements (see README):
|
|
11
|
+
* - QORE_EVM_RPC_URL pointing at a reachable QoreChain EVM JSON-RPC endpoint.
|
|
12
|
+
* - QORE_EVM_PRIVATE_KEY for a funded EVM account (0x-prefixed, 32 bytes).
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
createEvmClient,
|
|
16
|
+
deployContract,
|
|
17
|
+
evmAccountFromPrivateKey,
|
|
18
|
+
readContract,
|
|
19
|
+
writeContract,
|
|
20
|
+
} from "@qorechain/evm";
|
|
21
|
+
import type { Address, Hex } from "viem";
|
|
22
|
+
|
|
23
|
+
import { counterAbi, counterBytecode } from "../contracts/Counter.artifact.js";
|
|
24
|
+
|
|
25
|
+
function requireEnv(name: string): string {
|
|
26
|
+
const value = process.env[name];
|
|
27
|
+
if (!value) {
|
|
28
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main(): Promise<void> {
|
|
34
|
+
const evmRpc = process.env.QORE_EVM_RPC_URL ?? "http://localhost:8545";
|
|
35
|
+
const privateKey = requireEnv("QORE_EVM_PRIVATE_KEY") as Hex;
|
|
36
|
+
const initialCount = BigInt(process.env.QORE_COUNTER_INITIAL ?? "0");
|
|
37
|
+
|
|
38
|
+
const client = await createEvmClient({ endpoints: { evmRpc } });
|
|
39
|
+
const account = evmAccountFromPrivateKey(privateKey);
|
|
40
|
+
const wallet = client.getWalletClient(account);
|
|
41
|
+
|
|
42
|
+
console.log(`network: QoreChain EVM (chain id ${await client.getChainId()})`);
|
|
43
|
+
console.log(`deployer: ${account.address}`);
|
|
44
|
+
|
|
45
|
+
// Deploy.
|
|
46
|
+
console.log(`\nDeploying Counter(initial=${initialCount})…`);
|
|
47
|
+
const deployHash = await deployContract(wallet, {
|
|
48
|
+
abi: counterAbi,
|
|
49
|
+
bytecode: counterBytecode,
|
|
50
|
+
args: [initialCount],
|
|
51
|
+
});
|
|
52
|
+
const receipt = await client.publicClient.waitForTransactionReceipt({
|
|
53
|
+
hash: deployHash,
|
|
54
|
+
});
|
|
55
|
+
const contract = receipt.contractAddress;
|
|
56
|
+
if (!contract) {
|
|
57
|
+
throw new Error("Deployment receipt did not include a contract address.");
|
|
58
|
+
}
|
|
59
|
+
console.log(`deployed: ${contract}`);
|
|
60
|
+
|
|
61
|
+
// Read.
|
|
62
|
+
const before = await readContract(client.publicClient, {
|
|
63
|
+
address: contract as Address,
|
|
64
|
+
abi: counterAbi,
|
|
65
|
+
functionName: "count",
|
|
66
|
+
});
|
|
67
|
+
console.log(`count (before): ${before}`);
|
|
68
|
+
|
|
69
|
+
// Write: increment. `account` and `chain` come from the wallet client; we pass
|
|
70
|
+
// them explicitly so the call type-checks against viem's strict signature.
|
|
71
|
+
console.log("\nCalling increment()…");
|
|
72
|
+
const incHash = await writeContract(wallet, {
|
|
73
|
+
address: contract as Address,
|
|
74
|
+
abi: counterAbi,
|
|
75
|
+
functionName: "increment",
|
|
76
|
+
account: wallet.account ?? null,
|
|
77
|
+
chain: wallet.chain,
|
|
78
|
+
});
|
|
79
|
+
await client.publicClient.waitForTransactionReceipt({ hash: incHash });
|
|
80
|
+
|
|
81
|
+
// Read again.
|
|
82
|
+
const after = await readContract(client.publicClient, {
|
|
83
|
+
address: contract as Address,
|
|
84
|
+
abi: counterAbi,
|
|
85
|
+
functionName: "count",
|
|
86
|
+
});
|
|
87
|
+
console.log(`count (after): ${after}`);
|
|
88
|
+
console.log("\nDone.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main().catch((err: unknown) => {
|
|
92
|
+
console.error("\nDeploy failed.");
|
|
93
|
+
console.error(
|
|
94
|
+
"Check QORE_EVM_RPC_URL is reachable and QORE_EVM_PRIVATE_KEY funds an account.",
|
|
95
|
+
);
|
|
96
|
+
console.error(err instanceof Error ? err.message : err);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"target": "ES2021",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"lib": ["ES2022"],
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["scripts", "contracts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# QoreChain endpoints for the web app (Vite exposes only VITE_-prefixed vars).
|
|
2
|
+
# Defaults target localhost so the app runs out of the box against a local node.
|
|
3
|
+
# Point these at a testnet (qorechain-diana) or mainnet (qorechain-vladi) node.
|
|
4
|
+
VITE_QORE_REST_URL=http://localhost:1317
|
|
5
|
+
VITE_QORE_EVM_RPC_URL=http://localhost:8545
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# QoreChain full-stack web starter
|
|
2
|
+
|
|
3
|
+
A minimal [Vite](https://vitejs.dev) + React + TypeScript dApp that uses
|
|
4
|
+
[`@qorechain/sdk`](https://github.com/qorechain/qorechain-sdk) to:
|
|
5
|
+
|
|
6
|
+
- connect to QoreChain testnet (`createClient`),
|
|
7
|
+
- read a native balance for an address you enter, and
|
|
8
|
+
- read the tokenomics overview (`qor_getTokenomicsOverview`).
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- **Node.js >= 20**.
|
|
13
|
+
- A reachable QoreChain **REST** endpoint (`VITE_QORE_REST_URL`) for balances and
|
|
14
|
+
an **EVM JSON-RPC** endpoint (`VITE_QORE_EVM_RPC_URL`) for the `qor_*`
|
|
15
|
+
namespace. Both default to localhost. Point them at a testnet
|
|
16
|
+
(`qorechain-diana`) or mainnet (`qorechain-vladi`) node, or a local node.
|
|
17
|
+
|
|
18
|
+
## Setup & run
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pnpm install
|
|
22
|
+
cp .env.example .env # the scaffolder already does this for you
|
|
23
|
+
pnpm dev # http://localhost:5173
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Configure endpoints in `.env` (only `VITE_`-prefixed vars are exposed to the
|
|
27
|
+
browser):
|
|
28
|
+
|
|
29
|
+
| Variable | Purpose | Default |
|
|
30
|
+
| ---------------------- | ---------------------- | ----------------------- |
|
|
31
|
+
| `VITE_QORE_REST_URL` | Cosmos REST (balances) | `http://localhost:1317` |
|
|
32
|
+
| `VITE_QORE_EVM_RPC_URL` | EVM JSON-RPC (`qor_*`) | `http://localhost:8545` |
|
|
33
|
+
|
|
34
|
+
## Build / type-check
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
pnpm typecheck # tsc --noEmit
|
|
38
|
+
pnpm build # type-check + vite build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Using `@qorechain/sdk` before it is published
|
|
42
|
+
|
|
43
|
+
`@qorechain/sdk` is published to npm — once published, `pnpm install` just
|
|
44
|
+
works. Until then, scaffold with the CLI's `--local` flag to rewrite the
|
|
45
|
+
dependency to a `file:` link into the SDK monorepo, after building the workspace
|
|
46
|
+
packages:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# at the qorechain-sdk monorepo root
|
|
50
|
+
pnpm -r build
|
|
51
|
+
# then scaffold with --local
|
|
52
|
+
npx create-qorechain-dapp my-dapp --template fullstack-web --local
|
|
53
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>QoreChain dApp</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qorechain-fullstack-web-dapp",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "A minimal Vite + React + TypeScript QoreChain dApp using @qorechain/sdk to read balances and tokenomics.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "tsc --noEmit && vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@qorechain/sdk": "^0.3.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^18.3.0",
|
|
20
|
+
"@types/react-dom": "^18.3.0",
|
|
21
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
22
|
+
"typescript": "^5.6.3",
|
|
23
|
+
"vite": "^5.4.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { createClient, type QoreChainClient } from "@qorechain/sdk";
|
|
4
|
+
|
|
5
|
+
// Endpoints come from Vite env vars (VITE_-prefixed), with localhost defaults so
|
|
6
|
+
// the app runs out of the box against a local node. Override in `.env`.
|
|
7
|
+
const REST_URL = import.meta.env.VITE_QORE_REST_URL ?? "http://localhost:1317";
|
|
8
|
+
const EVM_RPC_URL =
|
|
9
|
+
import.meta.env.VITE_QORE_EVM_RPC_URL ?? "http://localhost:8545";
|
|
10
|
+
|
|
11
|
+
interface Balance {
|
|
12
|
+
denom: string;
|
|
13
|
+
amount: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function App(): JSX.Element {
|
|
17
|
+
// One client for the app's lifetime.
|
|
18
|
+
const client: QoreChainClient = useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
createClient({
|
|
21
|
+
network: "testnet",
|
|
22
|
+
endpoints: { rest: REST_URL, evmRpc: EVM_RPC_URL },
|
|
23
|
+
}),
|
|
24
|
+
[],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const [address, setAddress] = useState("");
|
|
28
|
+
const [balances, setBalances] = useState<Balance[] | null>(null);
|
|
29
|
+
const [tokenomics, setTokenomics] = useState<string | null>(null);
|
|
30
|
+
const [error, setError] = useState<string | null>(null);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
|
|
33
|
+
async function loadBalance(): Promise<void> {
|
|
34
|
+
setError(null);
|
|
35
|
+
setBalances(null);
|
|
36
|
+
if (!address.trim()) {
|
|
37
|
+
setError("Enter an address first.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setLoading(true);
|
|
41
|
+
try {
|
|
42
|
+
const res = await client.rest.getAllBalances(address.trim());
|
|
43
|
+
setBalances(res.balances as Balance[]);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function loadTokenomics(): Promise<void> {
|
|
52
|
+
setError(null);
|
|
53
|
+
setTokenomics(null);
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
const overview = await client.qor.getTokenomicsOverview();
|
|
57
|
+
setTokenomics(JSON.stringify(overview, null, 2));
|
|
58
|
+
} catch (err) {
|
|
59
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
60
|
+
} finally {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<main
|
|
67
|
+
style={{
|
|
68
|
+
fontFamily: "system-ui, sans-serif",
|
|
69
|
+
maxWidth: 720,
|
|
70
|
+
margin: "2rem auto",
|
|
71
|
+
padding: "0 1rem",
|
|
72
|
+
lineHeight: 1.5,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<h1>QoreChain dApp</h1>
|
|
76
|
+
<p style={{ color: "#555" }}>
|
|
77
|
+
Network: <code>{client.network.name}</code> (
|
|
78
|
+
<code>{client.network.chainId ?? "n/a"}</code>) · REST:{" "}
|
|
79
|
+
<code>{REST_URL}</code>
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<section style={{ marginTop: "1.5rem" }}>
|
|
83
|
+
<h2>Native balance</h2>
|
|
84
|
+
<div style={{ display: "flex", gap: "0.5rem" }}>
|
|
85
|
+
<input
|
|
86
|
+
value={address}
|
|
87
|
+
onChange={(e) => setAddress(e.target.value)}
|
|
88
|
+
placeholder="qor1…"
|
|
89
|
+
style={{ flex: 1, padding: "0.5rem" }}
|
|
90
|
+
aria-label="QoreChain address"
|
|
91
|
+
/>
|
|
92
|
+
<button onClick={loadBalance} disabled={loading}>
|
|
93
|
+
Get balance
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
{balances && (
|
|
97
|
+
<ul>
|
|
98
|
+
{balances.length === 0 && <li>(no balances)</li>}
|
|
99
|
+
{balances.map((b) => (
|
|
100
|
+
<li key={b.denom}>
|
|
101
|
+
{b.amount} {b.denom}
|
|
102
|
+
</li>
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
)}
|
|
106
|
+
</section>
|
|
107
|
+
|
|
108
|
+
<section style={{ marginTop: "1.5rem" }}>
|
|
109
|
+
<h2>Tokenomics overview</h2>
|
|
110
|
+
<button onClick={loadTokenomics} disabled={loading}>
|
|
111
|
+
Read qor_getTokenomicsOverview
|
|
112
|
+
</button>
|
|
113
|
+
{tokenomics && (
|
|
114
|
+
<pre
|
|
115
|
+
style={{
|
|
116
|
+
background: "#f5f5f5",
|
|
117
|
+
padding: "1rem",
|
|
118
|
+
overflowX: "auto",
|
|
119
|
+
borderRadius: 6,
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{tokenomics}
|
|
123
|
+
</pre>
|
|
124
|
+
)}
|
|
125
|
+
</section>
|
|
126
|
+
|
|
127
|
+
{error && (
|
|
128
|
+
<p style={{ color: "#b00020", marginTop: "1rem" }} role="alert">
|
|
129
|
+
{error}
|
|
130
|
+
</p>
|
|
131
|
+
)}
|
|
132
|
+
</main>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
import { App } from "./App.js";
|
|
5
|
+
|
|
6
|
+
const container = document.getElementById("root");
|
|
7
|
+
if (!container) {
|
|
8
|
+
throw new Error("Root element #root not found");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
createRoot(container).render(
|
|
12
|
+
<React.StrictMode>
|
|
13
|
+
<App />
|
|
14
|
+
</React.StrictMode>,
|
|
15
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"target": "ES2021",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"useDefineForClassFields": true,
|
|
15
|
+
"types": ["vite/client"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["src", "vite.config.ts"]
|
|
18
|
+
}
|