Skip to content

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. 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. 2

    Hunch relays it

    The site posts the signature to Hunch’s relayer, which checks it, simulates it and sends enterWithAuthorization, paying the gas (well under a cent).
  3. 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).

The typed data your wallet shows (example: 25 USDG)
{
  "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:

HunchVPM
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's powers
The relayer canThe 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.