Skip to main content

DApp-wallet integration development

Introduction

This article is the reference material for developing the integration between a DApp front end and Hathor official wallet applications (desktop and mobile).

Context

Hathor official desktop and mobile wallet applications expose a JSON-RPC API designed for communication with DApps. This is achieved using the WalletConnect sign protocol as a transport layer, where message payloads are JSON-RPC protocol requests/responses. WalletConnect sign protocol is part of the Reown solution (formerly known as WalletConnect), which consists of a set of web3 SDKs and services.

Communication between DApps and wallet applications does not occur directly; it is mediated by a relay server that is part of the Reown solution, which uses a pub/sub (publisher-subscriber) model for message exchange between parties.

Additionally, the Reown solution provides the WalletConnect modal, a UI component that enables the DApp user to manage the connection with his or her wallet application. Its main functionalities are generating and displaying a QR code for the user to establish the DApp-wallet connection, and keeping him or her informed about the connection status.

In a nutshell, to integrate your DApp with Hathor official wallet applications, you will need to use the WalletConnect sign client and the WalletConnect modal in your code, both provided by the Reown solution1.

Integration workflow

In this section, we outline the tasks you need to complete to implement the integration:

  1. Sign up to Reown cloud.
  2. Install packages.
  3. Create WalletConnect sign client.
  4. Create session proposal for wallet.
  5. Display pairing modal.
  6. Complete DApp-wallet pairing.
  7. Set up listeners.
  8. Send session requests.

Each of the following sections will detail one of these tasks. To illustrate the implementation of each task, we will provide web application examples using sample code snippets in JavaScript. Additionally, at the end of the article, we will present real implementation code from the bet demo DApp using React contexts. The bet demo DApp is a web application written in TypeScript, using Next.js framework, as a proof of concept to demonstrate how DApps work on Hathor platform.

Sign up to Reown cloud

As previously mentioned, the communication between DApps and Hathor official wallet applications using the Reown solution requires the mediation of a relay server. At the time of writing (Oct 10, 2024), it is necessary to use the centralized relay service provided by the Reown company itself through its cloud platform2.

You need to create an account on Reown cloud and register your project. By doing this, you will obtain a project ID that will be used to authenticate the connection between your DApp and the relay service provided by Reown cloud.

Install packages

Reown provides packages to install the WalletConnect sign client and modal on all major application development platforms, such as web, iOS, Android, Flutter, Unity, etc. To know how to install it on your platform, see WalletConnect sign installation at Reown docs, and WalletConnect modal installation at Reown docs.

Create WalletConnect sign client

For any operation to be performed in a DApp, the user must have their wallet connected. Therefore, a DApp typically prompts the user to connect their wallet upon access or when they attempt to complete their first operation.

In DApps using the Reown solution, such as yours, when the user presses the "connect wallet" button, the first action that occurs in your DApp's front end will be creating the WalletConnect sign client. The following code provides an example creation of a WalletConnect sign client:

import SignClient from "@walletconnect/sign-client";

const signClient = await SignClient.init({
projectId: "<your_project_ID>",
relayUrl: "<your_chosen_relay_server>",
metadata: {
name: "Your DApp",
description: "Your DApp description",
url: "https://your-dapp.com",
icons: ["https://your-dapp.com/icon.png"],
},
});

The SignClient class is used to instantiate the WalletConnect sign client. Initializing the client requires the Project ID you obtained from Reown Cloud. The client connects to the Reown Cloud relay server during initialization.

Create session proposal for wallet

The DApp and wallet application communicate via a session intermediated by the relay server. As soon as the WalletConnect sign client connects to the relay server, its next action should be to create a session proposal to be established with the user's wallet. To do this, you will need to use the connect method from the SignClient API. The following code provides an example of session proposal creation by the sign client:

const { uri, approval } = await signClient.connect({
requiredNamespaces: {
hathor: {
methods: ['htr_signWithAddress', 'htr_sendNanoContractTx'],
chains: ['hathor:testnet'], // or 'hathor:mainnet'
events: []
},
},
});

As parameters, you send which methods of the JSON-RPC API can be called during the session, which blockchain network (chains) the DApp and wallet will be connected to (in case of Hathor, mainnet, testnet, or privatenet), and which wallet events (not session related events) the DApp will receive.

