✨Works out of the box guarantee. If you face any issue at all, hit us up on Telegram and we will write the integration for you.

Cosmos
Neutron

Publish on chain using CosmWasm

Pre-requisite

At this stage, we assume that you followed the steps at ReactJs.

We will be using Keplr (opens in a new tab) as a wallet to interact with the frontend interface. Make sure that you have it installed and funded via Neutron-Testnet Faucet Channel (opens in a new tab).

Also, we use Wamskit (opens in a new tab) for compiling and deploying the cosmwasm contracts.

It is automatically installed when installing npm modules.

You can access the code of this walkthrough on Gitlab:

Contract deployment

Clone the client contract repo.

This Neutron contract (opens in a new tab) serves as a client to our Reclaim contract. It takes Reclaim's contract address, handles proofs, and verifies them.

git clone https://gitlab.reclaimprotocol.org/starterpacks/reclaim-cosmwasm-wasmkit-client.git
cd reclaim-neutron-client
wasmkit compile

Add your wallet information.

As stated in the README, add your wallet credentials to your wasmkit.config.js as specified in wasmkit.config.js.example.

const neutron_testnet_accounts = [
  {
    name: 'account_0',
    address: '', // your neutron address
    mnemonic: ''// your mnemonic key
  },
];

Deploy and verify a proof.

Take a look at wasmkit.config.js and all.ts, make sure that you have the correct RPCs and contract addresses.

If you want to deploy to mainnet, search for the Uncomment for mainnet comment.

By running wasmkit run scripts/all.ts, you can upload the contract, instantiate it, and verify a Reclaim proof on it. Run the script and take a note of your contract address, we will be using them later.

npm i
wasmkit run scripts/all.ts

Here is an example of how your output should look like:

storeCode instantiate&verify

React client development

Bootstrap project and install packages.

Add the following to project dependency(packages.json) and install modules.

"dependencies": {
    "@cosmjs/stargate": "^0.32.0",
    "@cosmjs/proto-signing": "^0.32.0",
    "@cosmjs/crypto": "^0.32.0",
    "@cosmjs/encoding": "^0.32.0",
    "@cosmjs/math": "^0.32.0",
    "@cosmjs/cosmwasm-stargate": "^0.32.0",
    "@reclaimprotocol/js-sdk": "^0.1.5",
    "react-qr-code": "^2.0.12",
    ...
}
npm i

Setup your React codebase.

We will continue building on the reclaim react client (opens in a new tab), new lines are highlighted below.

import "./App.css";
import { Reclaim } from "@reclaimprotocol/js-sdk";
import { useState } from "react";
import QRCode from "react-qr-code";
 
function App() {
  const [url, setUrl] = useState("");
  const [ready, setReady] = useState(false);
  const [proof, setProof] = useState({});
 
  const reclaimClient = new Reclaim.ProofRequest("YOUR_APPLICATION_ID_HERE"); //TODO: replace with your applicationId
 
  async function generateVerificationRequest() {
    const providerId = "PROVIDER_ID"; //TODO: replace with your provider ids you had selected while creating the application
 
    reclaimClient.addContext(
      `user's address`,
      "for acmecorp.com on 1st january"
    );
 
    await reclaimClient.buildProofRequest(providerId);
 
    reclaimClient.setSignature(
      await reclaimClient.generateSignature(
        APP_SECRET //TODO : replace with your APP_SECRET
      )
    );
 
    const { requestUrl, statusUrl } =
      await reclaimClient.createVerificationRequest();
 
    setUrl(requestUrl);
 
    await reclaimClient.startSession({
      onSuccessCallback: (proofs) => {
        console.log("Verification success", proofs);
        setReady(true);
        setProof(proofs[0]);
        // Your business logic here
      },
      onFailureCallback: (error) => {
        console.error("Verification failed", error);
        // Your business logic here to handle the error
      },
    });
  }
 
  return (
    <div className="App">
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          height: "50vh",
        }}
      >
        {!url && (
          <button onClick={generateVerificationRequest}>
            Create Claim QrCode
          </button>
        )}
        {url && <QRCode value={url} />}
      </div>
    </div>
  );
}
 
