@dappql/react 1.0.5 → 1.0.9
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
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @dappql/react
|
|
2
|
+
|
|
3
|
+
> React hooks for [DappQL](https://github.com/dappql/core). Typed, batched smart-contract reads and writes on top of [wagmi](https://wagmi.sh) + [viem](https://viem.sh), with automatic multicall fusion across your entire component tree, per-block reactivity, iterator queries, and mutation tracking.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @dappql/react wagmi viem @tanstack/react-query
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Pair with the [`dappql`](https://www.npmjs.com/package/dappql) CLI to generate your typed contract modules.
|
|
12
|
+
|
|
13
|
+
## Provider
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { WagmiProvider } from 'wagmi'
|
|
17
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
18
|
+
import { DappQLProvider } from '@dappql/react'
|
|
19
|
+
|
|
20
|
+
const queryClient = new QueryClient()
|
|
21
|
+
|
|
22
|
+
export function Root({ children }) {
|
|
23
|
+
return (
|
|
24
|
+
<WagmiProvider config={wagmiConfig}>
|
|
25
|
+
<QueryClientProvider client={queryClient}>
|
|
26
|
+
<DappQLProvider watchBlocks>{children}</DappQLProvider>
|
|
27
|
+
</QueryClientProvider>
|
|
28
|
+
</WagmiProvider>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Provider options:
|
|
34
|
+
|
|
35
|
+
| Option | Purpose |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `watchBlocks` | Refetch on every new block, makes reads reactive to chain state. |
|
|
38
|
+
| `simulateMutations` | Preflight every tx via `eth_call`. Aborts on revert. |
|
|
39
|
+
| `onMutationUpdate` | Single callback for every transaction lifecycle event, one place to drive toasts, analytics, receipts. |
|
|
40
|
+
| `addressResolver` | Function that resolves contract names to addresses, for registries, proxies, multi-deploy. |
|
|
41
|
+
| `AddressResolverComponent` | Async alternative to `addressResolver` when the resolver needs hooks. |
|
|
42
|
+
|
|
43
|
+
## Reads
|
|
44
|
+
|
|
45
|
+
### `useContextQuery`: the default
|
|
46
|
+
|
|
47
|
+
Batches calls across your **entire** component tree into one multicall.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Token, ToDo } from './contracts'
|
|
51
|
+
import { useContextQuery } from '@dappql/react'
|
|
52
|
+
|
|
53
|
+
function Dashboard({ account }) {
|
|
54
|
+
const { data, isLoading } = useContextQuery({
|
|
55
|
+
balance: Token.call.balanceOf(account),
|
|
56
|
+
symbol: Token.call.symbol(),
|
|
57
|
+
totalTasks: ToDo.call.totalTasks(),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if (isLoading) return <Spinner />
|
|
61
|
+
return <p>{data.balance.toString()} {data.symbol}</p>
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If `<Dashboard>` and `<Sidebar>` both use `useContextQuery`, their calls fuse into one RPC, not two.
|
|
66
|
+
|
|
67
|
+
### `useQuery`: component-scoped batching
|
|
68
|
+
|
|
69
|
+
Same shape as `useContextQuery`, but scoped to this hook call. Use when you need `blockNumber`, `paused`, custom `refetchInterval`, or `batchSize` overrides.
|
|
70
|
+
|
|
71
|
+
### `useSingleQuery` / `useSingleContextQuery`
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const { data } = useSingleContextQuery(Token.call.balanceOf(account))
|
|
75
|
+
// data: bigint (inferred from the ABI)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `useIteratorQuery`: on-chain arrays
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useIteratorQuery } from '@dappql/react'
|
|
82
|
+
|
|
83
|
+
const { data } = useIteratorQuery(totalTasks, (i) => ToDo.call.taskAt(account, i))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Writes
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useMutation } from '@dappql/react'
|
|
90
|
+
import { ToDo } from './contracts'
|
|
91
|
+
|
|
92
|
+
function NewTask() {
|
|
93
|
+
const mutation = useMutation(ToDo.mutation.addItem, 'Add task')
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
disabled={mutation.isLoading}
|
|
98
|
+
onClick={() => mutation.send('Buy milk', 0n)}
|
|
99
|
+
>
|
|
100
|
+
{mutation.confirmation.isSuccess ? 'Added' : 'Add task'}
|
|
101
|
+
</button>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Surface:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
mutation.send(...args) // broadcast; spread args, not array
|
|
110
|
+
mutation.simulate(...args) // manual preflight
|
|
111
|
+
mutation.estimate(...args) // gas estimate
|
|
112
|
+
mutation.isPending // awaiting signature
|
|
113
|
+
mutation.isLoading // awaiting signature OR mining
|
|
114
|
+
mutation.confirmation.isSuccess // receipt confirmed
|
|
115
|
+
mutation.reset()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Fluent request API
|
|
119
|
+
|
|
120
|
+
Every generated call exposes a small fluent API for overrides:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
Token.call.balanceOf(account)
|
|
124
|
+
.at('0x...') // override deploy address
|
|
125
|
+
.defaultTo(0n) // default value until the call resolves
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Related packages
|
|
129
|
+
|
|
130
|
+
| Package | Purpose |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| [`dappql`](https://www.npmjs.com/package/dappql) | Codegen CLI, generates the typed contract modules you import above |
|
|
133
|
+
| [`@dappql/async`](https://www.npmjs.com/package/@dappql/async) | Non-React runtime, same typed calls, no React required |
|
|
134
|
+
| [`@dappql/codegen`](https://www.npmjs.com/package/@dappql/codegen) | Framework-agnostic codegen engine |
|
|
135
|
+
| [`@dappql/mcp`](https://www.npmjs.com/package/@dappql/mcp) | MCP server, live contract context for AI coding agents |
|
|
136
|
+
|
|
137
|
+
## Full documentation
|
|
138
|
+
|
|
139
|
+
[github.com/dappql/core](https://github.com/dappql/core)
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextQueryManager.d.ts","sourceRoot":"","sources":["../src/ContextQueryManager.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,GAAG,EAAa,OAAO,EAAE,MAAM,MAAM,CAAA;AAE9C,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAW,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGxH,KAAK,KAAK,GAAG;IAAE,UAAU,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAAE,CAAA;AAC/F;;GAEG;AACH,KAAK,IAAI,GAAG;IACV,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,GAAG,CAAA;IACR,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,SAAS,GAAG,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;GAEG;AACH,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAA;CACd,CAAA;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,UAAU,CAAqD;IACvE,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAsC;IAEtD;;;;OAIG;gBACS,QAAQ,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,GAAG,EAAE,eAAe,CAAC,EAAE,uBAAuB;IAKpG,eAAe,IAAI,YAAY;IAI/B,UAAU,IAAI,MAAM;IAIpB,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;IAQ9B,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAM7B,OAAO,CAAC,gBAAgB;IAsCxB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,uBAAuB;IAKrD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM;CAuBtD;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GACtB,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"ContextQueryManager.d.ts","sourceRoot":"","sources":["../src/ContextQueryManager.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,GAAG,EAAa,OAAO,EAAE,MAAM,MAAM,CAAA;AAE9C,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAW,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGxH,KAAK,KAAK,GAAG;IAAE,UAAU,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAAE,CAAA;AAC/F;;GAEG;AACH,KAAK,IAAI,GAAG;IACV,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,GAAG,CAAA;IACR,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,SAAS,GAAG,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;GAEG;AACH,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAA;CACd,CAAA;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,UAAU,CAAqD;IACvE,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAsC;IAEtD;;;;OAIG;gBACS,QAAQ,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,GAAG,EAAE,eAAe,CAAC,EAAE,uBAAuB;IAKpG,eAAe,IAAI,YAAY;IAI/B,UAAU,IAAI,MAAM;IAIpB,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM;IAQ9B,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAM7B,OAAO,CAAC,gBAAgB;IAsCxB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,uBAAuB;IAKrD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM;CAuBtD;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GACtB,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,GAAG,iBAAiB,2CAwCvC;AAED,wBAAgB,uBAAuB,wBAEtC"}
|
|
@@ -116,7 +116,13 @@ export function ContextQueryProvider({ children, defaultBatchSize, watchBlocks,
|
|
|
116
116
|
const queryManager = useMemo(() => new ContextQueryManager(setQuery, addressResolver), []);
|
|
117
117
|
const [blockNumber, setBlockNumber] = useState();
|
|
118
118
|
useEffect(() => {
|
|
119
|
-
const unsubscribe = onBlockChange((
|
|
119
|
+
const unsubscribe = onBlockChange((block) => {
|
|
120
|
+
// Only set block number if it's a real block (not genesis)
|
|
121
|
+
// This prevents querying at block 0 before the subscription initializes
|
|
122
|
+
if (block > 0n) {
|
|
123
|
+
setBlockNumber(block);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
120
126
|
return () => {
|
|
121
127
|
unsubscribe();
|
|
122
128
|
};
|
|
@@ -130,7 +136,8 @@ export function ContextQueryProvider({ children, defaultBatchSize, watchBlocks,
|
|
|
130
136
|
notifyOnChangeProps: ['data', 'error'],
|
|
131
137
|
},
|
|
132
138
|
batchSize: defaultBatchSize,
|
|
133
|
-
blockNumber
|
|
139
|
+
// Don't pass blockNumber until we have a real one - let wagmi use "latest"
|
|
140
|
+
blockNumber: blockNumber && blockNumber > 0n ? blockNumber : undefined,
|
|
134
141
|
});
|
|
135
142
|
useRefetchOnBlockChange(result.refetch, watchBlocks && !!query.calls.length, blocksRefetchInterval);
|
|
136
143
|
useEffect(() => {
|
package/dist/blocksHandler.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare class BlockSubscriptionManager {
|
|
|
3
3
|
private subscribers;
|
|
4
4
|
private currentBlock;
|
|
5
5
|
subscribe(callback: Subscriber): () => boolean;
|
|
6
|
-
|
|
6
|
+
onBlockUpdated(newBlock: bigint): void;
|
|
7
7
|
}
|
|
8
8
|
export declare function useBlockNumberSubscriber(): (callback: Subscriber) => () => boolean;
|
|
9
9
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocksHandler.d.ts","sourceRoot":"","sources":["../src/blocksHandler.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;AAE/C,qBAAa,wBAAwB;IACnC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"blocksHandler.d.ts","sourceRoot":"","sources":["../src/blocksHandler.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;AAE/C,qBAAa,wBAAwB;IACnC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,YAAY,CAAgC;IAEpD,SAAS,CAAC,QAAQ,EAAE,UAAU;IAS9B,cAAc,CAAC,QAAQ,EAAE,MAAM;CAIhC;AAED,wBAAgB,wBAAwB,eAazB,UAAU,mBAKxB"}
|
package/dist/blocksHandler.js
CHANGED
|
@@ -3,15 +3,17 @@ import { usePublicClient } from 'wagmi';
|
|
|
3
3
|
export class BlockSubscriptionManager {
|
|
4
4
|
constructor() {
|
|
5
5
|
this.subscribers = new Set();
|
|
6
|
-
this.currentBlock =
|
|
6
|
+
this.currentBlock = undefined;
|
|
7
7
|
}
|
|
8
8
|
subscribe(callback) {
|
|
9
9
|
this.subscribers.add(callback);
|
|
10
|
-
//
|
|
11
|
-
|
|
10
|
+
// Only call immediately if we have a real block number
|
|
11
|
+
if (this.currentBlock !== undefined && this.currentBlock > 0n) {
|
|
12
|
+
callback(this.currentBlock);
|
|
13
|
+
}
|
|
12
14
|
return () => this.subscribers.delete(callback);
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
onBlockUpdated(newBlock) {
|
|
15
17
|
this.currentBlock = newBlock;
|
|
16
18
|
this.subscribers.forEach((sub) => sub(newBlock));
|
|
17
19
|
}
|
|
@@ -22,7 +24,7 @@ export function useBlockNumberSubscriber() {
|
|
|
22
24
|
useEffect(() => {
|
|
23
25
|
return client?.watchBlockNumber({
|
|
24
26
|
onBlockNumber: (blockNumber) => {
|
|
25
|
-
manager.
|
|
27
|
+
manager.onBlockUpdated(blockNumber);
|
|
26
28
|
},
|
|
27
29
|
});
|
|
28
30
|
}, [client, manager]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dappql/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Streamlined smart contract data fetching library for React dApps with TypeScript support",
|
|
5
5
|
"author": "DappQL Team",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/dappql/
|
|
31
|
+
"url": "git+https://github.com/dappql/core.git"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/dappql/
|
|
34
|
+
"url": "https://github.com/dappql/core/issues"
|
|
35
35
|
},
|
|
36
|
-
"homepage": "https://github.com/dappql/
|
|
36
|
+
"homepage": "https://github.com/dappql/core#readme",
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@tanstack/react-query": ">=5.0.0",
|