The relay server will immediately return an object containing two main properties:

  • uri: a string representing the session to be established between the DApp and the user's wallet. It should be displayed in the UI so that the user can connect to the session on the relay server.
  • approval: a promise that will be resolved when the user approves or rejects the session in their wallet.

Note that when the DApp receives this response, the wallet is not yet connected (and is not even aware) of the session. This is only the first step of what is called pairing between them. Only after the promise is resolved will the pairing be complete. You should use this promise to wait for the user's response.

Display pairing modal

Pairing is the process of establishing a session between the DApp and its user's wallet. The DApp's next action should be to create and display the pairing modal. The modal will present the user with the URI they need to use on their wallet application device to connect to the appropriate session on the relay server. The following code provides an example of the creation and display of the modal:

import { WalletConnectModal } from '@walletconnect/modal';

// Create modal
const walletConnectModal = new WalletConnectModal({
projectId: "<your_project_ID>",
standaloneChains: ['hathor:testnet']
});

// Display modal
if (uri) {
walletConnectModal.openModal({ uri });
}

The modal will present the following elements to the DApp user:

  • A QR code to be scanned by the user's mobile device using their wallet application.
  • A button to copy and paste the URI, as an alternative to scanning the QR code with a camera.
  • A list of wallet applications that support the Hathor Network and connection to DApps via WalletConnect sign protocol. Selecting an option from this list opens a deep link in the wallet application. This only works when the user is with both DApp and wallet application on the same device.

Complete DApp-wallet pairing

Now it’s the DApp user’s turn. The user will scan the QR code, copy the URI, or use the deep link to allow their wallet application to connect to the relay server. The wallet application will then prompt the user to approve or reject the pairing with the DApp. Once approved, the promise your DApp is waiting for will be resolved, a session will be established, and the user can start using the DApp. The following code provides a complete example of the pairing process:

import { WalletConnectModal } from '@walletconnect/modal';

// Create modal
const walletConnectModal = new WalletConnectModal({
projectId: "<your_project_ID>",
standaloneChains: ['hathor:testnet']
});

// Pairing process
try {
const { uri, approval } = await signClient.connect({
requiredNamespaces: {
hathor: {
methods: [
'htr_signWithAddress',
'htr_sendNanoContractTx'
],
chains: ['hathor:testnet'],
events: []
}
}
});

// Open modal if a URI was returned
if (uri) {
walletConnectModal.openModal({ uri });

// Await session approval from the wallet
const session = await approval();

// Handle the returned session (e.g. update UI to "connected" state)
// * You will need to create this function *
onSessionConnect(session);

// Close the QRCode modal in case it was open.
walletConnectModal.closeModal();
}
} catch (e) {
console.error(e);
}

Note that this code snippet also repeats the same actions from the previous two sections, but it has been refactored to present the entire pairing process.

Set up listeners

With the session between your DApp and the user's wallet established, you now need to set up listeners to capture and handle events from the wallet that may be useful for your DApp. The following code provides an example of setting up some useful listeners for your DApp:

signClient.on('session_event', ({ event }) => {
// Handle session events defined in requiredNamespaces
});

signClient.on('session_update', ({ topic, params }) => {
const { namespaces } = params;
const _session = signClient.session.get(topic);
// Overwrite the `namespaces` of the existing session with the incoming one.
const updatedSession = { ..._session, namespaces };
// Integrate the updated session state into your dapp state.
onSessionUpdate(updatedSession);
});

signClient.on('session_delete', () => {
// Session was deleted -> reset the dapp state, clean up from user session, etc.
//Example — clean DApp state
resetAppState();
//Example — notify user
alert('Session has been closed. Please reconnect your wallet.');
});

This snippet sets up three listeners for events related to the WalletConnect session:

  • session_event: notifies the occurrence of one of the events defined when you invoked client.connect in the events property. It is typically used to inform, for example, that the wallet application has switched to a different active wallet or blockchain network (chain) it is connected to (mainnet, testnet, etc.).
  • session_update: notifies updates to the session state, such as the addition of networks (chains), methods, or events.
  • session_delete: notifies that the wallet application has ended the session, allowing you to reset the DApp to a non-connected state.

Note that this is just a sample of the events that can be received and sent between the DApp and the wallet. For a complete list of session events, see Session events at Reown docs.

Send session requests

With the session established, the user can finally make use of the functionalities provided by the DApp. A single user operation in the DApp may require multiple calls to the JSON-RPC API.