export default App;

Create a new folder (utilities).

Structure the folder as per the following, these are configs to call Reclaim on Neutron.

src/
 |- utilities/NeutronContext.js
 |- utilities/NeutronFunctions.js
 |- App.js

Copy this to NeutronContext.js.

By default, the values below are for NeutronContext's testnet, uncomment for mainnet.

import { createContext, useState } from "react";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
 
const NeutronContext = createContext(null);
const NEUTRON_CHAIN_ID = "pion-1";
const NEUTRON_LCD = "https://rpc-palvus.pion-1.ntrn.tech/";
 
// Uncomment for Mainnet
// const NEUTRON_CHAIN_ID = "neutron-1";
// const NEUTRON_LCD = "https://rpc-kralum.neutron-1.neutron.org";
 
const NeutronContextProvider = ({ children }) => {
  const [neutronClient, setNeutronClient] = useState(null);
  const [neutronAddress, setNeutronAddress] = useState("");
 
  async function setupKeplr(setNeutronClient, setNeutronAddress) {
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
 
    while (
      !window.keplr ||
      !window.getEnigmaUtils ||
      !window.getOfflineSignerOnlyAmino
    ) {
      await sleep(50);
    }
 
    await window.keplr.enable(NEUTRON_CHAIN_ID);
    window.keplr.defaultOptions = {
      sign: {
        preferNoSetFee: false,
        disableBalanceCheck: true,
      },
    };
 
    const keplrOfflineSigner =
      window.getOfflineSignerOnlyAmino(NEUTRON_CHAIN_ID);
    const accounts = await keplrOfflineSigner.getAccounts();
 
    console.log(accounts[0].address);
    const neutronAddress = accounts[0].address;
 
    const neutronClient = await SigningCosmWasmClient.connectWithSigner(NEUTRON_LCD, keplrOfflineSigner);
 
    setNeutronAddress(neutronAddress);
    setNeutronClient(neutronClient);
  }
 
  async function connectWallet() {
    try {
      if (!window.keplr) {
        console.log("intall keplr!");
      } else {
        await setupKeplr(setNeutronClient, setNeutronAddress);
        localStorage.setItem("keplrAutoConnect", "true");
        console.log(neutronAddress);
      }
    } catch (error) {
      alert(
        "An error occurred while connecting to the wallet. Please try again."
      );
    }
  }
 
  function disconnectWallet() {
    // reset neutronClient and neutronAddress
    setNeutronAddress("");
    setNeutronClient(null);
 
    // disable auto connect
    localStorage.setItem("keplrAutoConnect", "false");
 
    // console.log for success
    console.log("Wallet disconnected!");
  }
 
  async function addNeutronToKeplr () {
        try {
          if (!window.keplr) {
            alert("Intall keplr!");
          } else {
            //Uncomment for Mainnet
            /*
            const chainConfig = {
              "rpc": "https://rpc-neutron.keplr.app",
              "rest": "https://lcd-neutron.keplr.app",
              "chainId": "neutron-1",
              "chainName": "Neutron",
              "chainSymbolImageUrl": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/neutron/chain.png",
              "bip44": {
                "coinType": 118
              },
              "bech32Config": {
                "bech32PrefixAccAddr": "neutron",
                "bech32PrefixAccPub": "neutronpub",
                "bech32PrefixValAddr": "neutronvaloper",
                "bech32PrefixValPub": "neutronvaloperpub",
                "bech32PrefixConsAddr": "neutronvalcons",
                "bech32PrefixConsPub": "neutronvalconspub"
              },
              "currencies": [
                {
                  "coinDenom": "NTRN",
                  "coinMinimalDenom": "untrn",
                  "coinDecimals": 6,
                  "coinGeckoId": "neutron-3",
                  "coinImageUrl": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/neutron/untrn.png"
                }
              ],
              "feeCurrencies": [
                {
                  "coinDenom": "NTRN",
                  "coinMinimalDenom": "untrn",
                  "coinDecimals": 6,
                  "coinGeckoId": "neutron-3",
                  "gasPriceStep": {
                    "low": 0.0053,
                    "average": 0.0053,
                    "high": 0.0053
                  }
                }
              ],
              "features": ["cosmwasm"]
            }
            */
 
            const chainConfig = {
              "rpc": "https://rpc-palvus.pion-1.ntrn.tech",
              "rest": "https://rest-palvus.pion-1.ntrn.tech",
              "chainId": "pion-1",
              "chainName": "Neutron Testnet",
              "chainSymbolImageUrl": "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/neutron/chain.png",
              "bip44": {
                "coinType": 118
              },
              "bech32Config": {
                "bech32PrefixAccAddr": "neutron",
                "bech32PrefixAccPub": "neutronpub",
                "bech32PrefixValAddr": "neutronvaloper",
                "bech32PrefixValPub": "neutronvaloperpub",
                "bech32PrefixConsAddr": "neutronvalcons",
                "bech32PrefixConsPub": "neutronvalconspub"
              },
              "currencies": [
                {
                  "coinDenom": "NTRN",
                  "coinMinimalDenom": "untrn",
                  "coinDecimals": 6
                }
              ],
              "feeCurrencies": [
                {
                  "coinDenom": "NTRN",
                  "coinMinimalDenom": "untrn",
                  "coinDecimals": 6,
                  "gasPriceStep": {
                    "low": 0.02,
                    "average": 0.02,
                    "high": 0.02
                  }
                }
              ],
              "features": []
            }
          await window.keplr.experimentalSuggestChain(chainConfig);
      }
    } catch (error) {
      alert(
        "An error occurred while connecting to the wallet. Please try again."
      );
    }
  }
 
 
 
  return (
    <NeutronContext.Provider
      value={{
        neutronClient,
        setNeutronClient,
        neutronAddress,
        setNeutronAddress,
        connectWallet,
        disconnectWallet,
        addNeutronToKeplr
      }}
    >
      {children}
    </NeutronContext.Provider>
  );
};
 
