Publish on-chain with Reclaim contract on Aptos
Pre-requisite
At this stage, we assume that you are familiar with the steps at ReactJs.
For this walkthrough, you'll need a compatible wallet to interact with the frontend interface. We recommend using Petra (opens in a new tab) Wallet.
Setting Up Your Wallet
-
Install Petra wallet: install wallet extension from the provided link (opens in a new tab).
-
Switch to Aptos testnet:
- Click on the settings icon in the bottom right corner.
- Navigate to the "Networks" section.
- Select "Testnet".
-
Fund Your Wallet: Ensure your wallet is funded with APT on the Testnet. You can obtain testnet APT from the Aptos Faucet (opens in a new tab).
Accessing the Code
The code for this walkthrough is available on GitLab:
Contract Deployment
If you do not need to customize or add additional logic to the on-chain contract, you may skip the following steps and use our pre-deployed contract on Testnet (opens in a new tab).
Clone the Aptos contract sdk repo.
To get started, clone the Aptos SDK repository and navigate to the project directory.
git clone https://gitlab.reclaimprotocol.org/integrations/onchain/aptos-sdk.git
cd aptos-sdk
Setting up your environment
To interact with Aptos and compile your contract, you need to install Aptos CLI. If you already have it, you can skip to step 3.
Refer to Aaptos documentation (opens in a new tab) for detailed instructions.
Next, Initialize Aptos:
aptos init
- You will be prompted to Choose your network from [devnet, testnet, mainnet, local, custom] (we are working on testnet).
- Enter your private key as a hex literal (0x...).
Run Tests
⚠️ Note
if any of the commands below resulted with the error Failed to reset to latest Git state 'mainnet'
try adding --skip-fetch-latest-git-deps
to the command.
aptos move test --dev
Compile the smart contract
Compile the smart contract using aptos CLI, Replace <your-wallet-addres>
with your address.
aptos move compile --named-addresses reclaim=<your-wallet-address>
Deploy the Contract
Make sure to save the the object Address.
aptos move deploy-object --address-name reclaim
Ceate Reclaim Manager
Now after deployment we will create the Reclaim Manager resourse, Replace <your-contract-addres>
with your actual contract address.
aptos move run --function-id <your-contract-address>::reclaim::create_reclaim_manager --args u32:1000
Add Epoch
To add an epoch, ensure you are using the account set as the owner of the contract (Same account used to create the Reclaim Manager). Replace <your-contract-address>
with your actual contract address.
aptos move run --function-id <your-contract-address>::reclaim::add_new_epoch --args u128:1 'address:["0x244897572368eadf65bfbc5aec98d8e5443a9072"]'
Frontend Development
Cloning the frontend repo.
git clone https://gitlab.reclaimprotocol.org/starterpacks/reclaim-aptos-example.git
cd reclaim-aptos-example
npm install
Code discovery (src/App.tsx).
We will submit the proof on chain once we get the onSuccessCallback
. Fill in your Reclaim credentials marked with //TODO
s.
import { useState } from "react";
import Header from "./components/Header";
import { Reclaim } from "@reclaimprotocol/js-sdk";
import QRCode from "react-qr-code";
import { Button } from "./components/Button";
import VerifyProof from "./verify-proof";
import { ClipLoader } from "react-spinners";
import { useWallet } from "@aptos-labs/wallet-adapter-react";
function App() {
const [url, setUrl] = useState("");
const [ready, setReady] = useState(false);
const [proof, setProof] = useState({});
const [loading, setLoading] = useState(false);
const { account, connected } = useWallet();
const APP_ID = "0x408edDD2dF298C2F5df1E2eDE2eBF1278A96Ee45"; //TODO: replace with your applicationId
const reclaimClient = new Reclaim.ProofRequest(APP_ID);
async function generateVerificationRequest() {
setLoading(true);
const providerId = "1bba104c-f7e3-4b58-8b42-f8c0346cdeab"; //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, true, "V2Linking");
reclaimClient.setSignature(
await reclaimClient.generateSignature(
"0x5bdaf747ad9333898a0d9cb613f499d0b00164d7b8230628cf7ffa30fd323372" //TODO : replace with your APP_SECRET
)
);
const { requestUrl } = await reclaimClient.createVerificationRequest();
setUrl(requestUrl);
setLoading(false);
await reclaimClient.startSession({
onSuccessCallback: (proofs) => {
console.log("Verification success", proofs);
setProof(proofs[0]);
setReady(true);
},
onFailureCallback: (error) => {
console.error("Verification failed", error);
setLoading(false);
},
});
}
return (
<main className="flex flex-col items-center justify-center min-h-screen gap-12">
<Header />
{!connected && (
<p className="text-red-500 font-semibold text-xl">
Please connect your wallet to create a claim QR code.
</p>
)}
<div className="flex flex-row gap-12">
{account && !url && !loading && (
<Button onClick={generateVerificationRequest}>
Create Claim QrCode
</Button>
)}
{loading && <ClipLoader color="#4A90E2" size={50} />} {url &&
!ready && <QRCode value={url} />}
{ready && <VerifyProof proof={proof} />}{" "}
</div>
</main>
);
}
export default App;
Code discovery (src/verify-proof.tsx).
If you deployed your own version of the contract, you will need to update the contract address and the owner address.
import { useState, useEffect } from "react";
import { Reclaim } from "@reclaimprotocol/js-sdk";
import { Button } from "./components/Button";
import transformSignatures from "./utils/transformSignatures";
import { ClipLoader } from "react-spinners";
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
import { Proof } from "./types/proofTypes";
import {
useWallet,
InputTransactionData,
} from "@aptos-labs/wallet-adapter-react";
export default function VerifyProof(props: any) {
const [proof, setProof] = useState<Proof | null>(null);
const [verified, setVerified] = useState(false);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [transactionHash, setTransactionHash] = useState<string | null>(null);
const { signAndSubmitTransaction } = useWallet();
const aptosConfig = new AptosConfig({ network: Network.TESTNET });
const aptos = new Aptos(aptosConfig);
const handleVerifyProof = async () => {
try {
if (!proof) {
setError("Proof data is not available.");
return;
}
setLoading(true);
const reclaimAddress =
// Replace with your contract address
"0xd33b912ac96983caa70ac9f44f7e274d1a29158c71e40c26b967e75e72a11d5f";
const ownerAddress =
// Replace with your owner address
"0x3878a32a3c5b833b1a8271f448e3d9923cf11895d1784ee98270f27414d3db8d";
// Transforming the Signature from a String to an array of decimal bytes
// to be compatible with the data type used in the contract vector<vector<u8>>
const transformedSignatures = transformSignatures(
proof.signedClaim.signatures
);
// Sending the proof as multiple parameters since Entry functions in aptos
// Can take primitive types, (ex: String, vector) arguments but cannot take Structs (ex :Proof)
const transaction: InputTransactionData = {
data: {
function: `${reclaimAddress}::reclaim::verify_proof`,
functionArguments: [
proof.claimInfo.context,
proof.claimInfo.parameters,
proof.claimInfo.provider,
proof.signedClaim.claim.epoch.toString(),
proof.signedClaim.claim.identifier,
proof.signedClaim.claim.owner,
proof.signedClaim.claim.timestampS.toString(),
transformedSignatures,
ownerAddress,
],
},
};
// Rest of the code remains the same ...
Explanation of Data Transformation
Why are we transforming the type of the signatures array?
In the contract, the signatures array is defined as vector<vector<u8>>
. When calling verify_proof
entry function, the only way to pass this vector as a parameter is in a comma-separated format, such as 0x1
, 0x2
or ["0x1", "0x2"]
. Therefore, we need to convert the hex representation of each signature into an array of unsigned 8-bit integers (u8) for compatibility. The transformSignatures
function handles this transformation by converting each hex signature string into its corresponding byte array.
export default function transformSignatures(hexSignatures: any) {
return hexSignatures.map((signature: any) => {
// Remove the "0x" prefix if present
const cleanSignature = signature.startsWith("0x")
? signature.slice(2)
: signature;
// Convert the hex signature to c
const byteArray = [];
for (let i = 0; i < cleanSignature.length; i += 2) {
byteArray.push(parseInt(cleanSignature.substr(i, 2), 16));
}
return byteArray;
});
}
Why are we not sending the proof
as it is (in JSON format)?
Aptos Move entry functions can only accept primitive types, String
, and vector
arguments, and they do not support complex data structures like structs (e.g., Proof). So the proof data must be transformed into an acceptable format (e.g., vectors or individual primitive elements) before being passed to the contract.
Submitting the proof.
npm run dev
- First, You will need to connect your wallet (Make sure you are on Testnet).
- Then, After requesting a proof from Reclaim and performing the verification on your end, a verify proof button will appear on the screen.
- Finally, Clicking on the
Verify Proof
button will send a transaction. - You can check the transaction on the explorer by clicking on
See Transaction on the Explorer
button.