A decentralized, trustless escrow tipping protocol on GenLayer. Fund creators with custom criteria, verified by independent AI consensus, with secure deadline-enforced refunds.
Frontend Portal: tiptip-seven.vercel.app
Contract (GenLayer Studionet): 0x1A247D4F65a92Ec862b8dBCa05215e481b64bE89
Online tipping and creator sponsorship suffer from a fundamental trust gap. Supporters deposit capital upfront based on social promises, with no programmatic guarantee of delivery or quality. If creators produce low-effort content, recycle old work, or fail to follow through, supporters have no recourse. This friction discourages high-value micro-patronage.
TipTip resolves this trust gap by introducing Conditional Escrow Tipping:
- Milestone Escrow: Supporter funds are locked in the smart contract, bound to a specific natural-language milestone description and an expiry deadline.
- On-Chain Oracles: Creators submit a live Web URL as proof of completion.
- Subjective Consensus: GenLayer’s decentralized AI validators read the live page content, verify it against the locked criteria, and vote on-chain to release the funds.
- Deterministic Expiry: If the milestone remains unverified past the deadline, the tipper reclaims 100% of their locked capital.
Note
Unlike standard oracles that only fetch raw API data, TipTip utilizes GenLayer to perform subjective semantic verification of unstructured human work (like articles, code releases, or video uploads) against natural-language criteria.
graph TD
classDef user fill:#18181b,stroke:#27272a,stroke-width:1px,color:#f4f4f5;
classDef contract fill:#1e1e24,stroke:#d4af37,stroke-width:2px,color:#f4f4f5;
classDef consensus fill:#0f172a,stroke:#3b82f6,stroke-width:1px,color:#f4f4f5;
Tipper[Tipper / Patron]:::user
Creator[Creator]:::user
subgraph TipTip Contract on GenLayer
EscrowState["Escrow Record<br/>- Creator Address<br/>- Locked Capital (GEN)<br/>- Semantic Criteria<br/>- Proof URL<br/>- Deadline Timestamp"]:::contract
end
subgraph GenLayer Decentralized Network
LeaderNode["Leader Node<br/>- Fetches URL content<br/>- Prompts primary LLM<br/>- Proposes Verdict JSON"]:::consensus
Validators["AI Validators<br/>- Fetch URL content<br/>- Compare output semantic consensus<br/>- Vote (Equivalence Principle)"]:::consensus
end
Tipper -- "1. Create Escrow (Locks GEN, sets criteria and deadline)" --> EscrowState
Creator -- "2. Submits deliverables and updates Proof URL" --> EscrowState
Creator -- "3. Triggers verification" --> LeaderNode
EscrowState -. "Read criteria and URL" .-> LeaderNode
LeaderNode -- "4. Proposes Verdict" --> Validators
Validators -- "5. Validates Verdict (Tolerance +-2 score)" --> EscrowState
EscrowState -- "6a. Verification Passes (Release GEN)" --> Creator
Tipper -- "6b. Expiry Passes (Claim Refund)" --> EscrowState
EscrowState -- "7. Reclaim GEN" --> Tipper
One of the primary challenges in consensus-based execution environments is avoiding non-deterministic state evaluation.
Warning
Calling system-level clocks (like datetime.now() in Python) inside write transactions causes validators to generate different storage outputs depending on their physical clock synchronization. This leads to state root mismatches and consensus failures.
TipTip resolves this by enforcing Client-Provided Deterministic Timestamps:
- When creating a tip or claiming a refund, the client computes the current Unix epoch (
Math.floor(Date.now() / 1000)) and passes it as a transaction argument (client_now). - The contract uses this argument to compute and write the exact deadline deterministically:
$$\text{deadline} = \text{client_now} + (\text{duration_days} \times 86400)$$ - All validators process the exact same integer timestamp, guaranteeing complete consensus finalization.
The contract stores tip escrows in a native TreeMap[str, str] (mapping tip_id to serialized JSON strings) to optimize space and validator lookup performance.
-
create_tip(creator: str, criteria: str, proof_url: str, duration_days: i32, client_now: i32) -> i32 (payable)Initializes a new escrow tip. Receives the lockedgl.message.valueand returns the incrementaltip_id.[!IMPORTANT] Reverts with
UserErrorifgl.message.value == 0. -
update_proof_url(tip_id: str, new_url: str) -> NoneUpdates the target proof link for verification.- Access Control: Restricted exclusively to the
creatoraddress declared in the tip configuration. - Constraint: Reverts if the tip status is not
0(Pending).
- Access Control: Restricted exclusively to the
-
verify_and_release(tip_id: str) -> NoneTriggers the decentralized AI verification loop. Queries the proof page, parses content, runs consensus, and transfers funds on validation. -
claim_refund(tip_id: str, client_now: i32) -> NoneAllows the tipper to reclaim the locked GEN funds.- Access Control: Restricted exclusively to the
tipperaddress. - Constraint: Reverts if
client_nowis less than the tip's computeddeadline.
- Access Control: Restricted exclusively to the
-
get_tip(tip_id: str) -> strReturns the raw JSON metadata of a specific tip escrow. -
get_tip_count() -> i32Returns the total count of tips registered. -
get_tips(start: i32, limit: i32) -> list[str]Paginated batch reader. Avoids loop-based RPC roundtrips by retrieving multiple tips in a single call, optimizing network overhead and frontend load speeds.
When verify_and_release is invoked, GenLayer validators run an independent consensus round using the Equivalence Principle to grade the creator's proof:
# Normalized output validation schema
{
"verified": True or False,
"quality_score": 1-10,
"reasoning": "Reasoning string"
}-
Output Normalization: The leader and validators run
_parse_verdict()to strip markdown JSON fences and coerce the model's text into structured fields (boolandint). This prevents validators from disagreeing over minor text formatting differences (e.g. whitespace, capitalization, JSON markdown styling). -
Semantic Verification: In
validator_fn, the validator checks if:- The boolean
verifieddecision matches the leader's decision exactly. - The numeric
quality_scorematches within a tolerance of$\pm 2$ points.
- The boolean
- Agreement: If the validators agree on the semantic verdict, consensus is reached, the transaction commits, and funds are disbursed via an EVM transfer.
The Next.js client is configured to deliver a premium user experience while bypassing typical dApp onboarding friction:
- Direct EVM provider switch: Bypasses browser Snap plugins. It uses the MetaMask/Rabby provider directly to switch the client's wallet to the GenLayer Studio Network parameters (Chain ID
61999, RPChttps://studio.genlayer.com/api). - Terminal Status Mapping: The transaction monitor polls for terminal consensus states (
ACCEPTED,FINALIZED,UNDETERMINED,VALIDATORS_TIMEOUT) to display precise, user-friendly feedback if consensus fails or times out.
Ensure your py-genlayer environment is set up and run the static linter:
# Install linter
pip install genvm-linter
# Execute validation checks
genvm-lint check contracts/tiptip.pyRun the mock test suite to simulate state transitions, deadlines, and validation logic locally in Python:
python3 tests/test_tiptip.py# Point CLI to Studionet
genlayer network set studionet
# Unlock deployer account
genlayer account unlock
# Deploy contract
genlayer deploy --contract contracts/tiptip.pyDistributed under the MIT License.