Skip to main content

Overview

The Incentiv SDK (@incentiv/dapp-sdk) provides a seamless way to connect your decentralized application (dApp) to the Incentiv blockchain network. It includes:
  • IncentivResolver: For wallet connection and account management
  • IncentivSigner: For signing transactions and interacting with smart contracts
The SDK supports Account Abstraction (AA) wallets, enabling gasless transactions and improved user experience.

Prerequisites

Before you begin, ensure you have:
  • Node.js (v16 or higher)
  • npm or yarn package manager
  • A modern browser with popups enabled for your application
  • Basic knowledge of:
    • React/JavaScript
    • Ethereum/Web3 concepts
    • Smart contracts

Installation

Step 1: Install the SDK

Install the Incentiv DApp SDK using npm:
npm install @incentiv/dapp-sdk ethers@^6.13.0
Or using yarn:
yarn add @incentiv/dapp-sdk ethers

Step 2: Install Additional Dependencies

The SDK requires buffer for Node.js compatibility in browser environments:
npm install buffer

Project Setup

Vite Configuration

If you’re using Vite, configure it to support the SDK: vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  define: {
    global: 'globalThis',
  },
  resolve: {
    alias: {
      buffer: 'buffer',
    },
  },
  optimizeDeps: {
    include: ['buffer'],
  },
});

Environment Variables

Create a configuration file for your environment settings: src/config.js:
const Config = {
  SC: {
    address: "0xC3A195E....." // Your contract address
  },
  Environments: {
    Mainnet: {
      Portal: "https://portal.incentiv.io", 
      RPC: "https://rpc.incentiv.io" 
    }
  },
  API: {
    baseURL: import.meta.env.VITE_API_URL || "https://your-api.com/api"
  }
};

export default Config;

Environment Variables Template

Create a .env file (or use environment variables):
VITE_API_URL=https://your-api.com/api
VITE_ENVIRONMENT=mainnet
VITE_CONTRACT_ADDRESS=0xC3A195E.....

Connecting to Wallet

Step 1: Import Required Modules

import { IncentivResolver, IncentivSigner } from "@incentiv/dapp-sdk";
import { ethers } from "ethers";
import Config from "./config";

Step 2: Connect to Wallet

The IncentivResolver is used to connect to the user’s wallet and get their account address:
const handleConnect = async () => {
  try {
    // Get the user's wallet address
    const address = await IncentivResolver.getAccountAddress(
      Config.Environments.Mainnet.Portal
    );

    // Validate the address
    if (!address || address === "null" || address === null) {
      throw new Error(
        "No wallet address received. The popup may have been closed or the connection was rejected."
      );
    }

    console.log("Connected address:", address);
    setAddress(address);

    // Initialize contract connection (see next section)
    await fetchContractData(address);
  } catch (err) {
    console.error("Connection error:", err);
    // Possible causes:
    // - Popup blocked by the browser
    // - User closed the popup
    // - User rejected the connection
  }
};

Complete Wallet Connection Example

import { useState } from "react";
import { IncentivResolver } from "@incentiv/dapp-sdk";
import Config from "./config";

