Skip to content

Commit f227c69

Browse files
authored
Merge pull request #21 from xdevguild/usescquery-with-result-parser
added result parser for more complex responses in useScQuery
2 parents 4668f76 + 07b798f commit f227c69

File tree

7 files changed

+1942
-4026
lines changed

7 files changed

+1942
-4026
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# MultiversX chain (can be devnet, testnet, mainnet)
66
NEXT_PUBLIC_MULTIVERSX_CHAIN = devnet
77

8-
# This is the masked/proxied public API endpoint
8+
# This is the masked/proxied MULTIVERSX_CUSTOM_API endpoint
99
# only current instance of the Dapp can use it if only API_ALLOWED_DAPP_HOST is set
1010
NEXT_PUBLIC_MULTIVERSX_API = /api/multiversx
1111

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
### [3.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v2.3.0) (2022-11-16)
1+
### [3.1.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v3.1.0) (2022-12-03)
2+
- rewritten useScQuery, but it keeps backward compatibility, you can still use simple data types like number, string and boolan as the results without ABI, if you need to catch more complex data types, you need to provide the ABI file, check for more info in the README.md file
3+
- dependencies updates
4+
5+
### [3.0.0](https://github.com/xdevguild/nextjs-dapp-template/releases/tag/v3.0.0) (2022-11-16)
26
- dependencies updates
37
- first phase of 'rebranding' into MultiversX ;)
48
- **Breaking**: `useElrondNetworkSync` is now `useNetworkSync`

