# Economy and coins

> Build a coin system with /daily, /balance, /give and a shop that sells roles.

You will build a small economy:

| Command | What it does |
|---|---|
| `/daily` | Gives 100 coins, once every 24 hours |
| `/balance` | Shows how many coins someone has |
| `/give` | Sends coins to another member |
| `/shop` | Lists what you can buy |
| `/buy` | Buys a role with coins |

Works on every plan.

## What you need

- About 15 minutes.
- For the shop: the roles you want to sell, and the bot's role **above** them in **Server Settings → Roles**.

Coins are saved in a **Mongo database** called `economy`, with one row per member. It is made the first time someone gets coins. See [Saving data](https://ccbot.app/docs/concepts/saving-data).

> **Note:** Every command below goes in the **Pre-hook** box of a slash command. Leave the **Response** box empty. `set_content` writes the reply.

## 1. /daily

Open **Create → Slash Command**. Name it `daily`, description `Claim your daily coins`. Pre-hook:

```python
cooldown("24 hours", error_message="You already claimed today. Come back %relative%.")

bank = SlashMongo("economy")
uid = str(user.id)
await bank.update_one({"_id": uid}, {"$inc": {"coins": 100}}, upsert=True)
doc = await bank.find_one({"_id": uid})

set_content(f"You got **100** coins. You now have **{intcomma(doc['coins'])}**.")
```

- `cooldown` stops the command for 24 hours per member. The wait is saved, so a bot restart does not reset it.
- `%relative%` becomes "in 5 hours", and Discord counts it down live.
- `$inc` adds to the number. `upsert=True` makes the row for a new member.

## 2. /balance

Name it `balance`. Add a **Member** argument named `member`, with **Required** off. Pre-hook:

```python
target = member or user
doc = await SlashMongo("economy").find_one({"_id": str(target.id)})
coins = doc["coins"] if doc else 0

set_content(f"**{target.display_name}** has **{intcomma(coins)}** coins.")
```

## 3. /give

Name it `give`. Add two arguments:

| Type | Name | Required |
|---|---|---|
| Member | `member` | On |
| Integer | `amount` | On |

Set the **Minimum** of `amount` to `1`. Pre-hook:

```python
if member.id == user.id or member.is_bot:
    set_error("Pick another member.")

bank = SlashMongo("economy")

# Take the coins only if the giver has enough. One step, so two
# /give at the same time cannot spend the same coins twice.
taken = await bank.update_one(
    {"_id": str(user.id), "coins": {"$gte": amount}},
    {"$inc": {"coins": -amount}},
)
if taken.modified_count == 0:
    set_error("You do not have enough coins.")

await bank.update_one(
    {"_id": str(member.id)},
    {"$inc": {"coins": amount}},
    upsert=True,
)

set_content(f"{user.mention} gave **{intcomma(amount)}** coins to {member.mention}.")
```

`set_error` stops the command and shows the text to the user. Nothing after it runs.

## 4. /shop

Name it `shop`. Pre-hook:

```python
SHOP = {
    "vip": {"price": 5000, "role": "VIP"},
    "red": {"price": 1000, "role": "Red"},
}

lines = []
for key, item in SHOP.items():
    lines.append(f"`{key}` — **{item['role']}** role, {intcomma(item['price'])} coins")

set_embed(Message(
    title="Shop",
    description="\n".join(lines) + "\n\nBuy with `/buy`.",
    color="#F1C40F",
))
```

Change `SHOP` to your own items. `role` is the exact role name in your server.

## 5. /buy

Name it `buy`. Add a **Text** argument named `item`, **Required** on. Under **Choices**, keep **Fixed list** and add one value per item: `vip`, `red`. Members then pick from a list instead of typing.

Pre-hook:

```python
SHOP = {
    "vip": {"price": 5000, "role": "VIP"},
    "red": {"price": 1000, "role": "Red"},
}

choice = SHOP.get(item)
if choice is None:
    set_error("That item is not in the shop.")
if has_role(user, choice["role"]):
    set_error("You already have that role.")

bank = SlashMongo("economy")
uid = str(user.id)
paid = await bank.update_one(
    {"_id": uid, "coins": {"$gte": choice["price"]}},
    {"$inc": {"coins": -choice["price"]}},
)
if paid.modified_count == 0:
    set_error(f"You need {intcomma(choice['price'])} coins.")

try:
    add_roles(user, choice["role"])
except Exception:
    # Give the coins back if the role could not be given.
    await bank.update_one({"_id": uid}, {"$inc": {"coins": choice["price"]}})
    set_error("Could not give the role. Your coins were returned.")

set_content(f"You bought the **{choice['role']}** role!")
```

> **Tip:** Keep the `SHOP` list the same in `/shop` and `/buy`. When you add an item, change both, and add its value to the `item` choices.

## 6. Test it

1. Run `/daily`. You get 100 coins.
2. Run `/daily` again. It says when you can claim next.
3. Run `/give` with a friend and `50`. Check both with `/balance`.
4. Lower a price to `50` and try `/buy`.

Errors from your code show in **Logs → Error Logs** on the dashboard.

## Ideas

- **Coins for chatting.** Use the Message event from [Levels and XP](https://ccbot.app/docs/guides/levels) and `$inc` coins instead of XP. Needs premium.
- **Rich list.** Copy `/leaderboard` from the levels guide and sort by `coins`.
- **Gambling.** A `/coinflip` that bets coins. Start from the [coin flip example](https://ccbot.app/docs/examples/coinflip).
- **Staff tools.** A `/addcoins` command for admins. Set its Discord permissions to **Manage Server** so only staff see it.
