---
name: megapot-subscribe
description: Create, check, or cancel a recurring daily Megapot ticket-purchase subscription via JackpotAutoSubscription. Keeper bots execute daily purchases automatically.
---

# Megapot Subscriptions

## What This Does

Creates a recurring daily ticket purchase subscription: `JackpotAutoSubscription.createSubscription` locks USDC upfront, and keeper bots call `executeSubscriptions` once per drawing. Supports checking active subscription state via `getSubscriptionInfo` and cancelling with a USDC refund via `cancelSubscription`.

## Prerequisites

- A wallet with a private key (EOA) connected to Base mainnet (chain ID 8453)
- Sufficient USDC balance: `ticketPrice × ticketsPerDay × totalDays` (6 decimals)
- Sufficient ETH for gas
- `viem` installed (`npm install viem`)

## Addresses

Base mainnet (chain ID 8453):

```ts
const JACKPOT_ADDRESS               = '0x3bAe643002069dBCbcd62B1A4eb4C4A397d042a2' as const;
const JACKPOT_AUTO_SUBSCRIPTION     = '0x02A58B725116BA687D9356Eafe0fA771d58a37ac' as const;
const USDC_ADDRESS                  = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const;
```

Base Sepolia testnet (chain ID 84532):

```ts
const JACKPOT_ADDRESS               = '0x465dA3c859f193A3807386387bEE941B2A4c3279' as const;
const JACKPOT_AUTO_SUBSCRIPTION     = '0x054a61E2FC77BAb3c9D94C3f835FB7ADE97a2F90' as const;
const USDC_ADDRESS                  = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' as const;
```