README.md

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
- [multiversx-nextjs-dapp.netlify.com](https://multiversx-nextjs-dapp.netlify.com)
44

55
Nextjs alternative to the [dapp-core](https://github.com/ElrondNetwork/dapp-core).
6-
Based on [Elven Tools Dapp](https://www.elven.tools/docs/minter-dapp-introduction.html).
6+
Based on [Elven Tools Dapp](https://www.elven.tools/docs/minter-dapp-introduction.html), (It is developed simultaneously, and at some stages, it will have more core functionality)
77

88
The Dapp is built using Nextjs and a couple of helpful tools.
99
It has straightforward and complete functionality.
@@ -12,8 +12,8 @@ It has straightforward and complete functionality.
1212

1313
- it works on Nextjs
1414
- it uses erdjs 11.* without the dapp-core library.
15-
- it uses backed side redirections to hide the API endpoint. The only exposed one is `/api/multiversx` and it is used only be the dapp internally
16-
- it uses the .env file - there is an example in the repo (for all configuration, also for the demo config)
15+
it uses backed-side redirections to hide the API endpoint. The only exposed one is `/api/multiversx` and it is used only by the dapp internally
16+
- it uses the .env file - there is an example in the repo (for all configurations, also for the demo config)
1717
- it uses chakra-ui
1818

1919
### How to start it locally:
@@ -25,15 +25,15 @@ It has straightforward and complete functionality.
2525
5. `npm run dev` -> for development
2626
6. `npm run build` -> `npm start` for production
2727

28-
Check how to deploy very similar dapp using the Netlify services: https://www.elven.tools/docs/dapp-deployment.html
28+
Check how to deploy a very similar dapp using the Netlify services: https://www.elven.tools/docs/dapp-deployment.html
2929

3030
### Howto
3131

3232
For simplicity, the template uses the main index page with demo components built using the core building blocks. Below you will find the list of most essential utilities, hooks, and components with examples that are actual code from the template. You can search them in the code to better understand how they work.
3333

3434
There are much more hooks and tools, but most of them are already used in the ones listed below.
3535

36-
The code samples are not ready to copy and paste. Please search them in the code.
36+
The code samples are not ready to copy and paste. Please search for them in the code.
3737

3838
#### useNetworkSync()
3939

@@ -140,7 +140,7 @@ const handleSendTx = useCallback(() => {
140140

141141
#### useScQuery()
142142

143-
The hook uses useSWR under the hood and can be triggered on a component mount or remotely on some action. It has two different states for the pending action. For initial load and on revalidate. It also takes one of three return data types: 'number', 'string', 'boolean'. For now, it assumes that you know what data type will be returned by a smart contract. Later it will get more afvanced functionality.
143+
The hook uses useSWR under the hood and can be triggered on a component mount or remotely on some action. It has two different states for the pending action. For initial load and on revalidate. It also takes one of three return data types: 'number', 'string', 'boolean'. For now, it assumes that you know what data type will be returned by a smart contract. Later it will get more advanced functionality.
144144

145145
```jsx
146146
const {
@@ -150,16 +150,51 @@ const {
150150
isValidating, // pending state for each revalidation of the data, for example using the mutate
151151
error,
152152
} = useScQuery<number>({
153-
type: SCQueryType.NUMBER, // can be number, string or boolean
153+
type: SCQueryType.NUMBER, // can be NUMBER, STRING, BOOLEAN or COMPLEX
154154
payload: {
155155
scAddress: process.env.NEXT_PUBLIC_MINT_SMART_CONTRACT_ADDRESS,
156156
funcName: process.env.NEXT_PUBLIC_QUERY_FUNCTION_NAME,
157-
args: [],
157+
args: [], // arguments for the query in hex format, you can use erdjs for that, for example: args: [ new Address('erd1....').hex() ] etc. It will be also simplified in the future.
158158
},
159159
autoInit: false, // you can enable or disable the trigger of the query on the component mount
160+
abiJSON: yourImportedAbiJSONObject // required for SCQueryType.COMPLEX type
160161
});
161162
```
162163

164+
**Example** with `SCQueryType.COMPLEX`. This type uses `/vm-values/query`, ABI and ResultParser. The ABI JSON contents are required here. You can copy abi.json and import it in the same place you use useScQuery. Put the abi JSON file wherever you like in the codebase. I chose the `config` directory. See the example below:
165+
166+
```jsx
167+
import { TypedOutcomeBundle } from '@elrondnetwork/erdjs';
168+
import abiJSON from '../config/abi.json';
169+
170+
const { data } = useScQuery<TypedOutcomeBundle>({
171+
type: SCQueryType.COMPLEX,
172+
payload: {
173+
scAddress: 'erd1qqq...',
174+
funcName: 'yourScFunction',
175+
args: [], // args in hex format, use erdjs for conversion, see above
176+
},
177+
autoInit: true,
178+
abiJSON,
179+
});
180+
```
181+
182+
The `data` here will be a `TypedOutcomeBundle`. Which is:
183+
184+
```typescript
185+
interface TypedOutcomeBundle {
186+
returnCode: ReturnCode;
187+
returnMessage: string;
188+
values: TypedValue[];
189+
firstValue?: TypedValue;
190+
secondValue?: TypedValue;
191+
thirdValue?: TypedValue;
192+
lastValue?: TypedValue;
193+
}
194+
```
195+
196+
You can then process the data. For example `data.firstValue.valueOf()` or `data.firstValue.toString()` if applicable. The returned type can be further processed using erdjs.
197+
163198
#### useLoggingIn()
164199

165200
The hook will provide information about the authentication flow state. It will tell if the user is already logged in or is logging in.
@@ -178,15 +213,15 @@ const { address, nonce, balance } = useAccount();
178213

179214
#### useLoginInfo()
180215

181-
The hook will provide the information about the user's auth data state. The data: loginMethod, expires, loginToken, signature. Login token and signature won't always be there. It depends if you'll use the token. Check [Elven Tools Dapp backend integration article](https://www.elven.tools/docs/dapp-backend-integration.html) for more info.
216+
The hook will provide information about the user's auth data state. The data: loginMethod, expires, loginToken, signature. Login token and signature won't always be there. It depends if you'll use the token. Check [Elven Tools Dapp backend integration article](https://www.elven.tools/docs/dapp-backend-integration.html) for more info.
182217

183218
```jsx
184219
const { loginMethod, expires, loginToken, signature } = useLoginInfo();
185220
```
186221

187222
#### useApiCall()
188223

189-
The hook provides a convenient way of doing custom API calls unrelated to transactions or smart contract queries. By default it will use MultiversX API endpoint. But it can be any type of API, not only MultiversX API. In that case you would need to pass the `{ baseEndpoint: "https://some-api.com" }` in options
224+
The hook provides a convenient way of doing custom API calls unrelated to transactions or smart contract queries. By default, it will use MultiversX API endpoint. But it can be any type of API, not only MultiversX API. In that case, you would need to pass the `{ baseEndpoint: "https://some-api.com" }` in options
190225

191226
```jsx
192227
const { data, isLoading, isValidating, fetch, error } = useApiCall<Token[]>({

components/demo/SimpleScQueryDemo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const SimpleScQeryDemo = ({
3232
});
3333

3434
useEffect(() => {
35-
if (queryResult) {
35+
if (queryResult !== undefined && queryResult !== null) {
3636
cb?.(queryResult.toString(), isLoading || isValidating, error);
3737
}
3838
}, [cb, error, isLoading, isValidating, queryResult]);

hooks/core/useScQuery.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
import useSWR, { Fetcher } from 'swr';
2+
import {
3+
ResultsParser,
4+
SmartContractAbi,
5+
SmartContract,
6+
Address,
7+
AbiRegistry,
8+
} from '@elrondnetwork/erdjs';
9+
import { ContractQueryResponse } from '@elrondnetwork/erdjs-network-providers';
210
import useSwrMutation from 'swr/mutation';
311
import { apiCall } from '../../utils/apiCall';
412

513
export enum SCQueryType {
614
NUMBER = 'number',
715
STRING = 'string',
816
BOOLEAN = 'boolean',
17+
COMPLEX = 'complex',
918
}
1019

1120
interface SCQueryData {
1221
type: SCQueryType;
1322
payload?: Record<string, unknown>;
1423
options?: Record<string, unknown>;
1524
autoInit?: boolean;
25+
abiJSON?: {
26+
name: string;
27+
endpoints: unknown[];
28+
types: unknown;
29+
};
1630
}
1731

1832
interface FetcherArgs {
@@ -31,11 +45,12 @@ export const fetcher: Fetcher<VMOutput, FetcherArgs> = async ({
3145
payload,
3246
}) => await apiCall.post(url, payload || {});
3347

34-
export function useScQuery<T extends number | string | boolean>({
48+
export function useScQuery<T extends number | string | boolean | unknown>({
3549
type,
3650
payload,
3751
options,
3852
autoInit = true,
53+
abiJSON,
3954
}: SCQueryData) {
4055
let url = '';
4156

@@ -49,6 +64,10 @@ export function useScQuery<T extends number | string | boolean>({
4964
case SCQueryType.BOOLEAN:
5065
url = '/vm-values/int';
5166
break;
67+
// You need to provide ABI JSON for proper results parsing
68+
case SCQueryType.COMPLEX:
69+
url = '/vm-values/query';
70+
break;
5271
}
5372

5473
const { data, error, mutate, isValidating, isLoading } = useSWR(
@@ -72,13 +91,46 @@ export function useScQuery<T extends number | string | boolean>({
7291
revalidate: true,
7392
});
7493

75-
const parseData = (data: string | number | undefined) => {
94+
const parseData = (data: string | number | undefined | unknown) => {
95+
if (type === SCQueryType.COMPLEX && !abiJSON) {
96+
throw new Error(
97+
'Please provide the ABI JSON contents if you want to use the COMPLEX queries in useScQuery! Check README.md for more info.'
98+
);
99+
}
100+
101+
if (
102+
type === SCQueryType.COMPLEX &&
103+
abiJSON &&
104+
(data as Record<string, unknown>)?.returnData &&
105+
payload?.scAddress &&
106+
payload?.funcName
107+
) {
108+
const parser = new ResultsParser();
109+
const abiRegistry = AbiRegistry.create(abiJSON);
110+
const abi = new SmartContractAbi(abiRegistry, [abiJSON.name]);
111+
const contract = new SmartContract({
112+
address: new Address(payload.scAddress as string),
113+
abi: abi,
114+
});
115+
const endpointDefinition = contract.getEndpoint(
116+
payload.funcName as string
117+
);
118+
const smResponse = ContractQueryResponse.fromHttpResponse(data);
119+
const parsedResponse = parser.parseQueryResponse(
120+
smResponse,
121+
endpointDefinition
122+
);
123+
return parsedResponse;
124+
}
125+
76126
if (type === SCQueryType.BOOLEAN) {
77127
return Boolean(Number(data));
78128
}
129+
79130
if (type === SCQueryType.NUMBER) {
80131
return Number(data);
81132
}
133+
82134
return data;
83135
};
84136

0 commit comments

Comments
 (0)