export { NeutronContext, NeutronContextProvider };

Copy this to NeutronFunctions.js.

Replace with your contract address. You can leave values as are if you did not do the deployment step, make sure to uncomment values for mainnet.

import { useContext } from "react";
import { NeutronContext } from "./NeutronContext";
import { calculateFee, GasPrice } from "@cosmjs/stargate";
 
let contractAddress = "neutron1cqh6yx9jt2c6khkedc6cu2v6h8vv7apmtye56lg76flhture79jsxmrke5";
 
// Uncomment for mainnet
// let contractAddress = "neutron1c4rl2zws642m2nfkm7jl9ej0mc8emkhss5397ph7dh3sqggrldlsapylwe";
 
const NeutronFunctions = () => {
  const { neutronClient, neutronAddress } = useContext(NeutronContext);
 
  let verify_proof = async (proof) => {
    const defaultGasPrice = GasPrice.fromString("0.025untrn");
    const defaultExecuteFee = calculateFee(200_000, defaultGasPrice);
 
    console.log(proof);
    const result = await neutronClient.execute(
      neutronAddress, 
      contractAddress,
      {
        verify_proof: {
          proof: proof
        }
      },
      defaultExecuteFee
    );
    console.log(result);
  }
 
  return {
    verify_proof
  };
};
 
export { NeutronFunctions };

Wrap the App with the specified Neutron context (index.js).

import { NeutronContextProvider } from "./utilities/NeutronContext";
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <NeutronContextProvider>
    <App />
  </NeutronContextProvider>
);

Create a VerifyProof component.

src/
 |- utilities/NeutronContext.js
 |- utilities/NeutronFunctions.js
 |- VerifyProof.jsx
 |- App.js

Include the VerifyProof() function.

import { useState, useEffect } from "react";
import { Reclaim } from "@reclaimprotocol/js-sdk";
import { NeutronFunctions } from "./utilities/NeutronFunctions";
 
