Help Needed: Debugging Insufficient Balance Error in Ledger Transfer

Hello Dfinity Community,

I am reaching out for assistance with a problem I’ve encountered in my Motoko code while trying to implement a token transfer function on the Internet Computer. Despite having sufficient balance, I am consistently getting an ‘Insufficient Balance’ error when trying to perform a transfer.

Here’s a brief overview of what I’m trying to achieve:

  • I have a lockToken function, which is meant to transfer tokens from a user’s principal to a specified canister.
  • The function retrieves the user’s balance and compares it with the required amount for the transfer.
  • Despite the user’s balance being sufficient, the Ledger.transfer call within the transferICP function returns an ‘Insufficient Balance’ error.

Below is the relevant part of my code:
lockToken method:

  public shared ({ caller }) func lockToken(principal : Principal, amount : Ledger.Tokens, dealId : Nat) : async LockTokenResult {
    let canisterPrincipal = Principal.fromText("example canister");
    let depositAddressBlob = Account.accountIdentifier(canisterPrincipal, Principal.toBlob(caller));
    let depositAddress : [Nat8] = Blob.toArray(depositAddressBlob);
    Debug.print("Caller Principal: " # Principal.toText(principal));
    let debitBalance = await getBalance(principal);
    Debug.print("Debit Balance: " # Nat64.toText(debitBalance));
    Debug.print("Required Balance: " # Nat64.toText(amount.e8s));
    Debug.print("ICP Fee: " # Nat64.toText(icp_fee));

    let transferResult = await transferICP(principal, depositAddress, amount.e8s);

    switch (transferResult) {
      case (#Ok(_)) {
        let dealOpt = deals.get(dealId);
        switch (dealOpt) {
          case (null) {
            return #DealNotFound;
          };
          case (?deal) {
            let updatedDeal = {
              id = dealId;
              status = "Tokens Locked";
              name = deal.name;
              from = deal.from;
              to = deal.to;
              amount = deal.amount;
              picture = deal.picture;
              description = deal.description;
              dealCategory = deal.dealCategory;
              dealType = deal.dealType;
              paymentScheduleInfo = deal.paymentScheduleInfo;
              dealTimeline = deal.dealTimeline;
              deliverables = deal.deliverables;
              supportingDocuments = deal.supportingDocuments;
              createTime = deal.createTime;
              submissionTime = deal.submissionTime;
              buyerCancelRequest = deal.buyerCancelRequest;
              sellerCancelRequest = deal.sellerCancelRequest;
            };
            deals.put(dealId, updatedDeal);

            let activityLog = {
              dealId = dealId;
              description = "Tokens locked for the deal.";
              activityType = "Tokens Locked";
              amount = deal.amount;
              status = "Tokens Locked";
              activityTime = Time.now();
              user = principal;
              deal = updatedDeal;
            };
            await createActivityLog(activityLog, principal);

            return #TokenLocked;
          };
        };
      };
      case (#Err(e)) {
        return #TransferError(e);
      };
    };
  };

transferICP method:

 public func transferICP(transferFrom : Principal, transferTo : [Nat8], amount : Nat64) : async Ledger.TransferResult {
    let res = await Ledger.transfer({
            memo            = 1;
            amount          = { e8s = amount };
            fee             = { e8s = 10_000 };
            from_subaccount = null;
            to              = transferTo;
            created_at_time = null;
    });
    return res;
  };

I’ve used Debug.print to confirm that the user’s balance is indeed sufficient before the transfer call. Here’s what I’ve verified:

2024-01-29 11:46:28.290769 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] Debit Balance: 2100000000
2024-01-29 11:46:28.290769 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] Required Balance: 100000000
2024-01-29 11:46:28.290769 UTC: [Canister bd3sg-teaaa-aaaaa-qaaba-cai] ICP Fee: 10000

Despite this, the transfer fails with an ‘Insufficient Balance’ error. I am wondering if there might be a subtle issue or a common pitfall that I’m overlooking.

I would greatly appreciate any insights or suggestions you might have. Has anyone else faced a similar issue, or can anyone spot a mistake in my approach?

Thank you in advance for your time and help!

So when the canister call this Ledger.transfer endpoint in ICP Ledger canister, the actual caller becomes the Canister, and not the principal which is supposed to do the transfer.

So Insufficient balance error is because the caller (canister in this case) might not hodl any ICPs.

@h1teshtr1path1 Can you point me in the direction where instead of calling the canister, how to call the user’s principal instead?