
X 2 Earn Apps
- 65 installs
- 9 repo stars
- Updated June 11, 2026
- vechain/vechain-ai-skills
Helps with ai & agent building tasks.
About
x-2-earn-apps is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- x-2-earn-apps
- AI & Agent Building
- AI-coding skill
X 2 Earn Apps by the numbers
- 65 all-time installs (skills.sh)
- +2 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #6,085 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vechain/vechain-ai-skills --skill x-2-earn-appsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 9 |
| Last updated | June 11, 2026 |
| Repository | vechain/vechain-ai-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
X2Earn Apps Skill
CRITICAL RULES
1. Read reference files FIRST. When the user's request involves any topic in the reference table below, read those files before doing anything else — before writing code, before making decisions. Briefly mention which files you are reading so the user can confirm the skill is active (e.g., "Reading X2Earn integration reference..."). 2. Information priority for VeChain topics: (a) Reference files in this skill — always the primary source. (b) VeChain MCP tools — use @vechain/mcp-server for on-chain data, transaction building, and live network queries; use Kapa AI MCP for VeChain documentation lookups. (c) Web search — only as a last resort, and only for topics NOT covered in the reference files. 3. Prefer working directly in the main conversation for VeChain tasks. Plan mode and subagents do not inherit skill context and may fall back to web search instead of using reference files. 4. After compaction or context loss, re-read this SKILL.md to restore awareness of the reference table and operating procedure before continuing work.
Scope
Use this Skill for X2Earn app development on VeBetterDAO:
- Building sustainability-rewarding applications
- Distributing B3TR token rewards to users
- Sustainability proofs and impact tracking
- App submission, endorsement, and lifecycle
- Integration patterns (smart contract, backend, hybrid)
- VePassport and Sybil resistance considerations
Operating procedure
1. Clarify before implementing
When the user's request is ambiguous, ask before building. Key questions:
- Which integration pattern? (smart contract only, backend only, hybrid)
- Mainnet or testnet?
- Does the app already have a Creator NFT and endorsement?
2. Implement with VeChain-specific correctness
- Network: always explicit (
mainnet/testnet/solo) - Use correct contract addresses for the target network
- Always include sustainability proofs when distributing rewards
- Register reward distributors before attempting distribution
3. Verify and deliver
A task is not complete until all applicable gates pass:
1. Code compiles — no build errors 2. Tests pass — existing tests still pass; new logic has test coverage 3. Reward distribution tested — verified on testnet with correct proofs
Reference files
Read the matching files BEFORE doing anything else. See Critical Rules above.
| Topic | File | Read when user mentions... |
|---|---|---|
| Getting started, integration patterns, test environment, contract addresses, reward distribution basics, fund management | getting-started.md | building X2Earn app, getting started, testnet setup, solo node, reward distributor, integration pattern, fund management |
| Sustainability proofs, impact tracking, proof types, reward metadata | sustainability-proofs.md | sustainability proof, impact codes, distributeRewardWithProof, proof types, impact categories, reward metadata |
| Security, anti-farming, bot prevention, rate limiting | security.md | security, farming, bot prevention, rate limiting, device fingerprinting, private key management, Guardian |
| AI image validation, photo verification, fake image detection | ai-image-validation.md | AI image validation, photo verification, fake image, doctored photo, watermark detection |
| App submission, Creator NFT, endorsement, APP_ID, categories | app-submission.md | submit app, Creator NFT, endorsement, APP_ID, app categories, treasury address, admin address |
| VePassport, personhood check, bot signaling, Sybil resistance | vepassport.md | VePassport, personhood, bot signaling, Sybil, isPerson, whitelisting, blacklisting, KYC |
AI Image Validation for X2Earn Apps
When to use
Use when the user asks about: AI image verification, photo validation, fake image detection, anti-fraud image checks, doctored photo detection, watermark detection, screen capture detection, image authenticity.
Overview
X2Earn apps that use photos as proof of sustainable actions should validate images with AI to detect fraud. The AI prompt should check for:
- Image quality sufficient for evaluation
- Doctored or unrealistic modifications
- Photos of a computer screen (re-photographed)
- Watermarks (stock image reuse)
- Painted or hand-drawn text replacing real data
---
Recommended Response Schema
Request the AI to return a structured JSON response:
{
"evaluation_feasible": true,
"doctored_unrealistic_score": 0.0,
"doctored_unrealistic_reasons": [],
"screen_capture_score": 0.0,
"screen_capture_reasons": [],
"watermark_score": 0.0,
"watermark_reasons": [],
"watermark_text": "",
"painted_text_score": 0.0,
"painted_text_reasons": [],
"final_label": "clean",
"final_confidence": 0.0
}Field Reference
| Field | Description |
|---|---|
evaluation_feasible | true if image quality is sufficient for checks; false if too poor to reliably evaluate |
doctored_unrealistic_score / _reasons | 0-1 score for detected doctoring or unrealistic content |
screen_capture_score / _reasons | 0-1 score for detecting the image was taken from a screen |
watermark_score / _reasons / _text | 0-1 score for watermarks; watermark_text contains recognized text (e.g., "Shutterstock") |
painted_text_score / _reasons | 0-1 score for hand-drawn or digitally painted text |
final_label | Classification: clean, doctored_unrealistic, screen_capture, watermarked, handdrawn, multiple_flags, inconclusive |
final_confidence | 0-1 overall confidence in the classification |
---
Multi-Stage Prompt
Use this prompt (or adapt it) with a vision-capable AI model. The prompt processes the image through 6 sequential stages:
Mobile Photo Authenticity Check — Multi-Stage Prompt
Objective:
Given a photo provided by a mobile device, evaluate it through multiple
analytical stages to determine:
1. If the photo has been doctored or altered in an unrealistic way.
2. If the photo has been taken from a computer screen rather than being
an original capture of a real-world scene.
3. If the photo contains visible or partially obscured watermarks.
Instructions:
You must progress through the stages in sequence. At each stage, clearly
indicate whether the photo passes or fails, and explain the reasoning.
Output:
Return only the final JSON object in the specified schema—no extra text.
---
STAGE 1 — Quick Triage (visibility & quality):
1. Is the content visible and in focus enough to evaluate?
2. Are there heavy obstructions, extreme blur, or tiny resolution?
3. If evaluation is not feasible, mark evaluation_feasible=false and
explain briefly.
---
STAGE 2 — "Doctored / Unrealistic" Screening:
Check for visual signs of synthetic or manipulated content. Consider:
- Physics & geometry: inconsistent shadows, impossible reflections,
mismatched perspective/vanishing points, warped straight lines near edits.
- Material cues: plastic-like skin, repeated textures, smeared
hair/eyelashes, "melting" edges, duplicated fingers/ears.
- Edge artifacts: halos, cut-out borders, fringing, mismatched depth of field.
- Compression anomalies: localized blockiness/quality shifts suggesting
pasted regions.
- Lighting: inconsistent color temperature or specular highlights vs.
environment.
- Text & patterns: deformed text/logos, repeated tiling.
- Context coherence: scale mismatches, impossible combinations.
Output:
- doctored_unrealistic_score (0-1)
- doctored_unrealistic_reasons (bullet list)
---
STAGE 3 — "Photo of a Screen" Screening:
Evidence the subject was displayed on a digital screen and re-photographed:
- Screen structure: visible pixel grid/subpixels, scanlines, PWM/refresh
bands, moire.
- Device clues: bezels, notch, status bar, window chrome, cursor, taskbar,
scroll bars.
- Optical clues: rectangular glare, Newton rings, rainbowing consistent
with glass.
- Focus/parallax: focus on flat screen surface; keystone perspective of
a monitor.
- White point/gamut: uniform backlight glow, overly blue/green whites.
Output:
- screen_capture_score (0-1)
- screen_capture_reasons (bullet list)
---
STAGE 4 — Watermark / Overlay Detection:
Detect watermarks or ownership/stock overlays:
- Typical forms: semi-transparent text/logos ("Getty Images",
"Shutterstock", "Adobe Stock", creator handles), diagonal repeating
patterns, corner logos, composited date/time stamps.
- Visual traits: consistent alpha translucency, uniform repetition,
crisp overlay unaffected by scene lighting/perspective, different
resolution/sharpness vs. underlying image.
- Placement: edges/corners/center diagonals; multiple repeats; tiling.
- Edge cases: distinguish legitimate camera UI overlays (e.g., timestamp)
from stock watermarks.
- Context: note watermark content if legible but do not identify a person.
Output:
- watermark_score (0-1)
- watermark_reasons (bullet list)
- watermark_text (if recognized, e.g., "Shutterstock"; otherwise empty)
---
STAGE 5 — Detect Hand-Drawn or Painted-On Text:
Detect text manually added using a paint/drawing program:
- Look for uneven, non-font-based handwriting or shapes inconsistent
with printed text
- Identify brush strokes, smudging, or digital pen artifacts
- Detect text blending poorly with the background or overlapping objects
unnaturally
- Check for consistent resolution between text and the rest of the image
Output:
- painted_text_score (0-1)
- painted_text_reasons (bullet list)
---
STAGE 6 — Final Decision & Confidence:
- evaluation_feasible: boolean
- final_label: one of "clean", "doctored_unrealistic", "screen_capture",
"watermarked", "handdrawn", "multiple_flags", "inconclusive"
- final_confidence (0-1): overall confidence in final_label
- Keep reasoning concise; cite visible cues only.
---
OUTPUT — JSON Schema (return only this):
{
"evaluation_feasible": true,
"doctored_unrealistic_score": 0.0,
"doctored_unrealistic_reasons": [],
"screen_capture_score": 0.0,
"screen_capture_reasons": [],
"watermark_score": 0.0,
"watermark_reasons": [],
"watermark_text": "",
"painted_text_score": 0.0,
"painted_text_reasons": [],
"final_label": "clean",
"final_confidence": 0.0
}---
Integration Tips
- Threshold tuning: Set a confidence threshold (e.g., 0.7) below which images are flagged for manual review rather than auto-rejected
- Combine with other checks: AI validation is one layer — combine with rate limiting, device fingerprinting, and uniqueness checks (see security reference)
- Test thoroughly: Run the prompt against known-good and known-fake images to calibrate detection accuracy and watch for hallucinations
- Multiple models: Consider using different AI providers as a cross-check for high-value rewards
Submitting Your App to VeBetterDAO
When to use
Use when the user asks about: submitting an app, Creator NFT, endorsement process, APP_ID, reward distributor setup, app categories, treasury address, admin address, allocation eligibility, creator NFT minting, blacklisting, team members.
Submission Process
1. Acquire a Creator NFT
Two paths:
- Application form: Fill out the form on the VeBetter platform for a background check to verify the team and app legitimacy
- VeChain Grants: Apply at vechain.org/grants — if successful, receive funding AND a Creator NFT
2. On-Chain App Submission
After receiving the Creator NFT, submit the app on-chain for endorsement consideration. This registers the app on the VeBetter platform.
Required information at submission:
| Field | Description |
|---|---|
| Treasury address | Where B3TR tokens go when you withdraw from weekly allocations (for marketing, team shares, etc.). Can be a multi-sig or simple EOA |
| Admin address | Has full control: can update app details, change treasury address, transfer ownership. Can be a multi-sig or simple EOA. Can be the same as treasury |
3. Connect with Endorsers
The Creator NFT grants access to the VeBetter XApps Creators Discord server where you can network with VeChain node holders who may endorse your app.
4. Get Endorsed
The app needs a cumulative endorsement score of 100 from VeChain node holders. Once reached, the app is officially added to VeBetterDAO and becomes eligible for weekly B3TR funding through allocation rounds.
---
After Submission
Your APP_ID
Once added, you receive an APP_ID that VeBetterDAO and other projects use to identify your app. You will need this for all reward distribution calls.
Add a Reward Distributor
Use your Admin address to add a Reward Distributor — the address (contract or wallet) that will call distributeReward on X2EarnRewardsPool.
Important: The reward distributor address can also withdraw funds. Protect its access carefully.
First B3TR Allocation
You will receive your first B3TR tokens at least one week after joining, after at least one voting round completes. Plan for users discovering your app before you can distribute rewards — handle this gracefully in your UX.
---
App Categories
Select up to 2 categories at submission. Categories are stored by id; the name is the display label in the VeBetterDAO UI.
[
{ "id": "others", "name": "Others" },
{ "id": "education-learning", "name": "Learning" },
{ "id": "fitness-wellness", "name": "Lifestyle" },
{ "id": "green-finance-defi", "name": "Web3" },
{ "id": "green-mobility-travel", "name": "Transportation" },
{ "id": "nutrition", "name": "Food & Drinks" },
{ "id": "plastic-waste-recycling", "name": "Recycling" },
{ "id": "renewable-energy-efficiency", "name": "Energy" },
{ "id": "sustainable-shopping", "name": "Shopping" },
{ "id": "pets", "name": "Pets" }
]Note: If submitting a native application, link a landing page to your app.
---
X2Earn Creator NFT
The Creator NFT authorizes app submissions and provides access to endorsers. It is the entry point to the VeBetterDAO ecosystem for app creators.
Key Properties
| Property | Detail |
|---|---|
| Standard | ERC721 (non-transferable) — OpenZeppelin implementation |
| Minting authority | X2Earn App Review Panel only |
| Max per user | 1 NFT per user, regardless of number of apps |
| Transferability | Non-transferable — cannot be sold or transferred |
| Contract | X2EarnCreator (mainnet: 0xe8e96a768ffd00417d4bd985bec9EcfC6F732a7f) |
Team Members
- After initial submission, the app admin can add up to 2 additional creators (max 3 per app)
- Each additional creator receives their own Creator NFT
- Additional creators are restricted to their assigned app only — they cannot use their NFT to submit new apps
- Each creator is associated with exactly one app
Blacklisting and Burn
- If an app is blacklisted and the creator is not associated with any other active app, their Creator NFT is burned
- This prevents blacklisted creators from submitting new projects without re-approval
- Creators must go through the full review process again to re-enter the ecosystem
X2Earn App Development Guide
When to use
Use when the user asks about: building X2Earn apps, distributing B3TR rewards, testing on testnet or solo, reward distributor setup, integration patterns, fund management.
Requirements
An X2Earn app must:
1. Distribute B3TR tokens on VeChain 2. Submit a proof (JSON) of the sustainable action the user was rewarded for
Optional but recommended: allow users to connect their wallet via VeChain Kit.
Integration Patterns
Pattern 1: Smart Contract Only
All validation and distribution on-chain. User calls contract directly.
Pattern 2: Smart Contract + Backend
Backend validates actions, contract distributes rewards.
Pattern 3: Backend Only (most web2-friendly)
All logic in a backend using @vechain/sdk-network. No custom contract needed.
Contract Addresses
Mainnet
| Contract | Address |
|---|---|
| B3TR | 0x5ef79995FE8a89e0812330E4378eB2660ceDe699 |
| X2EarnApps | 0x8392B7CCc763dB03b47afcD8E8f5e24F9cf0554D |
| X2EarnRewardsPool | 0x6Bee7DDab6c99d5B2Af0554EaEA484CE18F52631 |
| XAllocationPool | 0x4191776F05f4bE4848d3f4d587345078B439C7d3 |
Testnet
| Contract | Address |
|---|---|
| B3TR | 0x95761346d18244bb91664181bf91193376197088 |
| X2EarnApps | 0x0b54a094b877a25bdc95b4431eaa1e2206b1ddfe |
| X2EarnRewardsPool | 0x2d2a2207c68a46fc79325d7718e639d1047b0d8b |
| XAllocationPool | 0x6f7b4bc19b4dc99005b473b9c45ce2815bbe7533 |
| XAllocationVoting | 0x8800592c463f0b21ae08732559ee8e146db1d7b2 |
---
Test Environment
Testnet (recommended)
All contracts are pre-deployed. Use the testnet governance dApp:
1. Go to staging.testnet.governance.vebetterdao.org/apps 2. Create a new app (auto-mints a Creator NFT) 3. Navigate to /admin → "X2Earn Apps" tab → click "Check Endorsement" for your app 4. Start a new round via "Start new round & claim allocations" (may need multiple rounds before B3TR flows) 5. Go to app settings (cog icon) → add your contract/wallet address as a Reward Distributor
Solo Node (local testing)
Requires deploying mock contracts. You need:
B3TR_Mock— fake token to distributeX2EarnAppsMock— register your app, generate APP_ID, add reward distributorsX2EarnRewardsPoolMock— distribute rewards (can use same contract as mainnet)
Mock contracts available in x-app-template under contracts/mocks/.
Deploy script pattern — only deploy mocks on vechain_solo:
import { ethers, network } from "hardhat";
let REWARD_TOKEN_ADDRESS = "0xE5FEfcB230364ef7f9B5B0df6DA81B227726612b"; // mainnet
export async function deploy() {
const MySustainableContract = await ethers.getContractFactory("MySustainableContract");
const myContract = await MySustainableContract.deploy();
await myContract.waitForDeployment();
if (network.name === "vechain_solo") {
// Deploy mocks
const RewardToken = await ethers.getContractFactory("B3TR_Mock");
const rewardToken = await RewardToken.deploy();
REWARD_TOKEN_ADDRESS = await rewardToken.getAddress();
const X2EarnApps = await ethers.getContractFactory("X2EarnAppsMock");
const x2EarnApps = await X2EarnApps.deploy();
const X2EarnRewardsPool = await ethers.getContractFactory("X2EarnRewardsPoolMock");
const x2EarnRewardsPool = await X2EarnRewardsPool.deploy(
deployer.address, REWARD_TOKEN_ADDRESS, await x2EarnApps.getAddress()
);
// Register app and fund
await x2EarnApps.addApp(deployer.address, deployer.address, "MySustainableApp");
const appID = await x2EarnApps.hashAppName("MySustainableApp");
await rewardToken.approve(await x2EarnRewardsPool.getAddress(), ethers.parseEther("10000"));
await x2EarnRewardsPool.deposit(ethers.parseEther("2000"), appID);
// Add contract as distributor and configure
await x2EarnApps.addRewardDistributor(appID, await myContract.getAddress());
await myContract.setVBDAppId(appID);
await myContract.setX2EarnRewardsPool(await x2EarnRewardsPool.getAddress());
}
}---
Reward Distribution
JavaScript (Backend)
Install: yarn add @vechain/sdk-network @vechain/vebetterdao-contracts
import {
ProviderInternalHDWallet, ThorClient,
VeChainProvider, VeChainSigner,
} from "@vechain/sdk-network";
import { X2EarnRewardsPool } from "@vechain/vebetterdao-contracts";
async function rewardUser(address: string) {
const thor = ThorClient.at(process.env.NODE_URL || "");
const provider = new VeChainProvider(
thor,
new ProviderInternalHDWallet(
process.env.REWARD_SENDER_MNEMONIC?.split(" ") || []
)
);
const signer = await provider.getSigner();
const rewardsPool = thor.contracts.load(
process.env.X2EARN_REWARDS_POOL_ADDRESS || "",
X2EarnRewardsPool.abi,
signer as VeChainSigner
);
const tx = await rewardsPool.transact.distributeRewardDeprecated(
process.env.VEBETTERDAO_APP_ID || "",
10,
address,
JSON.stringify({
version: 2,
description: "User refilled water from a sustainable source",
proof: { image: "https://image.png", link: "https://twitter.com/tweet/1" },
impact: { carbon: 100, water: 200 },
})
);
await tx.wait();
}Note: the JS examples above use distributeRewardDeprecated (legacy JSON string format). For new apps, prefer distributeRewardWithProof with typed arrays — see the vebetterdao skill for the typed API.
Critical: the PUBLIC ADDRESS of the wallet calling distributeReward must be registered as a Reward Distributor for your app via the governance dApp settings.
Solidity (On-chain)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "./interfaces/IX2EarnRewardsPool.sol";
contract MySustainableAppContract {
IX2EarnRewardsPool public x2EarnRewardsPool;
bytes32 public VBD_APP_ID;
mapping(uint256 => bool) public rewardClaimed;
constructor(IX2EarnRewardsPool _pool, bytes32 _appId) {
x2EarnRewardsPool = _pool;
VBD_APP_ID = _appId;
}
/// @notice Claim reward for a validated sustainable action
/// @dev Contract address must be registered as Reward Distributor
function claimReward(uint256 _actionId) external {
// ... validate action is approved and unclaimed
x2EarnRewardsPool.distributeReward(
VBD_APP_ID,
actions[_actionId].rewardAmount,
msg.sender,
"" // proof can be empty or JSON string
);
rewardClaimed[_actionId] = true;
}
}Critical: the contract address must be set as a Reward Distributor on the governance dApp before it can call distributeReward.
Round attribution: if your app lets users accumulate actions and claim later, use the ForRound variants (distributeRewardForRound, distributeRewardWithProofForRound, distributeRewardWithProofAndMetadataForRound) to attribute actions to the round they were performed in. These accept an additional actionRound parameter (must be > 0) representing the round ID when the action happened. This prevents users from stacking actions across rounds.
---
Managing Distributor Funds
Apps receive B3TR from weekly allocation rounds into X2EarnRewardsPool. The admin can split tokens between two pools:
- Distributable rewards pool — available for user rewards via
distributeReward - Treasury pool — withdrawable by admin to team wallet for operations
Key operations
| Action | Dashboard | Contract call |
|---|---|---|
| Refill rewards pool | Add funds via VBD dashboard | X2EarnRewardsPool.increaseRewardsPoolBalance(appId, amount) |
| Toggle rewards pool | Settings toggle | X2EarnRewardsPool.toggleRewardsPoolBalance(appId, enable) |
| Pause distribution | Settings toggle | X2EarnRewardsPool.pauseDistribution() |
| Withdraw to team wallet | Dashboard | X2EarnRewardsPool.withdraw(amount, appId, reason) |
New apps: the rewards pool feature is enabled by default. You must immediately refill the rewards pool after joining, up to the amount you want to distribute. If the pool reaches 0, distribution will fail until refilled or the feature is disabled.
---
Developer Resources
- X-App-Template: github.com/vechain/x-app-template — demo sustainability app with mocked contracts
- VeBetterDAO Contracts: github.com/vechain/vebetterdao-contracts — full source for ABI/TypeChain generation
- Test dApp: staging.testnet.governance.vebetterdao.org
- Docs: docs.vebetterdao.org/developer-guides
Security Considerations for X2Earn Apps
When to use
Use when the user asks about: anti-farming, bot prevention, security for X2Earn apps, rate limiting rewards, device fingerprinting, reward abuse, suspicious behavior detection, private key management, scaling rewards.
Overview
X2Earn apps must guard against farmers unfairly obtaining B3TR tokens. Stopping farmers is difficult — the practical goal is to slow their progress until the effort/reward ratio is no longer worth it.
---
Farming Attack Vectors
| Vector | Description |
|---|---|
| Single person, single wallet | Repeats the same claim multiple times. May submit fake images to AI validation |
| Single person, multiple wallets | Switches between wallet addresses in VeWorld to repeat claims from different accounts |
| Bots (scripts) | Automated scripts submitting claims, possibly generating/adjusting images for AI verification |
| Coordinated group | Multiple people collaborating, sharing wallets or social login details, pooling rewards |
| Exploit vulnerabilities | Attacks smart contracts or backend APIs directly, bypassing the frontend entirely |
---
Backend API Security
Certificate-Based Authentication
Secure endpoints (e.g., /account) with signed certificates from VeWorld. The backend validates the certificate and extracts the wallet address to identify the user.
Captcha Verification
Protect claim endpoints (e.g., /claim) with ReCaptcha to ensure requests originate from the frontend, not scripts/bots.
CORS Domains
Restrict API calls to requests from the same domain. A request from api.fakeapp.com to api.myapp.com is rejected.
Rate Limiting
Strategies to limit claim frequency:
- Time-based cooldown: No new claim until N hours after the last paid reward. Check via backend database or by reading the last reward event on-chain
- Round-based limits: Cap claims per allocation round (week)
- Device-based limits: Use device identification to limit claims per device, not just per address
---
Action Verification
AI-Based Validation
- Thoroughly test prompts to ensure AI validates as expected
- Test for hallucinations when extracting data from images (e.g., receipts)
- Require a confidence score — flag low-confidence results for manual review
Unique Identifiers
Every sustainable action needs indisputable evidence: a social media post, external system data, receipt, or timestamp. For AI image verification, prompt the AI to extract data usable as a uniqueness check.
---
Suspicious Behavior and Banning
Apps should detect patterns like rewards paid every 10 seconds to accounts with no other transaction history. Implement the ability to ban:
- Individual accounts
- Entire devices (not just wallet addresses)
---
Private Key Management
The distributor account private key must be secure and not visible in network traffic. Backend contracts or APIs signing reward transactions need secure key storage (environment variables, secrets manager, HSM — never hardcoded or client-side).
---
Device Fingerprinting
Farmers may install VeWorld (or your native app) multiple times on the same device with different wallets — especially on Android where software allows multiple app instances. Mitigations:
- Use tools like FingerprintJS to identify devices uniquely
- Ban the device rather than just the account(s)
- For native apps, build protections against multiple installs
---
Additional Strategies
Fund Management
The weekly allocation is at risk if the app has vulnerabilities:
- Withdraw to treasury: Reduce the app's B3TR balance by moving funds to a treasury account, drip-feeding back as needed
- Split allocation: Set a portion for distribution and another as treasury funds (withdrawable anytime)
Identity Verification
For apps offering social login (Google, Facebook, Twitter):
- Validate social media profiles (complete profile? account age?)
- Consider verification emails
- Flag accounts created very recently
Progressive Unlocking
New accounts get tighter rate limits and lower rewards. As users demonstrate legitimate usage, they unlock higher limits and rewards. This forces attackers to invest significant effort before reaching meaningful rewards.
Scaling Rewards by Demand
Dynamically adjust reward amounts based on claim volume:
1. Compute average rewards per second since the start of the week 2. Project forward to end of week 3. If projected spend exceeds the weekly budget, scale down rewards 4. Scale back up during quiet periods
Goal: make the weekly allocation last the full week while guarding against usage spikes (legitimate or bot-driven).
---
Recommended Service: Guardian
Guardian is a fraud detection and risk assessment platform built for the exact challenges X2Earn apps face: bot activity, multi-wallet farming, VPN/proxy abuse, device spoofing, browser tampering, and coordinated fraud.
- Provides detection signals and behavioral risk scoring
- Integrates into frontend or backend flows
- Discount code VEBETTER50 for 50% off
- Dashboard:
dashboard.guardianstack.ai - Docs:
docs.guardianstack.ai
Sustainability Proofs, Impacts, and Metadata
When to use
Use when the user asks about: sustainability proofs, impact tracking, proof types, impact codes, reward metadata, distributeRewardWithProof, distributeRewardWithProofAndMetadata, on-chain proof format.
Proof Format (Version 2)
Proofs are stored on-chain as emitted events. The JSON structure:
{
"version": 2,
"description": "The description of the action",
"proof": {
"image": "https://image.png",
"link": "https://twitter.com/tweet/1"
},
"impact": {
"carbon": 100,
"water": 200
}
}Distribution Parameters
When calling distributeRewardWithProof on X2EarnRewardsPool:
| Parameter | Type | Description |
|---|---|---|
proofTypes | string[] | Proof types provided, e.g. ["link", "image"] |
proofValues | string[] | Values matching proofTypes, e.g. ["https://twitter.com/tweet/123", "https://example.com/image.png"] |
impactCodes | string[] | Impact category codes, e.g. ["water", "timber"] |
impactValues | uint256[] | Values matching impactCodes, e.g. [1000, 23] |
description | string | Optional description of the action |
Mandatory rule: At least proof OR impact must be provided. If neither is provided, the transaction reverts. Transaction also reverts if array lengths are mismatched or data is malformed.
---
Proof Types
| Type | Description |
|---|---|
image | Photo evidence of the sustainable action |
link | URL to external proof (tweet, article, etc.) |
text | Text description as proof |
video | Video evidence of the action |
Multiple proofs can be provided simultaneously (e.g., a photo and a link).
---
Impact Categories
Critical: Impact values MUST be numbers in the default minimum unit (milliliters, grams, watt-hours, etc.). Example: 1 liter of water = 1000 (milliliters).
| Key | Category | Unit | Description |
|---|---|---|---|
carbon | Carbon Footprint Reduction | Grams (g) of CO2 equivalent | Decrease in greenhouse gas emissions |
water | Water Conservation | Milliliters (ml) | Water saved |
energy | Energy Conservation | Watt-hours (Wh) | Electricity saved |
waste_mass | Waste Reduction | Grams (g) | Waste diverted from landfills |
timber | Timber Conservation | Grams (g) | Timber saved |
plastic | Plastic Reduction | Grams (g) | Plastic saved or reduced |
education_time | Education Time | Seconds | Time spent learning about sustainability |
trees_planted | Trees Planted | Count | Number of trees planted |
calories_burned | Calories Burned | Calories (kcal) | Energy expenditure from physical activity |
sleep_quality_percentage | Sleep Quality | Percentage (%) | Sleep quality improvement |
clean_energy_production_wh | Clean Energy Production | Watt-hours (Wh) | Clean energy generated |
Apps should calculate impact based on these categories. If your app doesn't fit any existing category, you can submit a custom category and impact definition.
---
Code Examples
JavaScript
import {
ProviderInternalHDWallet, ThorClient,
VeChainProvider, VeChainSigner,
} from "@vechain/sdk-network";
import { X2EarnRewardsPool } from "@vechain/vebetterdao-contracts";
const thor = ThorClient.fromUrl("https://mainnet.vechain.org");
const provider = new VeChainProvider(
thor,
new ProviderInternalHDWallet("your space separated mnemonic".split(" ")),
);
const rootSigner = await provider.getSigner();
const rewardsPool = thor.contracts.load(
X2EarnRewardsPool.address.mainnet,
X2EarnRewardsPool.abi,
rootSigner as VeChainSigner,
);
const tx = await rewardsPool.transact.distributeRewardWithProof(
APP_ID,
amount,
receiverAddress,
["link", "image"],
["https://link-to-proof.com", "https://link-to-image.com/1.png"],
["waste_mass"],
[100],
"User performed a sustainable action on my app",
);
await tx.wait();Solidity
import "./interfaces/IX2EarnRewardsPool.sol";
contract MyContract {
function sendReward() onlyAdmin {
// Solidity requires dynamic arrays to be declared explicitly
string[] memory proofTypes = new string[](1);
proofTypes[0] = "link";
string[] memory proofUrls = new string[](1);
proofUrls[0] = action.proofUrl;
string[] memory impactTypes = new string[](1);
impactTypes[0] = "waste_mass";
uint256[] memory impactValues = new uint256[](1);
impactValues[0] = calculateWasteMass(
challenge.litterSize,
challenge.litterCount
);
IX2EarnRewardsPool x2EarnRewardsPool = IX2EarnRewardsPool(
x2EarnRewardsPoolAddress
);
x2EarnRewardsPool.distributeRewardWithProof(
VBD_APP_ID,
rewardAmount,
receiver,
proofTypes,
proofUrls,
impactTypes,
impactValues,
"User participated in a solo cleanup"
);
}
}---
Reward Metadata
The distributeRewardWithProofAndMetadata function allows enriching distributions with additional contextual information. Metadata is emitted via the RewardMetadata event for off-chain indexing and analytics.
Suggested Metadata Structure
{
"location": {
"city": "Berlin",
"country": "Germany",
"region": "EU"
},
"referral_source": "social_media",
"campaign_id": "earth_day_2025"
}Privacy note: Including location metadata may require updating your privacy policy and terms of service.
JavaScript Example
const metadata = {
location: { city: "Berlin", country: "Germany", region: "EU" },
referral_source: "social_media",
campaign_id: "earth_day_2025",
};
const tx = await rewardsPool.transact.distributeRewardWithProofAndMetadata(
APP_ID,
amount,
receiverAddress,
["link", "image"],
["https://link-to-proof.com", "https://link-to-image.com/1.png"],
["waste_mass"],
[100],
"User performed a sustainable action on my app",
JSON.stringify(metadata),
);
await tx.wait();Solidity Example
function sendRewardWithMetadata() onlyAdmin {
// ... set up proof and impact arrays as above ...
string memory metadata = '{"location":{"city":"Berlin","country":"Germany","region":"EU"},'
'"referral_source":"social_media","campaign_id":"earth_day_2025"}';
x2EarnRewardsPool.distributeRewardWithProofAndMetadata(
VBD_APP_ID,
rewardAmount,
receiver,
proofTypes,
proofUrls,
impactTypes,
impactValues,
"User participated in a solo cleanup",
metadata
);
}RewardMetadata Event
Emitted on successful distributeRewardWithProofAndMetadata:
| Parameter | Type | Description |
|---|---|---|
amount | uint256 | Distributed reward amount |
appId | bytes32 | Application identifier |
receiver | address | Address receiving the reward |
metadata | string | JSON-formatted metadata string |
distributor | address | Address initiating the distribution |
Guidelines
- Optional: If your app doesn't need extra context, use
distributeRewardWithProofinstead - Standardization: Follow the suggested structure for ecosystem consistency
- Validation: Ensure metadata JSON is correctly formatted before calling the function
---
Deprecated Proof Format (Version 1)
If no version field is found, treat as version 1:
{
"app_name": "cleanify",
"action_type": "litter_picking",
"proof": {
"proof_type": "link",
"proof_data": "https://x.com/user/status/123456"
},
"metadata": {
"description": "User picked up 8 small pieces of litter.",
"additional_info": ""
},
"impact": {
"waste_mass": "300",
"biodiversity": "1"
}
}Deprecated impact codes: waste_items, people, biodiversity.
Distribution Functions Summary
| Function | Use case |
|---|---|
distributeReward(appId, amount, receiver, proof) | Legacy — JSON string proof (deprecated) |
distributeRewardWithProof(appId, amount, receiver, proofTypes, proofValues, impactCodes, impactValues, description) | Standard — typed arrays for proof and impact |
distributeRewardWithProofAndMetadata(appId, amount, receiver, proofTypes, proofValues, impactCodes, impactValues, description, metadata) | Extended — adds JSON metadata for off-chain indexing |
distributeRewardForRound(appId, amount, receiver, proof, actionRound) | Basic distribution attributed to a specific round |
distributeRewardWithProofForRound(..., actionRound) | Proof distribution attributed to a specific round |
distributeRewardWithProofAndMetadataForRound(..., actionRound) | Proof + metadata distribution attributed to a specific round |
Round Attribution
By default, actions are recorded in the current round. If your app allows users to accumulate actions and claim them later, use the ForRound variants to attribute actions to the round they were performed in. The actionRound parameter must be > 0 and represents the round ID when the action actually happened.
VePassport (Sybil Resistance)
When to use
Use when the user asks about: VePassport, personhood check, bot detection, Sybil resistance, bot signaling, whitelisting, blacklisting, isPerson, proof of participation, proof of investment, passport delegation, KYC.
Overview
VePassport determines whether a wallet belongs to a real person or a bot. Used by VeBetterDAO for voting eligibility and available to all X2Earn apps for Sybil resistance.
Contract: 0x35a267671d8EDD607B2056A9a13E7ba7CF53c8b3 (mainnet)
Source: github.com/vechain/vebetterdao-contracts
---
Personhood Check
interface IVeBetterPassport {
function isPerson(address user) external view returns (bool person, string memory reason);
function isPersonAtTimepoint(
address user,
uint48 timepoint
) external view returns (bool person, string memory reason);
}The check evaluates these criteria in order:
1. If the address has delegated their passport → not a person 2. If the address is whitelisted → is a person 3. If the address is blacklisted → not a person 4. If flagged as a bot above the signaling threshold → not a person 5. If sufficient participation in previous/current rounds → is a person 6. If holds a GM NFT level 2+ → is a person 7. If none of the above → not a person
Currently Enabled Checks
Not all checks are active. Currently only delegation and Proof of Participation are enabled. The VeBetter team can enable/disable checks; future governance by the DAO.
Historical Check
isPersonAtTimepoint verifies personhood at a specific past block. Used by governance contracts during voting to check if a user was a person at round start. Only delegation and proof of investment support historical checks — whitelisting, blacklisting, bot signaling, and participation use current block data.
---
Proof Modules
Proof of Participation
Users must complete 3 sustainable actions within a 12-round (week) period to be considered a person.
An action = receiving a reward from an X2Earn app. Points accumulate based on the app's security level:
| App Security Level | Points per Action |
|---|---|
| None | 0 |
| Low | 100 |
| Medium | 200 |
| High | 400 |
Proof of Investment
Verified through the GM NFT level the wallet holds. A GM NFT level 2+ indicates significant investment (upgrading costs B3TR), suggesting the wallet is genuine.
Status: Integrated but not currently enabled as a standalone check.
Proof of Identity (Future)
Various KYC levels from social profile linking to full identity verification. Optional. Not yet implemented.
---
Bot Signaling
Authorized apps can flag suspicious addresses (bots, scammers). If a user's signal count exceeds the threshold, they fail the personhood check.
Contract Interface
interface IVeBetterPassport {
function signalingThreshold() external view returns (uint256);
function signaledCounter(address _user) external view returns (uint256);
function appSignalsCounter(bytes32 _app, address _user) external view returns (uint256);
function appTotalSignalsCounter(bytes32 app) external view returns (uint256);
function signalUserWithReason(address _user, string memory reason) external;
function resetUserSignalsByAppWithReason(address user, string memory reason) external;
}Key Functions
| Function | Description |
|---|---|
signaledCounter(user) | Total times a user has been signaled across all apps |
appSignalsCounter(app, user) | Times a user has been signaled by a specific app |
signalingThreshold() | Current threshold — exceeding this fails personhood check |
signalUserWithReason(user, reason) | Flag a user as suspicious |
resetUserSignalsByAppWithReason(user, reason) | Clear signals for a user (VeBetter or selected apps) |
JavaScript Example
import {
ThorClient,
ProviderInternalBaseWallet,
VeChainProvider,
} from "@vechain/sdk-network";
// Connect to network
const thor = ThorClient.at("https://mainnet.vechain.org");
const wallet = new ProviderInternalBaseWallet([
{ privateKey: Buffer.from(privateKey.slice(2), "hex"), address },
]);
const provider = new VeChainProvider(thor, wallet, false);
const signer = await provider.getSigner(address);
// Load VePassport contract
const passport = thor.contracts.load(
"0x35a267671d8EDD607B2056A9a13E7ba7CF53c8b3",
passportAbi,
signer,
);
// Check current signal count
const [counter] = await passport.read.signaledCounter(addressToSignal);
// Signal a user
const tx = await passport.transact.signalUserWithReason(
addressToSignal,
"Suspicious automated behavior",
);Permission Setup
App admins can self-assign the SIGNALER_ROLE:
// Grant signaling permission to a team member
assignSignalerToAppByAppAdmin(bytes32 app, address user)
// Revoke signaling permission
removeSignalerFromAppByAppAdmin(bytes32 app, address user)Community dashboard: Signal Admin Dashboard
---
Whitelisting and Blacklisting
- Whitelist: Authorized entities can whitelist addresses (e.g., after KYC). Whitelisted = always a person
- Blacklist: Addresses found to be part of fake account networks. Blacklisted = never a person
- Mistakenly blacklisted wallets can request reinstatement
---
Passport Delegation
VePassport supports delegating your passport to other addresses for interoperability across the VeBetter and VeChain ecosystem. You can also attach other accounts you own to your passport.
Important: If you delegate your passport, your original address will NOT be considered a person — the delegated address inherits personhood instead.