Icrc1_transfer from canister always returns InsufficientFunds: ledger_balance=0 despite correct balance — both ICP and ckUSDC ledgers

Hi everyone,

I’m building an autonomous trading bot canister on ICP with Caffeine.AI and have been stuck on a persistent InsufficientFunds: ledger_balance=0 error when calling icrc1_transfer from within the canister. I’ve exhausted all code-level fixes and would appreciate community insight.

Canister ID: pbj3p-byaaa-aaaas-qadna-cai

The Problem: icrc1_balance_of({ owner = pbj3p-..., subaccount = null }) always returns the correct balance (2,250,000 for ckUSDC, 100,000,000 for ICP). But any icrc1_transfer call from the canister fails immediately with InsufficientFunds: ledger_balance=0.

What I’ve ruled out (with proof):

  • :cross_mark: Wrong subaccount — SHA224-computed Account ID matches IC Explorer To-address exactly

  • :cross_mark: Caller-derived subaccount problem — tested with Timer.setTimer, caller_context=NONE(timer) confirmed

  • :cross_mark: Cycles starvation — cycles=1,116,788,978,000 (~1.1T), more than sufficient

  • :cross_mark: Canister frozen — icrc1_name() query call to the ledger succeeds fine

  • :cross_mark: ckUSDC-specific issue — same error on the ICP ledger (ryjl3-...)

  • :cross_mark: amount < fee — tested with 50,000 base units (5x the fee)

  • :cross_mark: Pending/notify state — account_balance (legacy) and icrc1_balance_of both return identical values

  • :cross_mark: Pre-check await context switch — tested with transfer as the very first and only await

  • :cross_mark: Wrong Actor definition — using standard Motoko ICRC-1 actor interface

Timer test result (key evidence):

Timer fired | BACKEND_PRINCIPAL=pbj3p-byaaa-aaaas-qadna-cai
caller_context=NONE(timer)
balance_before=100000000
TRANSFER FAILED | error=InsufficientFunds: ledger_balance=0
amount=50000 | fee=10000

Minimal Motoko code that fails:

let ledger = actor("ryjl3-tyaaa-aaaaa-aaaba-cai") : actor {
  icrc1_transfer : (TransferArg) -> async TransferResult;
};
let result = await ledger.icrc1_transfer({
  from_subaccount = null;
  to = { owner = ownerPrincipal; subaccount = null };
  amount = 50000;
  fee = null;
  memo = null;
  created_at_time = null;
});
// Returns: Err(#InsufficientFunds { balance = 0 })

The canister has 1 ICP on { owner = pbj3p-..., subaccount = null } confirmed by both legacy and ICRC-1 balance APIs. The transfer fails regardless of context (public shared func, timer callback, with or without pre-check).

Has anyone seen this before? Is there something about how a canister must be initialized or configured before it can send outgoing ledger transfers?

Any help appreciated.

If you’re using Caffeine, could it be that the canister ID pbj3p-byaaa-aaaas-qadna-cai is the frontend canister, but it is the backend canister (that may not have any funds) that is making the call to the ledger?

It is an asset canister.

Hello Mathias,

I’m pretty sure that pbj3p-byaaa-aaaas-qadna-caiis the backend canister, since I’ve been tinkering with it for a long time. I’ve currently used up 150 credits to move the transfer from the frontend to the backend so that something like an autonomous bot can work at all. Unfortunately, Caffeine V3 has now also reached its limit. That’s why I’m posting here.

I forwarded your question to Caffeine again, and received the following response:

The canister ID pbj3p-byaaa-aaaas-qadna-cai is the backend canister, not the frontend. Here is the evidence:

  • debugSelfPrincipal() returns pbj3p-byaaa-aaaas-qadna-cai from within the Motoko code — frontend canisters don’t expose Motoko functions like this

  • icrc1_balance_of({ owner = pbj3p-..., subaccount = null }) returns 2,250,000 base units of ckUSDC and 100,000,000 e8s of ICP — funds that were explicitly sent to this principal

  • IC Explorer confirms incoming transfers to pbj3p-byaaa-aaaas-qadna-cai with Source Canister = ryjl3-tyaaa-aaaaa-aaaba-cai (the ICP ledger) and xevnm-gaaaa-aaaar-qafnq-cai (the ckUSDC ledger)

  • The canister exposes backend functions like executeBuyICP, debugTimerCallerTest, icrc1_balance_of queries — these are all Motoko backend methods, not frontend asset serving