function WalletConnection() {
  const [address, setAddress] = useState("");
  const [isConnecting, setIsConnecting] = useState(false);
  const [error, setError] = useState("");

  const handleConnect = async () => {
    setIsConnecting(true);
    setError("");

    try {
      // Connect to wallet
      const userAddress = await IncentivResolver.getAccountAddress(
        Config.Environments.Mainnet.Portal
      );

      // Validate address
      if (!userAddress || userAddress === "null" || userAddress === null) {
        throw new Error(
          "No wallet address received. The popup may have been closed or the connection was rejected."
        );
      }

      setAddress(userAddress);
      console.log("Wallet connected:", userAddress);
    } catch (err) {
      console.error("Connection error:", err);
      setError(
        err.message ||
          "Failed to connect. Please allow popups and try again."
      );
    } finally {
      setIsConnecting(false);
    }
  };

  return (
    <div>
      {!address ? (
        <button onClick={handleConnect} disabled={isConnecting}>
          {isConnecting ? "Connecting..." : "Connect Wallet"}
        </button>
      ) : (
        <div>
          <p>Connected: {address}</p>
        </div>
      )}
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

Initializing the Signer

After connecting to the wallet, you need to initialize the IncentivSigner to interact with smart contracts. The signer uses Account Abstraction (AA) for gasless transactions.

Step 1: Create Provider

import { parseEther, formatEther } from "ethers";

const provider = new ethers.JsonRpcProvider(
  Config.Environments.Mainnet.RPC
);

Step 2: Initialize IncentivSigner

import { IncentivSigner } from "@incentiv/dapp-sdk";

const signer = new IncentivSigner({
  address: userAddress, // From wallet connection
  provider: provider,
  environment: Config.Environments.Mainnet.Portal,
  entryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4", // EntryPoint contract address
});

Step 3: Prepare Contract Interface

import { Interface } from "ethers";
import ABI from "./YourContractABI.json";

const contractAddress = Config.SC.address;
const contractInterface = new Interface(ABI);

Complete Signer Initialization Example

import { useRef, useState } from "react";
import { IncentivResolver, IncentivSigner } from "@incentiv/dapp-sdk";
import { ethers, Interface } from "ethers";
import ABI from "./YourContractABI.json";
import Config from "./config";

function App() {
  const [address, setAddress] = useState("");
  const [isReady, setIsReady] = useState(false);
  const signerRef = useRef(null);
  const contractInterfaceRef = useRef(null);

  async function fetchContractData(userAddress) {
    try {
      const provider = new ethers.JsonRpcProvider(
        Config.Environments.Mainnet.RPC
      );

      const signer = new IncentivSigner({
        address: userAddress,
        provider,
        environment: Config.Environments.Mainnet.Portal,
        entryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
      });

      signerRef.current = signer;
      contractInterfaceRef.current = new Interface(ABI);

      setIsReady(true);
      console.log("Signer and contract interface initialized");
    } catch (err) {
      console.error("Failed to initialize signer:", err);
      signerRef.current = null;
      contractInterfaceRef.current = null;
      setIsReady(false);
    }
  }

  const handleConnect = async () => {
    try {
      const userAddress = await IncentivResolver.getAccountAddress(
        Config.Environments.Mainnet.Portal
      );

      setAddress(userAddress);
      await fetchContractData(userAddress);
    } catch (err) {
      console.error("Connection error:", err);
    }
  };

  return (
    <div>
      {!address ? (
        <button onClick={handleConnect}>Connect Wallet</button>
      ) : (
        <div>
          <p>Address: {address}</p>
          <p>Signer Status: {isReady ? "Ready" : "Not initialized"}</p>
        </div>
      )}
    </div>
  );
}

Making Payments & Signing Transactions

Depositing Funds (Native Token)

To deposit native tokens (CENT) to a smart contract:
async function depositFunds(amountInCENT) {
  if (!signer || !contractInterface) {
    throw new Error("Signer not initialized");
  }

  try {
    // Convert CENT to wei (1 CENT = 10^18 wei)
    const value = parseEther(amountInCENT.toString());

    // Call the deposit function with native value
    const data = contractInterface.encodeFunctionData("deposit", []);

    const tx = await signer.sendTransaction({
      to: Config.SC.address,
      data,
      value,
    });

    // Wait for transaction confirmation
    await tx.wait();

    console.log("Deposit successful:", tx.hash);
    return tx;
  } catch (err) {
    console.error("Deposit failed:", err);
    throw err;
  }
}

Withdrawing Funds

To withdraw funds from a smart contract:
async function withdrawFunds(amountInCENT) {
  if (!signer || !contractInterface) {
    throw new Error("Signer not initialized");
  }

  try {
    // Convert CENT to wei
    const withdrawalAmountWei = parseEther(amountInCENT.toString());

    // Call the withdraw function
    const data = contractInterface.encodeFunctionData("withdraw", [
    withdrawalAmountWei,
  ]);
  
  const tx = await signer.sendTransaction({
    to: Config.SC.address,
    data,
    value: 0n,
  });

    // Show pending transaction notification
    console.log("Transaction pending:", tx.hash);

    // Wait for confirmation
    await tx.wait();

    console.log("Withdrawal successful:", tx.hash);
    return tx;
  } catch (err) {
    console.error("Withdrawal failed:", err);
    throw err;
  }
}

Calling Contract Functions

For any contract function that requires signing:
async function callContractFunction(functionName, ...args) {
  if (!signer || !contractInterface) {
    throw new Error("Signer not initialized");
  }

  try {
    // Call the contract function
    const data = contractInterface.encodeFunctionData(functionName, args);

    const tx = await signer.sendTransaction({
      to: Config.SC.address,
      data,
      value: 0n,
    });

    // Wait for transaction
    await tx.wait();

    console.log("Transaction successful:", tx.hash);
    return tx;
  } catch (err) {
    console.error("Transaction failed:", err);
    throw err;
  }
}

Reading Contract State (View Functions)

For read-only operations, you don’t need to sign:
async function readContractData(functionName, ...args) {
  if (!signer || !contractInterface) {
    throw new Error("Signer not initialized");
  }

  try {
    // Call view function (no transaction needed)
    const result = await contract[functionName](...args);
    return result;
  } catch (err) {
    console.error("Read failed:", err);
    throw err;
  }
}

Complete Payment Example

import { useState } from "react";
import { IncentivResolver, IncentivSigner } from "@incentiv/dapp-sdk";
import { parseEther, formatEther } from "ethers";
import ABI from "./YourContractABI.json";
import Config from "./config";

function PaymentExample() {
  const [address, setAddress] = useState("");
  const [contract, setContract] = useState(null);
  const [balance, setBalance] = useState(0);
  const [isProcessing, setIsProcessing] = useState(false);

  // Initialize contract (same as previous example)
  async function initializeContract(userAddress) {
    const provider = new ethers.JsonRpcProvider(
      Config.Environments.Mainnet.RPC
    );

    const signer = new IncentivSigner({
      address: userAddress,
      provider: provider,
      environment: Config.Environments.Mainnet.Portal,
      entryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
    });

    const contractInstance = new ethers.Contract(
      Config.GamePot.address,
      ABI,
      signer
    );

    setContract(contractInstance);
  }

  // Deposit funds
  const handleDeposit = async (amount) => {
    if (!contract) return;

    setIsProcessing(true);
    try {
      const value = parseEtheramount.toString());
      const data = contractInterface.encodeFunctionData("deposit", []);
      const tx = await signer.sendTransaction({
        to: Config.SC.address,
        data,
        value,
      });
      await tx.wait();
      console.log("Deposit successful!");
      await updateBalance();
    } catch (err) {
      console.error("Deposit failed:", err);
    } finally {
      setIsProcessing(false);
    }
  };

  // Withdraw funds
  const handleWithdraw = async (amount) => {
    if (!contract) return;

    setIsProcessing(true);
    try {
      const amountWei = parseEtheramount.toString());
      const tx = await contract.withdraw(amountWei);
      await tx.wait();
      console.log("Withdrawal successful!");
      await updateBalance();
    } catch (err) {
      console.error("Withdrawal failed:", err);
    } finally {
      setIsProcessing(false);
    }
  };

  // Read balance
  const updateBalance = async () => {
    if (!contract || !address) return;

    try {
      const balanceWei = await contract.deposits(address);
      const balanceCENT = formatEther(balanceWei);
      setBalance(parseFloat(balanceCENT));
    } catch (err) {
      console.error("Failed to read balance:", err);
    }
  };

  return (
    <div>
      <button onClick={() => handleConnect()}>Connect Wallet</button>
      {contract && (
        <div>
          <p>Balance: {balance} CENT</p>
          <button onClick={() => handleDeposit(10)} disabled={isProcessing}>
            Deposit 10 CENT
          </button>
          <button onClick={() => handleWithdraw(5)} disabled={isProcessing}>
            Withdraw 5 CENT
          </button>
        </div>
      )}
    </div>
  );
}

