Documentation
Overview
Dues reads the subscription receipts already arriving in your mailbox and pays back a slice of the company behind each one as fractional tokenized stock. Pay for Netflix, hold a sliver of NFLX. Pay for Claude or ChatGPT, hold a sliver of NVDA.
Nothing gets minted. Every reward is a transfer of stock already sitting in a public vault, so the on-chain balance is the backing rather than a claim about it. You connect a mail filter and a wallet. Dues never signs in to your mailbox and only ever reads the receipts you send it.
How it works
- 01
Get an address
A unique collect+<id>@getdues.xyz that maps to your wallet and nothing else.
- 02
Forward receipts only
A filter scoped to subscription senders forwards matching mail. Two-factor codes and password resets never match it.
- 03
Dues reads three fields
Merchant, amount in USD, date. The merchant resolves to a tokenized stock.
- 04
Dues verifies
Valid DKIM, aligned to the merchant's own domain, deduped on Message-ID, addressed to you, inside the freshness window. Fail any one and the receipt pays nothing.
- 05
The reward is signed
Your $DUES holdings set the tier and rate, the remaining monthly ceiling applies, and an EIP-712 voucher is signed for the exact share count at the live price. The rate locks at the moment you earn.
- 06
You claim
Your own wallet submits the voucher and the vault transfers the stock. Dues never touches your key.
What earns a reward
A merchant that trades publicly pays in its own ticker. One that does not pays the proxy for its sector, so no covered receipt comes back empty. 83 merchants are mapped today and the list keeps growing.
1Password, Cloudflare, DigitalOcean, Fly.io, Hetzner, Neon, Netlify, NordVPN, Railway, Render, Supabase, Vercel, Vultr, Zoom
ChatGPT, Claude, Cursor, ElevenLabs, GeForce Now, Hugging Face, Midjourney, Perplexity, Replicate, Runway, Suno
Crunchyroll, Gemini, Google Cloud, Google One, Google Workspace, Max, Paramount+, Substack, The New York Times, YouTube Premium
AWS, GitHub, GitHub Copilot, LinkedIn Premium, Microsoft 365, Microsoft Azure, Xbox Game Pass
Adobe Creative Cloud, Canva, Figma, Framer, Miro, Notion
ClassPass, Oura, Peloton, Strava, Whoop
Discord Nitro, Meta Verified, Patreon, Tinder, X Premium
Epic Games, Nintendo Switch Online, PlayStation Plus, Steam
Amazon Prime, Audible, Twitch
Apple Music, Apple One, Apple TV+
Calm, Headspace, Noom
Disney+, Hulu
Netflix
Spotify
Uber One
Airbnb
Starlink
EA Play
Roblox Premium
Slack
Coinbase One
Robinhood Gold
Only a completed receipt from a covered merchant, verified by signature, pays anything.
Reward tiers
Your rate comes from how much $DUES you hold, measured as a share of total supply rather than a dollar figure. No lock-up. Each tier caps what one calendar month can pay.
| Tier | Held | Rate | Monthly ceiling |
|---|---|---|---|
| Base | none | 12% | $26 |
| Copper | 0.04% | 21% | $60 |
| Steel | 0.12% | 30% | $150 |
| Brass | 0.3% | 42% | $380 |
| Onyx | 0.65% | 58% | $900 |
| Charter | 1.25% | 72% | $1,900 |
- Rate at earn time. Whatever rate applied when a receipt was recorded is the rate locked into its voucher.
- Best of the last 24 hours. A brief dip in holdings does not knock you down a rung.
- Monthly ceiling. Rewards above the ceiling in a month are not paid, and it resets on the first.
Custody
DuesClaimVault holds the stock that backs rewards. It has no owner sweep and no arbitrary execute(). Stock leaves by exactly one path: a user calling claim() against a Dues-signed voucher. Stock enters by a plain transfer from the treasury.
Because every payout moves stock already in the vault, the vault's balances are honest backing you can read any time on the proof page. Payouts are authorised by the reward signer key, so the vault is exactly as trustworthy as that key is kept, which is why every rotation emits SignerSet on-chain.
Privacy and anti-abuse
Why a forged receipt pays nothing
- Signature and alignment. DKIM has to verify and its domain has to belong to the real merchant. Editing a forwarded mail breaks it.
- Uniqueness. Rewards dedupe on the immutable Message-ID, with a body hash covering receipts that lack one.
- Ownership. The receipt's original recipient has to include your own address.
- Freshness. Receipts older than the window are rejected outright.
- Ceilings. Each tier bounds what one account earns in a month.
What Dues can and cannot see
- Only the receipts you forward: merchant, amount, date. Never your mailbox, never a password.
- Delete the filter to revoke. There was no account access to take back.
- Claims are signed by your own wallet. The key never leaves your browser.
Contracts
The reward vault, MIT licensed and non-custodial, on Robinhood Chain (id 4663). The complete source is below with nothing elided.
claim(token, to, amount, id, deadline, sig)The only way stock leaves. Verifies the voucher against the signer, enforces a single-use id and a deadline, transfers to `to`.
reserves(token) → uint256Live vault balance of one reward stock. The proof-of-reserves read.
setRewardToken(token, allowed)Owner: add or drop a stock from the payout allow-list.
setSigner(s)Owner: rotate the reward signer. Emits SignerSet.
setPaused(p)Owner: emergency stop for claims. Moves no funds.
That is the entire owner surface. No sweep, no execute(), no fee pull, no receive(). Nothing moves a token out except claim().
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/// @title DuesClaimVault
/// @notice Pre-funded, multi-stock reward vault for Dues. It holds a basket of
/// tokenized stocks (NVDA, GOOGL, MSFT, AMZN, NFLX, SPOT, QQQ and the rest) and
/// releases a reward only against a Dues-signed EIP-712 voucher. Nothing is
/// minted here. Every payout moves stock that is already in the vault, so the
/// on-chain balance is the backing, not a claim about it.
///
/// The reward `amount` already carries the holder's tier rate. The Dues signer
/// reads their $DUES holdings, applies the tier rate (12% at Base up to 72% at
/// Charter) and the monthly ceiling, then signs the final figure. The contract
/// checks the signature, the reward-token allow-list, the single-use id and the
/// deadline. The economics stay off-chain in the signer.
///
/// Non-custodial by construction: there is no owner sweep and no arbitrary
/// execute(). Stock leaves only through claim(). The operator cannot withdraw
/// the backing. Stock flows in by a plain ERC-20 transfer from the treasury,
/// which collects protocol fees, buys the stock and deposits it here.
contract DuesClaimVault is EIP712, Ownable {
/// @notice Key that co-signs approved rewards. The trust anchor of the vault.
address public signer;
bool public paused;
mapping(address => bool) public isRewardToken; // tokenized-stock allow-list
mapping(bytes32 => bool) public voucherUsed; // reward id => redeemed
mapping(address => uint256) public totalPaid; // per-token lifetime payout
bytes32 public constant VOUCHER_TYPEHASH = keccak256(
"Reward(address token,address to,uint256 amount,bytes32 id,uint256 deadline)"
);
event Claimed(address indexed token, address indexed to, uint256 amount, bytes32 indexed id);
event RewardTokenSet(address indexed token, bool allowed);
event SignerSet(address indexed signer);
event PausedSet(bool paused);
constructor(address signer_, address[] memory rewardTokens_)
EIP712("Dues Rewards", "1")
Ownable(msg.sender)
{
require(signer_ != address(0), "signer=0");
signer = signer_;
for (uint256 i = 0; i < rewardTokens_.length; i++) {
require(rewardTokens_[i] != address(0), "token=0");
isRewardToken[rewardTokens_[i]] = true;
emit RewardTokenSet(rewardTokens_[i], true);
}
}
/// @notice Live reserve of one reward stock. The proof-of-reserves read.
function reserves(address token) external view returns (uint256) {
return IERC20(token).balanceOf(address(this));
}
// ---------------------------------------------------------------- claim ---
/// @notice Redeem an approved reward. Anyone may relay the voucher, but the
/// stock always lands at `to`. Single use per `id`, expires at `deadline`.
function claim(
address token,
address to,
uint256 amount,
bytes32 id,
uint256 deadline,
bytes calldata sig
) external {
require(!paused, "paused");
require(isRewardToken[token], "token");
require(to != address(0), "to=0");
require(block.timestamp <= deadline, "expired");
require(!voucherUsed[id], "used");
bytes32 structHash = keccak256(abi.encode(VOUCHER_TYPEHASH, token, to, amount, id, deadline));
require(ECDSA.recover(_hashTypedDataV4(structHash), sig) == signer, "bad sig");
voucherUsed[id] = true;
totalPaid[token] += amount;
require(IERC20(token).transfer(to, amount), "transfer failed");
emit Claimed(token, to, amount, id);
}
// ---------------------------------------------------------------- admin ---
// The owner surface is deliberately small. Nothing below moves a reward out
// of the vault: there is no sweep, no arbitrary execute, no fee pull and no
// receive(), so ether cannot be trapped here either. Rewards enter through a
// plain ERC-20 transfer from the treasury EOA and leave only through claim().
//
// Rotating `signer` is recorded on-chain via SignerSet, which is what makes
// the trust anchor auditable rather than merely asserted.
function setRewardToken(address token, bool allowed) external onlyOwner {
require(token != address(0), "token=0");
isRewardToken[token] = allowed;
emit RewardTokenSet(token, allowed);
}
function setSigner(address s) external onlyOwner {
require(s != address(0), "signer=0");
signer = s;
emit SignerSet(s);
}
/// @notice Pausing stops payouts and nothing else. It cannot move funds.
function setPaused(bool p) external onlyOwner {
paused = p;
emit PausedSet(p);
}
}
On-chain addresses
Everything settles on Robinhood Chain (id 4663). Nothing is deployed yet, so the table below reads launch soon until it is.
Launches paired with ETH. Your holdings set your rate.
Non-custodial. Stock leaves only against a signed voucher.
Takes fees, buys stock, funds the vault.
Co-signs every claim voucher. Public key, rotations logged on-chain.
CLI
The CLI talks to the Dues API and prints what comes back. It reads no local files, runs no shell commands and never asks for a private key.
npx github:getdues/dues-cli#v0.3.0 connect you@example.com --wallet 0x...npx github:getdues/dues-cli#v0.3.0 status <addr_id>npx github:getdues/dues-cli#v0.3.0 claim <addr_id>connectcreates your address, binds a wallet if you pass one, then waits for the mail confirmation code.statusreports this month's rewards, your tier and rate, and how much more to hold for the next one.claimprints the exactclaim()call to submit from a wallet you control.
MCP server
Lets an agent connect a mailbox and manage rewards in conversation. Four tools, one of which writes.
claude mcp add dues -s local -- npx github:getdues/dues-mcp#v0.3.0dues_connect(email, wallet?)write. Creates a forwarding address and returns the setup steps.dues_status(addr_id)read. Rewards, tier and rate, holdings, next tier, claimable count.dues_code(addr_id)read. The mail confirmation code once it arrives.dues_calldata(addr_id)read.claim()calldata per voucher. Public calldata, no signing.
FAQ
Can Dues take my rewards back?
No. Once stock is in the vault the only way it moves is a claim you sign. There is no owner withdrawal, which you can check in the source above.
Do I have to hold $DUES to earn?
No. Base earns 12% with zero holdings. Holding more raises both the rate and the monthly ceiling.
Is this an investment product?
No. Dues is a rewards utility. Tokenized stocks are held and transferred on Robinhood Chain, rewards are not a guaranteed return, and eligibility depends on the chain and on your jurisdiction.
How do I stop?
Delete the forwarding filter. Dues has no other access. Anything you already claimed stays in your wallet.