# Motoko compiler keeps failing with “unexpected token ‘;’ / ‘stable’ even after clean rebuild (dfx 0.30.2)

**URL:** https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883
**Category:** Motoko
**Created:** [February 6, 2026, 9:45am UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883 "2026-02-06T09:45:57Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![Arrey-Franky](https://avatars.discourse-cdn.com/v4/letter/a/eada6e/32.png) [@Arrey-Franky](https://forum.dfinity.org/u/Arrey-Franky)
#### Post date: [February 6, 2026, 9:45am UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/1 "2026-02-06T09:45:57Z")

</div>

Hi everyone,  
I’m stuck on a Motoko compile error that I’ve not been able to resolve after multiple clean rebuilds, and I’d appreciate guidance from someone more experienced with Motoko syntax and compiler behavior.

### Environment

- OS: Windows (WSL Ubuntu)

- dfx version: 0.30.2

- Network: local replica

- Project type: Motoko backend + JS/HTML frontend

- Command used:

```auto
dfx stop
dfx start --clean
dfx deploy

```

* * *

### The Error I Keep Getting

Every deploy fails with variations of this error:

```auto
syntax error [M0001], unexpected token ';'
syntax error [M0001], unexpected token 'stable'

```

Example:

```auto
/home/.../main.mo:15.3-15.9: syntax error, unexpected token 'stable'

```

or

```auto
/home/.../main.mo:22.59-22.60: syntax error, unexpected token ';'

```

* * *

### What I’ve Tried

- Removed semicolons from variable declarations

- Ran `dfx start --clean`

- Deleted `.dfx` directory

- Reduced the file to minimal examples (which _do_ compile)

- Re-added code gradually, but the error returns once stable variables or upgrade hooks are involved

* * *

### Current `main.mo` (backend)

```auto
import Debug "mo:base/Debug";
import Time "mo:base/Time";
import Principal "mo:base/Principal";
import HashMap "mo:base/HashMap";
import Iter "mo:base/Iter";
import List "mo:base/List";
import Array "mo:base/Array";

actor DBank {

  /* -----------------------------
     STABLE STORAGE
  -------------------------------- */
  stable var balancesStable : [(Principal, Int)] = []
  stable var timestampsStable : [(Principal, Int)] = []
  stable var platformEarningsStable : Int = 0
  stable var transactionLogsStable : [(Principal, Text)] = []

  /* -----------------------------
     TRANSIENT STORAGE
  -------------------------------- */
  transient var balances : HashMap.HashMap<Principal, Int>
  transient var lastUpdated : HashMap.HashMap<Principal, Int>
  transient var platformEarnings : Int
  transient var transactionLogs : [(Principal, Text)]

  /* -----------------------------
     CONFIG
  -------------------------------- */
  let INTEREST_BP : Int = 500;
  let PLATFORM_FEE_BP : Int = 1000;
  let BP_DIVISOR : Int = 10_000;
  let YEAR_NS : Int = 31_536_000_000_000_000;

  /* -----------------------------
     INIT
  -------------------------------- */
  {
    balances := HashMap.fromIter(
      balancesStable.vals(),
      10,
      Principal.equal,
      Principal.hash
    );

    lastUpdated := HashMap.fromIter(
      timestampsStable.vals(),
      10,
      Principal.equal,
      Principal.hash
    );

    platformEarnings := platformEarningsStable;
    transactionLogs := transactionLogsStable;
  }

  /* -----------------------------
     UPGRADE HOOKS
  -------------------------------- */
  system func preupgrade() {
    balancesStable := Iter.toArray(balances.entries());
    timestampsStable := Iter.toArray(lastUpdated.entries());
    platformEarningsStable := platformEarnings;
    transactionLogsStable := transactionLogs;
  };

  system func postupgrade() {
    balances := HashMap.fromIter(
      balancesStable.vals(),
      10,
      Principal.equal,
      Principal.hash
    );

    lastUpdated := HashMap.fromIter(
      timestampsStable.vals(),
      10,
      Principal.equal,
      Principal.hash
    );

    platformEarnings := platformEarningsStable;
    transactionLogs := transactionLogsStable;
  };

  /* -----------------------------
     INTEREST
  -------------------------------- */
  func applyInterest(user : Principal) {
    let now = Time.now();

    let lastTime = switch (lastUpdated.get(user)) {
      case (?t) t;
      case null now;
    };

    let elapsed = now - lastTime;
    if (elapsed <= 0) return;

    let balance = switch (balances.get(user)) {
      case (?b) b;
      case null 0;
    };

    let rawInterest =
      ((balance * INTEREST_BP * elapsed) / YEAR_NS) / BP_DIVISOR;

    if (rawInterest <= 0) {
      lastUpdated.put(user, now);
      return;
    };

    let fee = (rawInterest * PLATFORM_FEE_BP) / BP_DIVISOR;
    let userInterest = rawInterest - fee;

    balances.put(user, balance + userInterest);
    platformEarnings += fee;
    lastUpdated.put(user, now);
  };

  /* -----------------------------
     TOP UP
  -------------------------------- */
  public func topUp(user : Principal, amount : Int)
    : async { success : Bool; message : Text; balance : Int } {

    if (amount <= 0) {
      return {
        success = false,
        message = "Top-up amount must be greater than 0",
        balance = switch (balances.get(user)) {
          case (?b) b;
          case null 0;
        }
      };
    };

    applyInterest(user);

    let current = switch (balances.get(user)) {
      case (?b) b;
      case null 0;
    };

    let newBalance = current + amount;
    balances.put(user, newBalance);
    lastUpdated.put(user, Time.now());

    let log = "Top-up: $" # debug_show(amount);
    transactionLogs := Array.append(transactionLogs, [(user, log)]);
    Debug.print(log);

    return {
      success = true,
      message = "You have successfully topped up $" # debug_show(amount),
      balance = newBalance
    };
  };

  /* -----------------------------
     WITHDRAW
  -------------------------------- */
  public func withdraw(user : Principal, amount : Int)
    : async { success : Bool; message : Text; balance : Int } {

    if (amount <= 0) {
      return {
        success = false,
        message = "Withdrawal amount must be greater than 0",
        balance = switch (balances.get(user)) {
          case (?b) b;
          case null 0;
        }
      };
    };

    applyInterest(user);

    let current = switch (balances.get(user)) {
      case (?b) b;
      case null 0;
    };

    if (amount > current) {
      let log = "Withdrawal failed: insufficient funds";
      transactionLogs := Array.append(transactionLogs, [(user, log)]);
      Debug.print(log);

      return {
        success = false,
        message = "Insufficient balance",
        balance = current
      };
    };

    let newBalance = current - amount;
    balances.put(user, newBalance);
    lastUpdated.put(user, Time.now());

    let log = "Withdrawal: $" # debug_show(amount);
    transactionLogs := Array.append(transactionLogs, [(user, log)]);
    Debug.print(log);

    return {
      success = true,
      message = "You have successfully withdrawn $" # debug_show(amount),
      balance = newBalance
    };
  };

  /* -----------------------------
     READ
  -------------------------------- */
  public query func getBalance(user : Principal) : async Int {
    switch (balances.get(user)) {
      case (?b) b;
      case null 0;
    };
  };

  public query func getTransactionLogs(user : Principal) : async [Text] {
    transactionLogs
      |> List.fromArray
      |> List.filter(func (e) { e.0 == user })
      |> List.map(func (e) { e.1 })
      |> List.toArray;
  };
}

```

* * *

### My Questions

1. Are `stable` + `transient` variables inside the same actor still valid in Motoko 0.30.x?

2. Is the anonymous actor init block `{ ... }` still correct syntax?

3. Are there breaking syntax changes I’m missing compared to older Motoko examples?

4. Is there a recommended minimal pattern for `stable` + `HashMap` + upgrade hooks that definitely compiles today?

At this point I’m not trying to add features I just want a **correct, compiling Motoko baseline** that follows best practices.

Thanks in advance 🙏  
Any help or reference example would be massively appreciated.

---

<div class="post-metadata">

### Author: ![alex-df](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/alex-df/32/12155_2.png) [@alex-df](https://forum.dfinity.org/u/alex-df)
#### Post date: [February 6, 2026, 9:59am UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/2 "2026-02-06T09:59:13Z")

</div>

Hello, do I understand correctly that you had older code that used to compile but now it doesn’t?

What you could do is just keep old code and add the “–legacy-persistence” compiler flag; In your dfx.json file, you can add a field of this kind to the canister object:  
`"args": "--legacy-persistence"`

On the other hand, if you want to (re-)build your app from scratch, we can walk you through how to use the new enhanced orthogonal persistence feature and the `core` library instead of the old base.

hope this helps,  
Alex

---

<div class="post-metadata">

### Author: ![Arrey-Franky](https://avatars.discourse-cdn.com/v4/letter/a/eada6e/32.png) [@Arrey-Franky](https://forum.dfinity.org/u/Arrey-Franky)
#### Post date: [February 6, 2026, 11:22am UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/3 "2026-02-06T11:22:31Z")

</div>

Hi Alex, thanks for the explanation, that helps a lot.

Yes, that’s correct, the code used to compile before and I haven’t intentionally changed the persistence model. It sounds like the issue is coming from newer Motoko versions enforcing the enhanced orthogonal persistence rules.

For now, I’d prefer to keep the existing code working rather than rebuild everything from scratch. I’ll try adding the `--legacy-persistence` flag in `dfx.json` as you suggested.

Once I confirm that works, I’d be interested in understanding the recommended path for migrating to the new persistence model in the future.

Thanks again for the guidance.

---

<div class="post-metadata">

### Author: ![timo](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/timo/32/5543_2.png) [@timo](https://forum.dfinity.org/u/timo)
#### Post date: [February 6, 2026, 5:36pm UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/4 "2026-02-06T17:36:39Z")

</div>

First thing you have to do is:

```auto
persistent actor DBank {

```

The `persistence` keyword is mandatory now.

Then remove all the `stable` keywords. They are implicit because the actor is declared `persistent`.

Then put semicolons at the end of each line. They are missing currently.

Then your “transient storage” block has is missing the initial values. The lines starting with `transient var` need a `= <some value>;` at the end.

And as said in the previous post, at this stage you should right away switch to using `core`, not `base`.

---

<div class="post-metadata">

### Author: ![Arrey-Franky](https://avatars.discourse-cdn.com/v4/letter/a/eada6e/32.png) [@Arrey-Franky](https://forum.dfinity.org/u/Arrey-Franky)
#### Post date: [February 9, 2026, 8:32am UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/5 "2026-02-09T08:32:45Z")

</div>

Hello Timo/Alex

Thanks for the guidance so far. I’ve dug a bit deeper, and I think the compile errors I’m seeing (`unexpected token 'stable' / ';'`) are due to changes in **Motoko 0.30.x**. Here’s what I’ve learned:

1. **Persistent actors are now required for stable variables**

2. **Transient keyword is obsolete**

3. **Init block syntax changed slightly**

4. **Upgrade hooks remain valid** , but semicolons after function blocks cause the `unexpected token ';'` error.

I’ve tested a minimal baseline with `persistent actor`, `stable var`, `init`, and `preupgrade/postupgrade` hooks, and it compiles cleanly in 0.30.2.

I plan to refactor my full `DBank` actor with this pattern. If you want, I can share the updated full version so others using 0.30.x don’t hit the same issues.

---

<div class="post-metadata">

### Author: ![Kamirus](https://sea1.discourse-cdn.com/flex023/user_avatar/forum.dfinity.org/kamirus/32/24780_2.png) [@Kamirus](https://forum.dfinity.org/u/Kamirus)
#### Post date: [April 20, 2026, 12:03pm UTC](https://forum.dfinity.org/t/motoko-compiler-keeps-failing-with-unexpected-token-stable-even-after-clean-rebuild-dfx-0-30-2/63883/6 "2026-04-20T12:03:55Z")

</div>

Hey, since you plan a refactor you can have a look at the [Motoko writing skill](https://github.com/caffeinelabs/motoko/tree/master/.agents/skills/writing-motoko) to see how we currently use modern Motoko. We also use mops for managing dependencies but also building and typechecking, here is the [mops skill](https://github.com/caffeinelabs/mops/blob/main/.agents/skills/mops-cli/SKILL.md)

About your message:

1. Avoid using `stable` keyword
2. You can still use `transient` , these fields will be reset on an upgrade, they won’t persist, so you probably want to avoid `transient` fields
3. Your AI agent is hallucinating, `init { … }` is not valid motoko
4. I’d avoid these hooks and use the `migration` syntax, see [this migration skill](https://github.com/caffeinelabs/motoko/blob/master/.agents/skills/migrating-motoko/SKILL.md)