So the hypothesis — that pbj3p-... is the frontend canister while a separate, unfunded backend is making ledger calls — does not match the evidence. The funds are confirmed on pbj3p-..., and all Motoko logic (including the failing icrc1_transfer calls) runs on that same canister.

However, the question does point to something worth double-checking: it would be useful to explicitly confirm the frontend canister ID and verify that no logic accidentally runs there. On Caffeine, the frontend canister only serves static assets and should never be making inter-canister calls to ledgers.

The core issue remains: icrc1_transfer from pbj3p-... (the confirmed backend canister, with confirmed funds, confirmed cycles, confirmed timer context without user-caller) still returns InsufficientFunds: ledger_balance=0. That is the open and unresolved question.

{
“backend_host”: “https://icp-api.io”,
“backend_canister_id”: ptpmw-niaaa-aaaas-qadoa-cai”,
“project_id”: “019d4202-8cbb-719f-8b4c-f64265502664”,
“ii_derivation_origin”: “undefined”
}

source https://pbj3p-byaaa-aaaas-qadna-cai.icp0.io/env.json

How is it possible that a random guy in a helmet provided the only useful answer?

Its a crazy world huh

Helmet guy provided a proof the other answers were correct

In a high level technical forum, engineers often treat a problem like a math proof.

once they’ve identified the logical error “You are calling from the frontend” they consider the case closed. To them, the “how to” is just implementation detail that they assume any developer should know.

Identifying that it’s the frontend doesn’t help cog138 if he doesn’t know where the actual backend went or how to point his environment to it.

This is one of the many reasons there are very few humans using this product.
1 Like

My guess - user thinks: I’ve moved the transfer call from the frontend (js) to the backend (motoko). Takes the first id they can find - it’s the asset canister id.

But the asset canister hosts your frontend, it’s a static file server. It’s also ‘backend’, so you have two backend canisters, one serves files, the other is your custom motoko api.

The truth, is much worse than that.

The truth is, the troubleshooting and diagnostic responses from within the tool have zero knowledge of how the deployment system works. Unless a user has the expertise to dig through developer tools themself, they won’t be able to figure out the “how.”

The system is opaque, by design.

2 Likes

Yes, that’s my take on the whole thing as well. I’ve often asked myself why the system knows so little about fundamental things and has to do so much research just to try out all sorts of things afterward.

Well, it was definitely an interesting experiment for me. It could have worked. I would have liked to have had a small prototype to get a good kickstart into ICP development and then study the code.

But I think I’ll stick with my game in Unity DOTS and C# with Rider. That three-day of vibe coding with Caffeine was probably just procrastination anyway, putting off my game, which I’ve been working on for a few years now. The whole vibe coding thing isn’t for me anyway—it’s very tiring and kills any joy in development.

This is going to sound crazy to a lot of you.

Perhaps user experience should come first.

Then maybe there would be more users who pay.

Instead of “users” who make number go up.

Well, Caffeine confidently and convincingly assured me that this is how it’s supposed to work—a wallet in the canister that allows the canister to perform swaps on ICPSwap autonomously. That makes sense to me. Or does it not?
And believe me, I definitely didn’t just grab the first ID that came to mind; I’ve now iterated on the problem 94 times with Caffeine. And now Caffeine itself has reached a dead end and assumes there’s a fundamental ledger issue or broken canister state, so it suggested “escalating” it here in the forum to get some help - which I did ;D

Well, screw it—it’s just playing around anyway.

its not your fault its the cofee

1 Like

Do you have github repo at caffeinepub. We could try to get it to work somehow.
When i tested caffeine and built mixer, i had to get canister backend to be actor that can hold and transfer funds when called, most likely would work with timers as well. Lets say you get this working, next step is to get it working with ICPSwap, what i did try (just swaps, but failed).

I think for this to be built, CaffeineAI team has to teach AI to implement them correctly.

T shirt worthy. Make this and make money

make website no shirt