Front-end Template with React
For building web applications, it is most common to use the Casper JS SDK with React. This is a popular solution among developers, but you may use any front-end library or framework, including none at all, to interact with a Casper network via the Casper JS SDK.
This guide will walk you through setting up and developing a React application with Vite that communicates with a Casper network. Experience with Vite is not required; however, if you have never built a React app, you should begin by reading the React documentation.
Get Started
Begin by opening a terminal and running:
node -v
To get your Node.js version.
To ensure compatibility, you should be running Node.js version 18 or above. Upgrade to version 18 using the Node Version Manager or another tool if you are running an earlier version.
Using npm, create a new Vite project by running:
npm install -g vite
npm create vite@latest
Name your project, select "React", then choose your preferred language. In this example, we will use JavaScript.
Head into your new project directory, replacing vite-project
with your project name:
cd vite-project/
Run the following command to test the server:
npm install
vite dev
Quit the server by pressing q
. Install the Casper JS SDK by running the following:
npm install casper-js-sdk
This guide will use axios to communicate with the backend; install it by running:
npm install axios
Casper Wallet Integration
The Casper Wallet extension content script injects the SDK into your website's global scope. Provider class and event types can be accessed with window.CasperWalletProvider
and window.CasperWalletEventTypes
. If the value of these variables is undefined
the Casper Wallet is not installed.
Start with a helper for getting the provider instance:
touch src/casper-wallet.js
Fill the file with the following content:
// Timeout (in ms) for requests to the extension [DEFAULT: 30 min]
const REQUESTS_TIMEOUT_MS = 30 * 60 * 1000;
export const getProvider = () => {
let providerConstructor = window.CasperWalletProvider;
if (providerConstructor === undefined) {
alert("Casper Wallet extension is not installed!");
return;
}
let provider = providerConstructor({
timeout: REQUESTS_TIMEOUT_MS,
});
return provider;
};
For complete integration details, refer to README of Casper Wallet SDK.
To ensure that a user's public key will be available to all necessary components, create a React state variable in src/App.jsx or another parent component that encapsulates the components that should have access to this public key:
import React from "react";
import Connect from "./Connect";
import "./App.css";
function App() {
const [publicKey, setPublicKey] = React.useState(null);
return (
<>
<Connect setPublicKey={setPublicKey} />
<div>
{publicKey !== null && (
<>
Wallet connected: {publicKey}
<br />
</>
)}
</div>
</>
);
}
export default App;
This is an example of src/App.jsx that imports and displays the Connect
component that is described next. The setPublicKey
function is passed to the Connect
component as a prop so that it may set the public key and make it available to all of src/App.jsx. This way, when more components are added to src/App.jsx, they may utilize the publicKey
variable.
To connect to the Casper Wallet within your React app, create the Connect
component and import the getProvider
helper.
touch src/Connect.jsx
Open the file and write:
import { getProvider } from "./casper-wallet";
const provider = getProvider();
const Connect = (props) => {
return (
<>
<button onClick={() => connectToWallet(props)}>Connect Wallet</button>
{/* Place for disconnect button */}
</>
);
};
export default Connect;
Notice that Connect
accepts props, and forwards them to the connectToWallet
function described below. This function is called when the button is clicked, allowing it to set the public key within src/App.jsx using props.setPublicKey()
.
Write the connectToWallet
function under the Connect
function component:
const connectToWallet = (props) => {
provider
.requestConnection()
.then((connected) => {
if (!connected) {
alert("Couldn't connect to wallet");
} else {
provider
.getActivePublicKey()
.then((publicKey) => {
props.setPublicKey(publicKey);
})
.catch((error) => {
alert(error.message);
});
}
})
.catch((error) => {
alert(error.message);
});
};
The connectToWallet()
function calls provider.isConnected()
to check if the Casper Wallet is already connected. If it is, it gets the public key of the selected account; if it's not, it opens up a connection request within the Wallet. provider.isConnected()
will throw an error if the Wallet is not installed as an extension or if it is locked.
Disconnect the Casper Wallet
To request that the Casper Wallet disconnect from a website, add the following function call to src/Connect.jsx:
const disconnect = (props) => {
provider
.disconnectFromSite()
.then((disconnected) => {
if (disconnected) {
props.setPublicKey(null);
alert("Disconnected");
}
})
.catch((error) => {
alert(error.message);
});
};
Then connect it to a button:
const Connect = (props) => {
return (
<>
<button onClick={() => connectToWallet(props)}>Connect Wallet</button>
<button onClick={() => disconnect(props)}>Disconnect</button>
</>
);
};
Call a Smart Contract
For this example, we'll call a hypothetical "hello world" contract containing a single entrypoint "update_message". We'll call the "update_message" entrypoint with text entered by the user in an HTML input
field.
When calling smart contracts from React, you'll need to implement the logic within a function accessible from a React component. You can obtain user-entered data from the DOM using elements like input
, then grab the value within the smart-contract-calling function.
Create a new component:
touch src/UpdateMessage.jsx
Open the file and write:
import { useState } from "react";
import { Contracts, CasperClient, RuntimeArgs, CLValueBuilder, CLPublicKey, DeployUtil } from "casper-js-sdk";
import axios from "axios";
import { getProvider } from "./casper-wallet";
const provider = getProvider();
const UpdateMessage = (props) => {
const [message, setMessage] = useState("");
return (
<>
<input
id="message"
type="text"
value={message}
onChange={(e) => {
setMessage(e.target.value);
}}
/>
<button onClick={() => updateMessage(props, message)}>Update Message</button>
</>
);
};
export default UpdateMessage;
On the front-end you'll need to build the deploy and forward it to the Casper Wallet to be signed. In most cases, you will be calling smart contract entrypoints. This example deploy shows the calling of entrypoint "update_message" which will update the chain's global state to reflect the new data. You'll need the user's active public key to prepare the deploy, and you may retrieve this from the publicKey
variable passed in as a prop from src/App.jsx
. Write this function under your UpdateMessage
component function.
const NODE_URL = "http://65.108.127.242:7777/rpc";
const NETWORK_NAME = "casper-test"; // "casper" for mainnet
const CONTRACT_HASH = "hash-75143aa708275b7dead20ac2cc06c1c3eccff4ffcf1eb9aebb8cce7c35cea041";
const updateMessage = (props, message) => {
const casperClient = new CasperClient(NODE_URL);
const contract = new Contracts.Contract(casperClient);
contract.setContractHash(CONTRACT_HASH);
const runtimeArguments = RuntimeArgs.fromMap({
message: CLValueBuilder.string(message),
});
const deploy = contract.callEntrypoint(
"update_message",
runtimeArguments,
CLPublicKey.fromHex(props.publicKey),
NETWORK_NAME,
"1000000000", // 1 CSPR (10^9 Motes)
);
const deployJSON = DeployUtil.deployToJson(deploy);
provider
.sign(JSON.stringify(deployJSON), props.publicKey)
.then((signedDeploy) => {
// Initiates sign request
axios
.post("/sendDeploy", signedDeploy, {
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
alert(response.data);
})
.catch((error) => {
console.error(error.message);
});
})
.catch((error) => {
console.error(error.message);
});
};
In this example, updateMessage
builds a deploy and forwards it to the Casper Wallet to be signed by the user. Once it's been signed, signedDeploy
is forwarded to the backend at the /sendDeploy
endpoint using axios.post
before being sent off to a Casper node. If an error occurs, or the user rejects the signature request, it will be logged to stderr
. In this particular example, the result of this deployment will be presented to the user in the form of a JavaScript alert; however, you may do with the response data as you wish.
The backend endpoint /sendDeploy
should handle signed deployment by simply passing it to a Casper node.
In Casper node v1.5.0
, which sets up appropriate CORS headers, it will also be possible to send deployments directly from the browser, without relying on a backend server. This is useful for prototyping, however it is advised that you operate your own node.
Now that this component is created, render it to the user interface in src/App.jsx, passing along the publicKey
as a prop:
import React from "react";
import Connect from "./Connect";
import UpdateMessage from "./UpdateMessage";
import "./App.css";
function App() {
const [publicKey, setPublicKey] = React.useState(null);
return (
<>
<Connect setPublicKey={setPublicKey} />
<div>
{publicKey !== null && (
<>
Wallet connected: {publicKey}
<br />
<UpdateMessage publicKey={publicKey} />
</>
)}
</div>
</>
);
}
Query a Smart Contract
Consider that the message written to the chain during the update_message
entrypoint invocation is stored in the dictionary messages
in the contract. Further consider that each account may write its own message and that the messages are stored under the account's account hash as the dictionary key. Querying this kind of data is essential in any dApp; here is how to communicate contract data to and from the front-end.
Create a new component:
touch src/Query.jsx
Open the file and write:
import axios from "axios";
import { CLPublicKey } from "casper-js-sdk";
const Query = (props) => {
return <button onClick={() => query(props)}>Query</button>;
};
const query = (props) => {
const accountHash = CLPublicKey.fromHex(props.publicKey).toAccountHashStr().substring(13);
axios
.get("/queryMessage?accountHash=" + accountHash)
.then((response) => {
alert(response.data);
})
.catch((error) => {
console.error(error.message);
});
};
export default Query;
All this component does is render an HTML button
element that, when pressed, performs a GET
request to the backend that includes the user's active account hash. The account hash is derived from the active public key, and is used to look up the message stored by the current user.
The toAccountHashStr
method produces a string that is prepended by the text "account-hash-". In this case, this text is not needed, so it is discarded by chaining on the substring(13)
method.
This functionality relies on the /queryMessage
endpoint, which should be implemented in your backend.
Now add this component to src/App.jsx, making available the publicKey
state variable via a prop:
import React from "react";
import Connect from "./Connect";
import UpdateMessage from "./UpdateMessage";
import Query from "./Query";
import "./App.css";
function App() {
const [publicKey, setPublicKey] = React.useState(null);
return (
<>
<Connect setPublicKey={setPublicKey} />
<div>
{publicKey !== null && (
<>
Wallet connected: {publicKey}
<br />
<UpdateMessage publicKey={publicKey} />
<Query publicKey={publicKey} />
</>
)}
</div>
</>
);
}
Test Application
Test your application by running the following:
vite dev
Your application will start locally, and a URL will be shown where you can visit your application. Alternatively, press h
, then o
to open the app in a browser.
Build for Production
When you're ready to release your application, you'll want to compile it to pure JavaScript and serve it from a web server. Do so by running:
vite build
Once this is complete, you can preview your production build by running:
vite preview