For example, to perform a single operation, the DApp may need to:

  • obtain a user's address by calling the htr_getAddress method;
  • check the user's balance for a given token using htr_getBalance;
  • retrieve user's UTXOs using htr_getUtxos;
  • ask the user to create a token using htr_createToken; and finally
  • request the user to create or execute a contract using htr_sendNanoContractTx.

The last part of a user operation in the DApp always involves creating or (most commonly) executing a nano contract. The straightforward way for your DApp to do this is by using the RPC method htr_sendNanoContractTx. Your DApp uses htr_sendNanoContractTx to request the wallet to create and submit a nano contract transaction to Hathor Network, with parameters derived from the user's actions in your DApp's UI.

When processing an htr_sendNanoContractTx request, the wallet assembles the transaction, presents it to the user, and asks for their authorization. If the user approves, the wallet signs and submits the transaction to the network.

In summary, the final step to complete the integration is to use the established session to send JSON-RPC requests. The following code provides an example of sending session requests:

await signClient.request({
topic: session.topic,
chainId: "hathor:testnet",
request: {
method: "htr_sendNanoContractTx",
id: '1',
jsonrpc: '2.0',
params: {
// Set of params specific for the RPC method being called.
}
}
});

The SignClient API exposes the request method, which your DApp should use to send JSON-RPC requests to the wallet. The request method requires the following parameters:

  • topic: identifier of the session between the DApp and the wallet. In pub/sub communication models as used in WalletConnect sign protocol, parties subscribe to topics on the relay server to send and receive messages.
  • chainId: identifier of the blockchain network (chain).
  • request: payload containing the actual JSON-RPC request.

The request payload follows the JSON-RPC protocol standard and contains the following properties:

  • jsonrpc: version of the JSON-RPC protocol being used. Always "2.0".
  • id: unique identifier for the request to match with the response.
  • method: name of the method being called.
  • params: set of parameters specific to the called RPC method.

method refers to one of the RPC methods in the JSON-RPC API exposed by the official Hathor wallet applications. In the snippet, we chose to present the htr_sendNanoContractTx method because it is the main method of the API. You will use htr_sendNanoContractTx whenever your DApp needs the user's wallet to assemble, sign, and submit a nano contract transaction to Hathor Network.

For the complete list of methods you can call, see: Hathor RPC lib API docs at Github

note

By definition, the main business logic of a DApp resides on chain in the form of smart contracts. Therefore, every operation performed by a user in a DApp will result in the creation or (more commonly) execution of a contract, which is triggered during the processing of a blockchain transaction that calls a method from this contract.

Now, we will provide a complete example of a request to the RPC method htr_sendNanoContractTx, including its parameters. Suppose your DApp implements a decentralized sports betting platform. The front end of your DApp presents a list of sports events on which users (bettors) can place bets on the outcome. For simplicity, imagine that each sports event is modeled as a nano contract that your company, which runs the betting platform, has registered on Hathor blockchain.

Suppose the user (bettor) has already connected their wallet to the DApp and is placing a bet. When the bettor submits their bet, the DApp will send a request to the wallet by calling the RPC method htr_sendNanoContractTx, where the parameters define the nano contract transaction that the user's wallet should create and submit to the network.

tip

It's important not to confuse method in request with method in params. In request, method refers to the RPC method being called. Each of these methods has its own parameters. In the case of htr_sendNanoContractTx, the main parameter is also called method. Here, it refers to the specific method in the nano contract that will be executed.

The following code provides a complete example of sending session requests, including the RPC method parameters:

await signClient.request({
topic: session.topic,
chainId: "hathor:testnet",
request: {
method: "htr_sendNanoContractTx",
id: '1',
jsonrpc: '2.0',
params: {
method: "bet",
// Id of NC: 32 bytes encoded in hex
nc_id: "000000368cc0b54e45fd193e50ef47bdb1699a15b22732be72dff867c4c78520",
actions: [
{
type: "deposit",
// Id of token: 32 bytes encoded in hex
token: "00000000179feb9d31e1ded6592099d1820100bf0a1fc20267a5ab2d8b914353",
amount: 10000
}
],
args: [
// Bettor address: 25 bytes encoded in base58check
"H9L7do74fkLF7VHa662a4huGni1zKW4EGZ",
// Result that bettor is betting on
"real-madrid2x0barcelona"
],
push_tx: true,
}
}
});