Complete Code Examples

Full Integration Example

Here’s a complete React component demonstrating the full integration:
import { useEffect, useState, useRef } from "react";
import { IncentivResolver, IncentivSigner } from "@incentiv/dapp-sdk";
import { parseEther, formatEther } from "ethers";
import ABI from "./YourContractABI.json";
import Config from "./config";

function FullIntegrationExample() {
  const [address, setAddress] = useState("");
  const [contract, setContract] = useState(null);
  const [balance, setBalance] = useState(0);
  const [isConnecting, setIsConnecting] = useState(false);
  const [isProcessing, setIsProcessing] = useState(false);
  const signerRef = useRef(null);

  // Initialize contract and signer
  async function fetchContractData(userAddress) {
    try {
      const provider = new ethers.JsonRpcProvider(
        Config.Environments.Mainnet.RPC
      );

      const signer = new IncentivSigner({
        address: userAddress,
        provider: provider,
        environment: Config.Environments.Mainnet.Portal,
        entryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
      });

      signerRef.current = signer;

      const contractInstance = new ethers.Contract(
        Config.GamePot.address,
        ABI,
        signer
      );

      setContract(contractInstance);
      await updateBalance(userAddress, contractInstance);
    } catch (err) {
      console.error("Failed to initialize contract:", err);
      setContract(null);
      signerRef.current = null;
    }
  }

  // Connect wallet
  const handleConnect = async () => {
    setIsConnecting(true);
    try {
      const userAddress = await IncentivResolver.getAccountAddress(
        Config.Environments.Mainnet.Portal
      );

      if (!userAddress || userAddress === "null" || userAddress === null) {
        throw new Error(
          "No wallet address received. The popup may have been closed or the connection was rejected."
        );
      }

      setAddress(userAddress);
      await fetchContractData(userAddress);
    } catch (err) {
      console.error("Connection error:", err);
      alert(`Failed to connect: ${err.message}`);
    } finally {
      setIsConnecting(false);
    }
  };

  // Update balance
  async function updateBalance(userAddr, contractInstance) {
    if (!contractInstance || !userAddr) return;

    try {
      const balanceWei = await contractInstance.deposits(userAddr);
      const balanceCENT = formatEther(balanceWei);
      setBalance(parseFloat(balanceCENT));
    } catch (err) {
      console.error("Failed to read balance:", err);
    }
  }

  // Deposit
  const handleDeposit = async (amount) => {
    if (!contract) return;

    setIsProcessing(true);
    try {
      const value = parseEtheramount.toString());
      const data = contractInterface.encodeFunctionData("deposit", []);
      const tx = await signer.sendTransaction({
        to: Config.SC.address,
        data,
        value,
      });
      console.log("Transaction sent:", tx.hash);
      await tx.wait();
      console.log("Transaction confirmed");
      await updateBalance(address, contract);
    } catch (err) {
      console.error("Deposit failed:", err);
      alert(`Deposit failed: ${err.message}`);
    } finally {
      setIsProcessing(false);
    }
  };

  // Withdraw
  const handleWithdraw = async (amount) => {
    if (!contract) return;

    setIsProcessing(true);
    try {
      const amountWei = parseEtheramount.toString());
      const tx = await contract.withdraw(amountWei);
      console.log("Transaction sent:", tx.hash);
      await tx.wait();
      console.log("Transaction confirmed");
      await updateBalance(address, contract);
    } catch (err) {
      console.error("Withdrawal failed:", err);
      alert(`Withdrawal failed: ${err.message}`);
    } finally {
      setIsProcessing(false);
    }
  };

  return (
    <div style={{ padding: "20px" }}>
      <h1>Incentiv SDK Integration</h1>

      {!address ? (
        <button onClick={handleConnect} disabled={isConnecting}>
          {isConnecting ? "Connecting..." : "Connect Wallet"}
        </button>
      ) : (
        <div>
          <p><strong>Connected:</strong> {address}</p>
          <p><strong>Balance:</strong> {balance.toFixed(4)} CENT</p>
          <p><strong>Contract Status:</strong> {contract ? "Ready" : "Not initialized"}</p>

          <div style={{ marginTop: "20px" }}>
            <button
              onClick={() => handleDeposit(10)}
              disabled={isProcessing}
              style={{ marginRight: "10px" }}
            >
              Deposit 10 CENT
            </button>
            <button
              onClick={() => handleWithdraw(5)}
              disabled={isProcessing}
            >
              Withdraw 5 CENT
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

export default FullIntegrationExample;

Configuration

Network Configuration

Update your configuration based on the network: Mainnet:
const MainnetConfig = {
  Portal: "https://portal.incentiv.io/",
  RPC: "https://rpc.incentiv.io",
  EntryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
  VerifierContract: "0xd44EbfDf4FFf3e367b7e07e47eA0e70F5277Bca0"
};

Contract Addresses

Always use checksummed addresses:
const CONTRACT_ADDRESS = "0xC3A195E.....";

Best Practices

1. Error Handling

Always wrap SDK calls in try-catch blocks:
try {
  const address = await IncentivResolver.getAccountAddress(portal);
} catch (err) {
  // Handle specific error types
  if (err.message.includes("extension not found")) {
    // Show install wallet message
  } else if (err.message.includes("user rejected")) {
    // User cancelled the connection
  } else {
    // Generic error handling
  }
}

2. Loading States

Provide user feedback during async operations:
const [isConnecting, setIsConnecting] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);

// Use these states to disable buttons and show loading indicators

3. Transaction Confirmation

Always wait for transaction confirmation:
const data = contractInterface.encodeFunctionData("deposit", []);
const tx = await signer.sendTransaction({
  to: Config.SC.address,
  data,
  value,
});
await tx.wait(); // Wait for confirmation

4. Balance Updates

Update balances after transactions:
await tx.wait();
await updateBalance(); // Refresh balance

5. Address Validation

Validate addresses before using them:
if (!address || address === "null" || address === null) {
  throw new Error("Invalid address");
}

6. Contract Initialization Check

Always check if contract is initialized before use:
  if (!signer || !contractInterface) {
    throw new Error("Signer not initialized");
  }

7. Use Refs for Signers

Store signer in a ref to avoid re-initialization:
const signerRef = useRef(null);

// Initialize once
signerRef.current = new IncentivSigner({...});

// Use later
const contractInterfaceRef = useRef(null);

signerRef.current = new IncentivSigner({...});
contractInterfaceRef.current = new Interface(ABI);

Troubleshooting

Issue: Portal popup is blocked

Solution:
  • Allow popups for your application.
  • Trigger getAccountAddress() from a user action (such as clicking a button).
  • Try the connection again.

Issue: “No wallet address received”

Solution:
  • Verify the Portal URL is correct.
  • Ensure the popup was not blocked.
  • Ensure the user completed the connection flow in the Portal.
  • If the popup was closed or the user rejected the request, ask them to try again.

Issue: Transaction fails with “insufficient funds”

Solution:
  • Check the user’s wallet balance
  • Ensure enough funds for gas fees
  • Verify the amount being sent is correct

Issue: Contract calls fail

Solution:
  • Verify the contract address is correct
  • Check the ABI matches the contract
  • Ensure the signer is properly initialized
  • Verify the function name and parameters are correct

Issue: “Provider not found”

Solution:
  • Check the RPC URL is correct
  • Verify network connectivity
  • Test the RPC endpoint directly

Issue: Buffer is not defined

Solution:
  • Ensure buffer is installed: npm install buffer
  • Configure Vite as shown in the Project Setup section
  • Add buffer to optimizeDeps

API Reference

IncentivResolver

getAccountAddress(portal: string): Promise<string>

Opens the Incentiv Portal in a popup and returns the user’s wallet address after the connection flow is completed. Parameters: Returns:
  • Promise<string>: The user’s wallet address
Example:
const address = await IncentivResolver.getAccountAddress(
  "https://portal.incentiv.io"
);

IncentivSigner

Constructor

new IncentivSigner({
  address: string,
  provider: ethers.Provider,
  environment: string,
  entryPoint: string
})
Parameters:
  • address: User’s wallet address
  • provider: Ethers.js provider instance
  • environment: Portal URL
  • entryPoint: EntryPoint contract address
Example:
const signer = new IncentivSigner({
  address: "0x...",
  provider: provider,
  environment: "https://portal.incentiv.io",
  entryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4"
});

Additional Resources

License

This guide is provided as-is for educational purposes. Always refer to the official Incentiv SDK documentation for the most up-to-date information.

Example

Follow this example