#A balance column is a denormalisation

The moment you store users.coin_balance, you own a consistency problem: every award and every spend must update it atomically, forever, in every code path anyone adds later.

#Derive it instead

public function coinBalance(): int
{
    return (int) $this->coinTransactions()->sum('amount');
}

The ledger is the only source of truth, and every coin traces back to the event that created it.

#Make the rule structural

A convention that "nobody updates this table" survives exactly as long as the person who wrote it stays on the project. Enforce it in the model:

static::updating(function () {
    throw new RuntimeException('coin_transactions is append-only.');
});

Corrections become compensating entries, which is what an auditor wants anyway.