The parameters of htr_sendNanoContractTx vary depending on whether the transaction to be assembled by the wallet is for the creation or execution of a nano contract. In this example, the user wants to place a bet, which involves executing an already existing contract. In this case, we have the following parameters:

  • nc_id: identifies the contract registered on the blockchain that should be executed.
  • method: method of the contract that should be called for execution.
  • actions: set of deposits and withdrawals that should result from this successful execution of the contract. In this case, there is only one action, which is a single deposit that represents the value of the bet.
  • args: set of arguments to be passed to the contract method for execution. In this case, the first argument to the bet method is the address that identifies the specific bettor, and the second is the outcome of the event being bet on.
  • push_tx: defines whether, once signed, the wallet application itself should submit the transaction to the network or return it to the DApp (so that the DApp can handle the submission). In this case, it is set to true, meaning the wallet itself will submit the transaction to Hathor Network.

React contexts

Using the React framework, a good way to make the WalletConnect sign client available throughout your DApp is by implementing a context. The following pane presents an example of a provider for the WalletConnect sign client:

WalletConnectProvider.js
import React, { createContext, useContext, useEffect, useState } from "react";
import SignClient from "@walletconnect/sign-client";

const WalletConnectContext = createContext();

export const useWalletConnect = () => useContext(WalletConnectContext);

export const WalletConnectProvider = ({ children }) => {
const [client, setClient] = useState(null);

useEffect(() => {
const initClient = async () => {
try {
const _client = await SignClient.init({
projectId: "<your_project_ID>",
relayUrl: "<your_chosen_relay_server>",
metadata: {
name: "Your DApp",
description: "Your DApp description",
url: "https://your-dapp.com",
icons: ["https://your-dapp.com/icon.png"],
},
});
setClient(_client);
} catch (error) {
console.error("Error initializing WalletConnect client:", error);
}
};

initClient();
}, []);

return (
<WalletConnectContext.Provider value={{ client }}>
{children}
</WalletConnectContext.Provider>
);
};

As an example, we will present the main modules of the bet demo DApp where the wallet integration was implemented. The following pane shows the creation of the WalletConnect Sign client in the bet demo DApp:

bet-dapp/src/contexts/WalletConnectClient.ts
import Client from '@walletconnect/sign-client';
import {
DEFAULT_APP_METADATA,
DEFAULT_LOGGER,
DEFAULT_PROJECT_ID,
DEFAULT_RELAY_URL,
} from '@/constants';

let client: Client | null = null;

export async function initializeClient(): Promise<Client> {
if (client) {
return client;
}

try {
client = await Client.init({
logger: DEFAULT_LOGGER,
relayUrl: DEFAULT_RELAY_URL,
projectId: DEFAULT_PROJECT_ID,
metadata: DEFAULT_APP_METADATA,
});
return client;
} catch (error) {
console.error('Failed to initialize WalletConnect client:', error);
throw error;
}
}

export function getClient(): Client {
if (!client) {
throw new Error('WalletConnect client is not initialized');
}
return client;
}

The previous module creates the client and a React context to maintain its state throughout the application. The following module shows how the context provider was implemented:

bet-dapp/src/contexts/WalletConnectClientContext.ts
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import Client from '@walletconnect/sign-client';
import { PairingTypes, SessionTypes } from '@walletconnect/types';
import { Web3Modal } from '@web3modal/standalone';
import { getSdkError } from '@walletconnect/utils';
import { get } from 'lodash';
import { DEFAULT_PROJECT_ID } from '@/constants';
import { getClient } from './WalletConnectClient';

interface IContext {
client: Client | undefined;
session: SessionTypes.Struct | undefined;
connect: (pairing?: { topic: string }) => Promise<void>;
disconnect: () => Promise<void>;
chains: string[];
pairings: PairingTypes.Struct[];
accounts: string[];
setChains: React.Dispatch<React.SetStateAction<string[]>>;
getFirstAddress: () => string;
}

const WalletConnectClientContext = createContext<IContext>({} as IContext);

const web3Modal = new Web3Modal({
projectId: DEFAULT_PROJECT_ID,
themeMode: 'dark',
walletConnectVersion: 2,
});

