JavaScript / TypeScript client that lets any web dApp send transactions through the Incentiv Portal.
Incentiv uses a proprietary signing flow that lives inside the Incentiv Portal. Trying to reproduce that logic inside your dApp would be brittle, insecure, and would violate the user‑experience guidelines of the network.
The Incentiv dApp SDK solves this by:
- Opening a secure pop‑up in the Incentiv Portal when you need the user to sign.
- Submitting the resulting UserOperation on your behalf.
- Returning the UserOperation hash so that you can track it afterwards.
- Providing a drop‑in
ethers.jsSignerso you can keep the rest of your code unchanged.
- Zero private‑key handling in your site – users sign inside the Portal
IncentivSigner→ works anywhere anethers.Signeris expectedIncentivResolver→ low‑level helper if you don't use ethers- Supports Staging, Testnet & Mainnet (or any custom Portal URL)
- Ships with TypeScript types
npm install @incentiv/dapp-sdk ethers@5.7.2 # ethers v5 is required
# or
yarn add @incentiv/dapp-sdk ethers@5.7.2ethers v5.x only for now
import { IncentivSigner, IncentivResolver } from "@incentiv/sdk";
import { ethers } from "ethers";
/**
* Choose which Incentiv environment you want to talk to
*/
const Environment = {
Portal: "https://portal.incentiv.io",
RPC: "https://rpc.incentiv.io",
EntryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
VerifierContract: "0xd44EbfDf4FFf3e367b7e07e47eA0e70F5277Bca0",
};
async function main () {
// Ask the Portal to connect and give us the user's address
const address = await IncentivResolver.getAccountAddress(Environment.Portal);
// Standard ethers provider
const provider = new ethers.providers.StaticJsonRpcProvider(Environment.RPC);
// Drop‑in signer – use it just like any ethers.Signer
const signer = new IncentivSigner({
address,
provider,
environment: Environment.Portal,
entryPoint: Environment.EntryPoint,
verifierContract: Environment.VerifierContract
});
// Send a transaction – the Portal pop‑up will appear automatically
const userOpReceipt = await signer.sendTransaction({
to: "0xYourContract",
data: "0x…", // encoded calldata
value: ethers.utils.parseEther("0.01")
});
console.log("UserOperation hash:", userOpReceipt.hash);
// Wait for confirmation
await userOpReceipt.wait();
}
main().catch(console.error);IncentivResolver and IncentivSigner rely on window.open and postMessage, therefore they must run in a browser context (NodeJS & SSR are not supported).
The SDK supports signing arbitrary messages through the Incentiv Portal and verifying those signatures on-chain. This is useful for authentication, proof of ownership, and other use cases where you need cryptographic proof without sending a transaction.
IncentivSigner.signMessageDetailed()→ Opens Portal popup → Returns signature + owner dataIncentivSigner.verifySignature()→ Calls Verifier contract → Returns validity + AA wallet address
import { IncentivSigner } from "@incentiv/sdk";
import { ethers } from "ethers";
const Environment = {
Portal: "https://portal.incentiv.io",
RPC: "https://rpc.incentiv.io",
EntryPoint: "0x3eC61c5633BBD7Afa9144C6610930489736a72d4",
VerifierContract: "0xd44EbfDf4FFf3e367b7e07e47eA0e70F5277Bca0",
};
async function signAndVerify() {
const provider = new ethers.providers.StaticJsonRpcProvider(Environment.RPC);
const signer = new IncentivSigner({
address: "0x...", // Your user's AA wallet address
provider,
environment: Environment.Portal,
entryPoint: Environment.EntryPoint,
verifierContract: Environment.VerifierContract,
});
// Sign a message - Portal popup will appear
const message = "Please sign this message to authenticate";
const signResponse = await signer.signMessageDetailed(message);
console.log("Signature:", signResponse.signature);
console.log("Owner:", signResponse.owner);
// Verify the signature on-chain
const verification = await signer.verifySignature(
message,
signResponse.signature,
signResponse.owner
);
console.log("Is valid:", verification.isValid);
console.log("AA wallet address:", verification.accountAddress);
}-
Signing: When you call
signMessageDetailed(), the SDK opens the Incentiv Portal in a popup window where the user can securely sign the message. The Portal returns both the signature and the owner data (the key that signed it). -
Verification: The
verifySignature()method calls the on-chain Verifier contract to validate that:- The signature is valid for the given message and owner
- Returns the corresponding AA wallet address for that owner
This allows you to prove that a user controls a specific AA wallet without requiring a transaction, which is perfect for authentication flows, session management, or any off-chain verification needs.
| Variant | URL |
|---|---|
| Testnet | https://testnet.incentiv.io |
| Mainnet | https://portal.incentiv.io |
| Method | Description |
|---|---|
static getAccountAddress(environment) |
Opens the Portal pop‑up with intent CONNECT. Resolves with the user's account address. |
constructor(environment) |
Creates a resolver bound to a specific Portal URL. |
sendTransaction(tx: TransactionRequest) |
Opens the Portal pop‑up with intent CALL, waits for the signature & submission, then resolves with the UserOperation hash. |
sendBatchTransaction(calls: BatchCall[], options: BatchRequestOptions) |
Opens the Portal pop‑up with intent BATCH, executes multiple calls in a single UserOperation, then resolves with the UserOperation hash. |
getPortalUrl() |
Returns the Portal URL the resolver is bound to. |
| Method | Notes |
|---|---|
getAccountAddress() |
Same as the static resolver call but also stores the address internally. |
sendTransaction(tx) |
Returns a TransactionResponse‑like object whose .hash is the UserOperation hash. The .wait() method is not implemented yet. |
sendBatchTransaction(calls: BatchCall[], options: BatchRequestOptions) |
Returns a TransactionResponse‑like object for batch transactions. Executes multiple calls in a single UserOperation with full .wait() support. |
signMessage(message) |
Opens the Portal popup to sign an arbitrary message. Returns the signature as a string concatenated with the owner data with a colon separator. Concatenation is done to preserve compatibility with ethers signer interface. For ease of use, you can use the signMessageDetailed() method instead. |
signMessageDetailed(message) |
Same as signMessage() but returns a SignResponse object containing the signature and owner data separately. |
verifySignature(message, signature, owner) |
Verifies a signature on-chain using the Verifier contract. Returns { isValid: boolean, accountAddress: string }. accountAddress is the address of the AA wallet that is owned by the message signer. Requires verifierContract to be set in constructor. |
connect(provider) |
Returns a new signer instance bound to the given provider. |
setAccountAddress(address) |
Manually set / override the account address. |
signTransactionis intentionally unsupported – transaction signing happens inside the Portal UI throughsendTransaction()orsendBatchTransaction().
interface BatchCall {
to: string; // Target contract address
value?: string; // Value to send (optional)
data?: string; // Encoded calldata (optional)
}
interface BatchRequestOptions {
from: string; // Sender address
gasLimit?: string; // Gas limit (optional)
gasPrice?: string; // Gas price (optional)
maxPriorityFeePerGas?: string; // EIP-1559 priority fee (optional)
maxFeePerGas?: string; // EIP-1559 max fee (optional)
}interface SignResponse {
signature: string; // The cryptographic signature
owner: string; // The owner/key data used to create the signature
}sequenceDiagram
participant dApp
participant PortalPopup
dApp->>PortalPopup: window.open(.../dapp?data=CONNECT)
PortalPopup-->>dApp: postMessage CONNECT_RESOLVED (address)
dApp->>PortalPopup: window.open(.../dapp?data=CALL&calldata=...)
PortalPopup-->>dApp: postMessage CALL_EXECUTED (hash)
sequenceDiagram
participant dApp
participant PortalPopup
participant VerifierContract
dApp->>PortalPopup: window.open(.../dapp?data=SIGN&message=...)
PortalPopup-->>dApp: postMessage SIGN_RESOLVED (signature, owner)
dApp->>VerifierContract: verifySignature(owner, message, signature)
VerifierContract-->>dApp: { isValid, accountAddress }
MIT © 2025 Incentiv Network