> Base mainnet public RPC: `https://mainnet.base.org` (rate-limited; use [Alchemy](https://www.alchemy.com/) or [QuickNode](https://www.quicknode.com/) for production).

## ABI

Only the fragments needed for this task:

```ts
import { parseAbi } from 'viem';

const abi = parseAbi([
  // --- Jackpot read ---
  "function ticketPrice() view returns (uint256)",

  // --- JackpotAutoSubscription writes ---
  "function createSubscription(address _recipient, uint64 _totalDays, uint64 _dynamicTicketCount, (uint8[] normals, uint8 bonusball)[] _userStaticTickets, address[] _referrers, uint256[] _referralSplit)",
  "function cancelSubscription()",

  // --- JackpotAutoSubscription reads ---
  "function getSubscriptionInfo(address _recipient) view returns (((uint64 remainingUSDC, uint64 lastExecutedDrawing, uint64 subscribedTicketPrice, uint64 dynamicTicketCount, address[] referrers, uint256[] referralSplit) subscription, (uint8[] normals, uint8 bonusball)[] staticTickets))",

  // --- JackpotAutoSubscription events ---
  "event SubscriptionCreated(address indexed payer, address indexed recipient, uint256 totalCost, uint256 totalDays, uint256 drawingStart, uint256 dynamicTicketCount, uint256 staticTicketCount, uint256 ticketPrice)",
  "event SubscriptionCancelled(address indexed recipient, uint8 indexed executionAction, uint64 refundAmount)",
  "event SubscriptionExecuted(address indexed recipient, uint256 indexed drawingId, uint256[] ticketIds, uint256 dynamicTicketsPurchased, uint256 staticTicketsPurchased)",

  // --- JackpotAutoSubscription errors ---
  "error ActiveSubscriptionExists()",
  "error NoActiveSubscription()",
  "error InvalidDuration()",
  "error InvalidTicketCount()",
  "error JackpotNotInitialized()",
  "error TooManyReferrers()",

  // --- USDC ---
  "function balanceOf(address account) view returns (uint256)",
  "function allowance(address owner, address spender) view returns (uint256)",
  "function approve(address spender, uint256 amount) returns (bool)",
]);
```

## Recipe

### Setup

```ts
import {
  createPublicClient,
  createWalletClient,
  http,
  parseAbi,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

// --- PLACEHOLDERS: replace with your values ---
const PRIVATE_KEY = '0xYOUR_PRIVATE_KEY' as `0x${string}`;  // placeholder
const RPC_URL     = 'https://mainnet.base.org';               // or your own RPC URL

const JACKPOT_ADDRESS           = '0x3bAe643002069dBCbcd62B1A4eb4C4A397d042a2' as const;
const JACKPOT_AUTO_SUBSCRIPTION = '0x02A58B725116BA687D9356Eafe0fA771d58a37ac' as const;
const USDC_ADDRESS              = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const;

// Your referrer address — you earn a percentage of every ticket sale and user winnings.
// Replace with your wallet address before deploying.
const REFERRER_ADDRESS = '0x0000000000000000000000000000000000000001' as const; // placeholder — replace with your address

// For Base Sepolia testnet: replace `base` with `baseSepolia` from 'viem/chains',
// swap addresses to the testnet values above, and use https://sepolia.base.org as RPC.

const abi = parseAbi([
  "function ticketPrice() view returns (uint256)",
  "function createSubscription(address _recipient, uint64 _totalDays, uint64 _dynamicTicketCount, (uint8[] normals, uint8 bonusball)[] _userStaticTickets, address[] _referrers, uint256[] _referralSplit)",
  "function cancelSubscription()",
  "function getSubscriptionInfo(address _recipient) view returns (((uint64 remainingUSDC, uint64 lastExecutedDrawing, uint64 subscribedTicketPrice, uint64 dynamicTicketCount, address[] referrers, uint256[] referralSplit) subscription, (uint8[] normals, uint8 bonusball)[] staticTickets))",
  "event SubscriptionCreated(address indexed payer, address indexed recipient, uint256 totalCost, uint256 totalDays, uint256 drawingStart, uint256 dynamicTicketCount, uint256 staticTicketCount, uint256 ticketPrice)",
  "event SubscriptionCancelled(address indexed recipient, uint8 indexed executionAction, uint64 refundAmount)",
  "event SubscriptionExecuted(address indexed recipient, uint256 indexed drawingId, uint256[] ticketIds, uint256 dynamicTicketsPurchased, uint256 staticTicketsPurchased)",
  "error ActiveSubscriptionExists()",
  "error NoActiveSubscription()",
  "error InvalidDuration()",
  "error InvalidTicketCount()",
  "error JackpotNotInitialized()",
  "error TooManyReferrers()",
  "function balanceOf(address account) view returns (uint256)",
  "function allowance(address owner, address spender) view returns (uint256)",
  "function approve(address spender, uint256 amount) returns (bool)",
]);

const account = privateKeyToAccount(PRIVATE_KEY);

const publicClient = createPublicClient({
  chain: base,
  transport: http(RPC_URL),
});

const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(RPC_URL),
});
```

---

## Flow 1 — Create a 30-day × 5-tickets/day Subscription

### Step 1 — Read ticket price and calculate total cost

```ts
const ticketPrice = await publicClient.readContract({
  address: JACKPOT_ADDRESS,
  abi,
  functionName: 'ticketPrice',
});
// e.g. 1_000_000n — 1 USDC at 6 decimals

const totalDays        = 30n;
const ticketsPerDay    = 5n;   // _dynamicTicketCount: all quick-pick
const totalCost        = ticketPrice * totalDays * ticketsPerDay;
// e.g. 1_000_000n × 30n × 5n = 150_000_000n  →  150 USDC
```

### Step 2 — Approve USDC spend on JackpotAutoSubscription

```ts
// Approval target is JackpotAutoSubscription, NOT Jackpot
const currentAllowance = await publicClient.readContract({
  address: USDC_ADDRESS,
  abi,
  functionName: 'allowance',
  args: [account.address, JACKPOT_AUTO_SUBSCRIPTION],
});

if (currentAllowance < totalCost) {
  const approveTx = await walletClient.writeContract({
    address: USDC_ADDRESS,
    abi,
    functionName: 'approve',
    args: [JACKPOT_AUTO_SUBSCRIPTION, totalCost],
  });
  await publicClient.waitForTransactionReceipt({ hash: approveTx });
  console.log('USDC approved:', approveTx);
}
```

### Step 3 — Create the subscription

All 5 daily tickets are quick-picks: pass `_dynamicTicketCount = 5` and an empty `_userStaticTickets` array.

```ts
const createTx = await walletClient.writeContract({
  address: JACKPOT_AUTO_SUBSCRIPTION,
  abi,
  functionName: 'createSubscription',
  args: [
    account.address,                          // _recipient  — wallet that receives the ticket NFTs each day
    30n,                                      // _totalDays  — 30 daily drawings
    5n,                                       // _dynamicTicketCount — 5 quick-pick tickets per drawing
    [],                                       // _userStaticTickets  — no static (hand-picked) tickets
    [REFERRER_ADDRESS],                       // _referrers — your revenue address
    [1000000000000000000n],                   // _referralSplit — 100% to single referrer (1e18 scale)
  ],
});

const receipt = await publicClient.waitForTransactionReceipt({ hash: createTx });
console.log('Subscription created, tx:', createTx);
// Emits: SubscriptionCreated(payer, recipient, totalCost=150_000_000, totalDays=30,
//         drawingStart, dynamicTicketCount=5, staticTicketCount=0, ticketPrice)
```

**Cost breakdown example** (assuming `ticketPrice = 1_000_000n`):

```
ticketPrice     =   1_000_000  (1.00 USDC)
tickets per day ×           5
days            ×          30
──────────────────────────────
totalCost       = 150_000_000  (150.00 USDC)
```

---

## Flow 2 — Check Subscription Status

> **Important:** `getSubscriptionInfo` reverts with `NoActiveSubscription` if the address has no subscription — it does NOT return empty/zero data. In React/wagmi, use `allowFailure: true` in `useReadContract` or handle the error state. In raw viem, wrap the call in a try/catch.

> **wagmi tip:** Set `query: { retry: false }` on the `useReadContract` hook for `getSubscriptionInfo` to prevent wagmi from retrying the expected revert:
> ```tsx
> const { data, isError } = useReadContract({
>   address: SUBSCRIPTION_ADDRESS, abi, functionName: 'getSubscriptionInfo',
>   args: [address],
>   query: { retry: false },
> });
> // isError === true means no active subscription — show "create" UI
> ```

```ts
const info = await publicClient.readContract({
  address: JACKPOT_AUTO_SUBSCRIPTION,
  abi,
  functionName: 'getSubscriptionInfo',
  args: [account.address],  // or any recipient address to inspect
});

const { subscription, staticTickets } = info;
const {
  remainingUSDC,          // uint64 — USDC remaining in the subscription (6 decimals)
  lastExecutedDrawing,    // uint64 — drawingId of the most recently executed day
  subscribedTicketPrice,  // uint64 — ticket price locked at subscription creation
  dynamicTicketCount,     // uint64 — quick-pick tickets purchased per drawing
  referrers,              // address[]
  referralSplit,          // uint256[]
} = subscription;

const remainingDays = remainingUSDC / (subscribedTicketPrice * dynamicTicketCount);

console.log('Remaining USDC:   ', Number(remainingUSDC) / 1e6, 'USDC');
console.log('Remaining days:   ', remainingDays.toString());
console.log('Last executed:    drawing', lastExecutedDrawing.toString());
console.log('Tickets/day:      ', dynamicTicketCount.toString());
console.log('Static tickets:   ', staticTickets.length);
```

---

## Flow 3 — Cancel with USDC Refund

`cancelSubscription` is called by the subscriber (msg.sender). It returns the remaining USDC to the payer immediately.

```ts
const cancelTx = await walletClient.writeContract({
  address: JACKPOT_AUTO_SUBSCRIPTION,
  abi,
  functionName: 'cancelSubscription',
  // no arguments — cancels the subscription for msg.sender's registered recipient
});

const receipt = await publicClient.waitForTransactionReceipt({ hash: cancelTx });
console.log('Subscription cancelled, tx:', cancelTx);
// Emits: SubscriptionCancelled(recipient, executionAction, refundAmount)
// refundAmount is the USDC returned (6 decimals)
```

---

## Parameters

### `createSubscription`

| Parameter | Type | Description |
|---|---|---|
| `_recipient` | `address` | Wallet that receives the ticket NFTs each drawing. Usually `account.address`. |
| `_totalDays` | `uint64` | Number of drawings to subscribe for (e.g. `30`). Must be > 0. |
| `_dynamicTicketCount` | `uint64` | Quick-pick tickets to buy per drawing (e.g. `5`). Must be > 0 unless static tickets are provided. |
| `_userStaticTickets` | `{ normals: uint8[], bonusball: uint8 }[]` | Hand-picked tickets repeated every drawing. **Recommended maximum: 10 entries.** The contract does not enforce a length cap, but Base's per-block calldata budget makes large static arrays risky to settle, and megapot.io's UI caps static picks at 10 per subscription — following the same convention keeps your integration consistent. The array is stored once at subscription creation and replayed each drawing. Each `normals` must be exactly 5 unique values in ascending order, each in `[1, normalBallMax]` (typically `[1, 30]`). Pass `[]` for all-quick-pick. |
| `_referrers` | `address[]` | Your revenue address(es). You earn a share of ticket price and user winnings for every purchase. Up to `maxReferrers` addresses (typically 5). Always include at least your own address. |
| `_referralSplit` | `uint256[]` | Referral fee split weights per referrer in **1e18 (PRECISE_UNIT) scale, not basis points**. Must sum to exactly `1000000000000000000` (`10n ** 18n`). Same length as `_referrers`. Pass `[]` when no referrers. A single referrer getting 100% = `[1000000000000000000n]` (10^18). A 70/30 split = `[700000000000000000n, 300000000000000000n]`. |

> To purchase without referral attribution (no fees earned), pass empty arrays: `_referrers: []`, `_referralSplit: []`.

### `getSubscriptionInfo` return fields

| Field | Type | Description |
|---|---|---|
| `remainingUSDC` | `uint64` | USDC balance remaining in the subscription (6 decimals). |
| `lastExecutedDrawing` | `uint64` | DrawingId of the last drawing the bot executed for this subscription. |
| `subscribedTicketPrice` | `uint64` | Ticket price (6 decimals) snapshotted at subscription creation. |
| `dynamicTicketCount` | `uint64` | Quick-pick tickets per drawing. |
| `referrers` | `address[]` | Referrer addresses associated with this subscription. |
| `referralSplit` | `uint256[]` | Referral fee split weights per referrer in 1e18 scale (PRECISE_UNIT). |
| `staticTickets` | `{ normals: uint8[], bonusball: uint8 }[]` | Hand-picked tickets repeated every drawing. |

---

## Common Errors

| Error | Cause |
|---|---|
| `ActiveSubscriptionExists()` | Recipient already has an active subscription; cancel it first |
| `NoActiveSubscription()` | `cancelSubscription` or `getSubscriptionInfo` called when no subscription exists for the address. `getSubscriptionInfo` reverts (does not return empty data) — wrap in try/catch or use `allowFailure: true` in multicall. |
| `InvalidDuration()` | `_totalDays` is zero |
| `InvalidTicketCount()` | `_dynamicTicketCount` is zero and no static tickets provided |
| `InvalidStaticTicket()` | A static ticket has an out-of-range normal ball or bonusball value (per-ticket validation; this error does not signal an array-length problem — see `_userStaticTickets` parameter notes for the recommended 10-entry cap) |
| `JackpotNotInitialized()` | The Jackpot contract has not been initialised yet |
| `TooManyReferrers()` | `_referrers` length exceeds `maxReferrers()` on the Jackpot contract |
| `SafeERC20FailedOperation` | USDC `approve` or `transferFrom` failed — check balance and that approval targets `JackpotAutoSubscription`, not `Jackpot` |

---

## Related

- `megapot-buy-tickets` — buy 1–10 tickets with custom numbers
- `megapot-buy-random` — buy up to 10 random tickets (no number selection needed)
- `megapot-buy-bulk` — buy 11+ tickets with custom numbers (batch)
- `megapot-contracts-reference` — full address table and complete ABI for all contracts
- `megapot-claim-winnings` — for claiming prizes after a drawing settles