export function WalletConnectClientContextProvider({
children,
}: {
children: ReactNode | ReactNode[];
}) {
const [client, setClient] = useState<Client>();
const [pairings, setPairings] = useState<PairingTypes.Struct[]>([]);
const [session, setSession] = useState<SessionTypes.Struct>();
const [accounts, setAccounts] = useState<string[]>([]);
const [chains, setChains] = useState<string[]>([]);

const reset = () => {
setSession(undefined);
setAccounts([]);
setChains([]);
};

const onSessionConnected = useCallback(
async (_session: SessionTypes.Struct) => {
const allNamespaceAccounts = Object.values(_session.namespaces)
.map((namespace) => namespace.accounts)
.flat();
const allNamespaceChains = Object.keys(_session.namespaces);

setSession(_session);
setChains(allNamespaceChains);
setAccounts(allNamespaceAccounts);
},
[]
);

const getFirstAddress = useCallback(() => {
const [_, _network, addr] = get(session, 'namespaces.hathor.accounts[0]', '::').split(':');
return addr;
}, [session]);

const _subscribeToEvents = useCallback(
async (_client: Client) => {
if (!_client) {
throw new Error('WalletConnect is not initialized');
}

_client.on('session_ping', (args) => {
console.log('EVENT', 'session_ping', args);
});

_client.on('session_event', (args) => {
console.log('EVENT', 'session_event', args);
});

_client.on('session_update', ({ topic, params }) => {
console.log('EVENT', 'session_update', { topic, params });
const { namespaces } = params;
const _session = _client.session.get(topic);
const updatedSession = { ..._session, namespaces };
onSessionConnected(updatedSession);
});

_client.on('session_delete', () => {
console.log('EVENT', 'session_delete');
reset();
});
},
[onSessionConnected]
);

const _checkPersistedState = useCallback(
async (_client: Client) => {
if (!_client) {
throw new Error('WalletConnect is not initialized');
}
setPairings(_client.pairing.getAll({ active: true }));

if (session) return;
if (_client.session.length) {
const lastKeyIndex = _client.session.keys.length - 1;
const _session = _client.session.get(_client.session.keys[lastKeyIndex]);
await onSessionConnected(_session);
return _session;
}
},
[session, onSessionConnected]
);

useEffect(() => {
(async () => {
const client = getClient();

await _subscribeToEvents(client);
await _checkPersistedState(client);
setClient(client);
})();
}, [_subscribeToEvents, _checkPersistedState]);

const connect = useCallback(
async (pairing: { topic: string } | undefined) => {
if (!client) {
throw new Error('WalletConnect is not initialized');
}

if (session) {
return;
}

try {
const requiredNamespaces = {
'hathor': {
methods: ['htr_signWithAddress', 'htr_sendNanoContractTx'],
chains: ['hathor:testnet'],
events: [],
}
};

const { uri, approval } = await client.connect({
pairingTopic: pairing?.topic,
requiredNamespaces,
});

if (uri) {
const standaloneChains = Object.values(requiredNamespaces)
.map((namespace) => namespace.chains)
.flat() as string[];

web3Modal.openModal({ uri, standaloneChains });
}

const session = await approval();
await onSessionConnected(session);
setPairings(client.pairing.getAll({ active: true }));
} catch (e) {
console.error(e);
} finally {
web3Modal.closeModal();
}
},
[client, session, onSessionConnected]
);

const disconnect = useCallback(async () => {
if (!client) {
throw new Error('WalletConnect is not initialized');
}
if (!session) {
throw new Error('Session is not connected');
}

try {
await client.disconnect({
topic: session.topic,
reason: getSdkError('USER_DISCONNECTED'),
});
} catch (error) {
console.error('SignClient.disconnect failed:', error);
} finally {
reset();
}
}, [client, session]);

const value = useMemo(
() => ({
pairings,
accounts,
chains,
client,
session,
connect,
disconnect,
getFirstAddress,
setChains,
}),
[
pairings,
accounts,
chains,
client,
session,
connect,
disconnect,
getFirstAddress,
setChains,
]
);

return (
<WalletConnectClientContext.Provider value={value}>
{children}
</WalletConnectClientContext.Provider>
);
}

export function useWalletConnectClient() {
const context = useContext(WalletConnectClientContext);
if (context === undefined) {
throw new Error(
'useWalletConnectClient must be used within a WalletConnectClientContextProvider'
);
}
return context;
}

Finally, note that the JSON-RPC API is also made available in the DApp through a context:

bet-dapp/src/contexts/WalletConnectClient.ts
import { createContext, ReactNode, useCallback, useContext, useState } from 'react';
import { useWalletConnectClient } from './WalletConnectClientContext';
import {
SendNanoContractRpcRequest,
SendNanoContractTxResponse,
SignOracleDataResponse,
SignOracleDataRpcRequest,
} from 'hathor-rpc-handler-test';
import { HATHOR_TESTNET_CHAIN } from '../constants';

/**
* Types
*/
export interface IFormattedRpcResponse<T> {
method?: string;
address?: string;
valid: boolean;
result: T;
}

export interface IHathorRpc {
sendNanoContractTx: (ncTxRpcReq: SendNanoContractRpcRequest) => Promise<SendNanoContractTxResponse>;
signOracleData: (signOracleDataReq: SignOracleDataRpcRequest) => Promise<SignOracleDataResponse>;
}

export interface IContext {
ping: () => Promise<void>;
hathorRpc: IHathorRpc;
rpcResult?: IFormattedRpcResponse<SignOracleDataResponse | SendNanoContractTxResponse | string | null | undefined> | null;
isRpcRequestPending: boolean;
reset: () => void;
}

/**
* Context
*/
export const JsonRpcContext = createContext<IContext>({} as IContext);

/**
* Provider
*/
export function JsonRpcContextProvider({
children,
}: {
children: ReactNode | ReactNode[];
}) {
const [pending, setPending] = useState(false);
const [result, setResult] = useState<IFormattedRpcResponse<SignOracleDataResponse | SendNanoContractTxResponse | string | null | undefined> | null>();

const { client, session } = useWalletConnectClient();

const reset = useCallback(() => {
setPending(false);
setResult(null);
}, []);

const ping = async () => {
if (typeof client === 'undefined') {
throw new Error('WalletConnect is not initialized');
}
if (typeof session === 'undefined') {
throw new Error('Session is not connected');
}

try {
setPending(true);

let valid = false;

try {
await client.ping({ topic: session.topic });
valid = true;
} catch (e) {
valid = false;
}

// display result
setResult({
method: 'ping',
valid,
result: valid ? 'Ping succeeded' : 'Ping failed',
});
} catch (e) {
console.error(e);
setResult(null);
} finally {
setPending(false);
}
};

const walletClientGuard = () => {
if (client == null) {
throw new Error('WalletConnect is not initialized');
}
if (session == null) {
throw new Error('Session is not connected');
}
};

const hathorRpc = {
sendNanoContractTx: async (ncTxRpcReq: SendNanoContractRpcRequest): Promise<SendNanoContractTxResponse> => {
walletClientGuard();

try {
setPending(true);

const result: SendNanoContractTxResponse = await client!.request<SendNanoContractTxResponse>({
chainId: HATHOR_TESTNET_CHAIN,
topic: session!.topic,
request: ncTxRpcReq,
});

return result;
} catch (error: any) {
setResult({
valid: false,
result: error?.message ?? error,
});

// Still propagate the error
throw error;
} finally {
setPending(false);
}
},
signOracleData: async (signOracleDataReq: SignOracleDataRpcRequest): Promise<SignOracleDataResponse> => {
walletClientGuard();

try {
setPending(true);

const result: SignOracleDataResponse = await client!.request<SignOracleDataResponse>({
chainId: HATHOR_TESTNET_CHAIN,
topic: session!.topic,
request: signOracleDataReq,
});

return result;
} catch (error: any) {
setResult({
valid: false,
result: error?.message ?? error,
});

// Still propagate the error
throw error;
} finally {
setPending(false);
}
}
};

return (
<JsonRpcContext.Provider
value={{
ping,
hathorRpc,
rpcResult: result,
isRpcRequestPending: pending,
reset,
}}
>
{children}
</JsonRpcContext.Provider>
);
}

export function useJsonRpc() {
const context = useContext(JsonRpcContext);
if (context === undefined) {
throw new Error('useJsonRpc must be used within a JsonRpcContextProvider');
}
return context;
}

What's next?

Hathor RPC lib API docs at Github: for the comprehensive documentation of the JSON-RPC API.


Footnotes

  1. Unlike the WalletConnect sign client, using the WalletConnect modal is not required. It's simply a useful component that saves you from having to implement the modal yourself and already lists all the wallet applications your DApp integrates with for the user.

  2. Reown has plans to provide a decentralized alternative for the relay server called WalletConnect Network. To know more about it see WalletConnect Network official website.