export default function VerifyProof(props) {
  const [proof, setProof] = useState({});
  const [verified, setVerified] = useState(false);
  const { verify_proof } = NeutronFunctions();
 
  useEffect(() => {
    const newProof = Reclaim.transformForOnchain(props.proof);
    setProof(newProof);
  }, []);
 
  return (
    <div>
      <button
        className="button"
        onClick={async () => {
          try {
            console.log(proof);
            await verify_proof(proof);
            setVerified(true);
          } catch (e) {
            console.error(e);
          }
        }}
      >
        Verify Proof
      </button>
      {verified && <p> Proof verified </p>}
      <style jsx="true">{`
        .container {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
        }
        .button {
          border: solid 1px #ccc;
          margin: 0 0 20px;
          border-radius: 3px;
        }
      `}</style>
    </div>
  );
}

Create a ConnectButton component.

|- utilities/NeutronContext.js
|- utilities/NeutronFunctions.js
|- VerifyProof.jsx
|- ConnectButton.jsx
|- App.js

Add a button and placeholder for connecting.

import { useContext } from "react";
import { NeutronContext } from "./utilities/NeutronContext";
 
export default function ConnectButton () {
    const { neutronAddress, connectWallet, addNeutronToKeplr } = useContext(NeutronContext);
  
    return (
        <div>Neutron
        <div>
          <button className="button" onClick={addNeutronToKeplr}>
            Add Neutron to Keplr
          </button>
          <hr/>
          <button className="button" onClick={connectWallet}>
            Connect Keplr
          </button>
        </div>
        <h2>
          {neutronAddress
            ? neutronAddress.slice(0, 10) + "...." + neutronAddress.slice(41, 45)
            : "Please connect wallet"}
        </h2>
      </div>
    )
}

Bringing it all together (App.js).

We will submit the proof on chain once we get the success callback. New lines are highlighted.

import "./App.css";
import { Reclaim } from "@reclaimprotocol/js-sdk";
 import { useState } from "react";
import QRCode from "react-qr-code";
import VerifyProof from "./VerifyProof";
import ConnectButton from "./ConnectButton";
 
function App() {
  const [url, setUrl] = useState("");
  const [ready, setReady] = useState(false);
  const [proof, setProof] = useState({});
 
 
  const reclaimClient = new Reclaim.ProofRequest("YOUR_APPLICATION_ID_HERE"); //TODO: replace with your applicationId
 
  async function generateVerificationRequest() {
    const providerId = "PROVIDER_ID"; //TODO: replace with your provider ids you had selected while creating the application
 
    reclaimClient.addContext(
      `user's address`,
      "for acmecorp.com on 1st january"
    );
 
    await reclaimClient.buildProofRequest(providerId);
 
    reclaimClient.setSignature(
      await reclaimClient.generateSignature(
        APP_SECRET //TODO : replace with your APP_SECRET
      )
    );
 
    const { requestUrl, statusUrl } =
      await reclaimClient.createVerificationRequest();
 
    setUrl(requestUrl);
 
    await reclaimClient.startSession({
      onSuccessCallback: (proofs) => {
        console.log("Verification success", proofs);
        setReady(true);
        setProof(proofs[0]);
        // Your business logic here
      },
      onFailureCallback: (error) => {
        console.error("Verification failed", error);
        // Your business logic here to handle the error
      },
    });
  }
 
  return (
    <div className="App">
      <ConnectButton />
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          height: "50vh",
        }}
      >
        {!url && (
          <button onClick={generateVerificationRequest}>
            Create Claim QrCode
          </button>
        )}
        {url && <QRCode value={url} />}
      </div>
      {ready && <VerifyProof proof={proof}></VerifyProof>}
    </div>
  );
}
 
export default App;

Submitting the proof

npm run build
 
npm run start

After requesting a proof from Reclaim and performing the verification on your end, a verify proof button will appear on the screen. Make sure your Keplr is connected, click the button, a wallet pop-up will show prompting you to submit.

screen1

keplr1

Now your proof will get approved on-chain, here is the sample transaction (opens in a new tab) from the screenshot above.