Using Hunch
Gasless betting
The one signature a bet takes, what exactly you sign, and what the relayer can and cannot do with it.
One signature
USDG supports signed transfers (EIP-3009): instead of sending a transaction, you sign a message that authorises one specific transfer, and someone else submits it. Hunch uses that so a bet needs no ETH and no approval:
- 1
You sign
Your wallet signs a USDG “receive with authorization” for exactly your stake, payable only to the betting contract, with a one-time code that names the market, the side and the amount. - 2
Hunch relays it
The site posts the signature to Hunch’s relayer, which checks it, simulates it and sendsenterWithAuthorization, paying the gas (well under a cent). - 3
The contract books your bet
The betting contract runs every normal check, books the position for you (the signer, never the sender), then pulls the USDG with your authorization. Your position appears when the transaction confirms.
What you sign
EIP-712 typed data in USDG’s own signing domain. USDG does not publish its domain on-chain, so it is fixed in the app exactly as the token computes it (the resulting separator was recomputed and matched on-chain).
{
"domain": {
"name": "Global Dollar",
"version": "1",
"chainId": 4663,
"verifyingContract": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168"
},
"primaryType": "ReceiveWithAuthorization",
"types": {
"ReceiveWithAuthorization": [
{ "name": "from", "type": "address" },
{ "name": "to", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "validAfter", "type": "uint256" },
{ "name": "validBefore", "type": "uint256" },
{ "name": "nonce", "type": "bytes32" }
]
},
"message": {
"from": "<your address>",
"to": "0x1c23356536eA8E30F53481b971098aC30DA43576",
"value": "25000000",
"validAfter": "0",
"validBefore": "<a few minutes from now>",
"nonce": "<enterNonce(marketId, outcome, amount, salt)>"
}
}value is in USDG’s 6 decimals (25000000 is 25.00 USDG). to is always the betting contract.
How the bet is bound
The one-time code (the EIP-3009 nonce) is not random. It is computed from the bet itself, so the signature is only valid for that bet:
bytes32 public constant ENTER_TYPEHASH =
keccak256("HunchEnter(uint256 marketId,uint8 outcome,uint256 amount,bytes32 salt)");
function enterNonce(uint256 marketId, uint8 outcome, uint256 amount, bytes32 salt)
public view returns (bytes32)
{
return keccak256(abi.encode(ENTER_TYPEHASH, block.chainid, address(this),
marketId, outcome, amount, salt));
}When the relayer calls enterWithAuthorization(from, marketId, outcome, amount, validAfter, validBefore, salt, signature), the contract recomputes the code from the market, side and amount it was given and hands it to USDG. If any of them differ from what you signed, the code differs, your signature does not match, and USDG rejects the transfer. Outcome 0 is UP and 1 is DOWN. The salt is a random value so two identical bets have different codes.
What the relayer can and cannot do
| The relayer can | The relayer cannot |
|---|---|
| Submit your signed bet, and pay its gas. | Change the market, the side or the amount: the code would no longer match your signature. |
| Choose when to send it, inside the validity window you signed. | Send your USDG anywhere else: USDG only lets the named receiver (the betting contract) pull it. |
| Refuse to send it (for example if it fails a check). | Take the position: it is booked for the signer, never the sender. |
| Use the signature twice: USDG marks the code used. |
Checks before sending
Before it spends gas on your bet, the relayer checks off-chain, then simulates:
- the signature is over USDG’s domain on chain 4663 and is valid for
from; - the code equals
enterNonce(marketId, outcome, amount, salt); - the validity window is open;
- the amount is between 1 and 25 USDG, the market is taking bets, and new bets are not paused;
- the request does not come from a blocked country;
- no more than 10 requests a minute from one IP address or one signer.
The contract then runs the same rules again on-chain: open, not past the bell, a valid side, inside the limits, not paused. The relayer’s checks save gas; the contract’s checks are the ones that matter.
Wallets
- Your wallet must be on Robinhood Chain to sign. Wallets such as MetaMask refuse typed data whose domain chain id differs from the active network, so the bet button walks you through connect, switch (free) and sign.
- Smart-contract wallets can sign too: USDG accepts the byte-string signature form that checks a contract wallet’s own signature rule (ERC-1271).
Paying the gas yourself
The fallback path is two ordinary transactions from your wallet on Robinhood Chain: approve USDG to the betting contract, then enter(marketId, outcome, amount). It needs a little ETH (a bet costs well under a cent) and does not involve the relayer at all.