Smart Contract Function Executes Successfully But Does Not Transfer Tokens

Hello, this is Luis from Digiko!

I am experiencing an issue with a smart contract function on Klever mainnet and need assistance understanding what might be wrong.

Background

I have deployed a DEX contract at address: klv1qqqqqqqqqqqqqpgq2jqc28xwmk82mng4kwpm3j9vkq3vyga8xw9qq85y6h

The contract includes the following functions, all of which work correctly:

  • swapKlvToDgko / swapDgkoToKlv (token swaps)
  • addLiquidity (owner adds liquidity to pool)
  • withdrawFees (owner claims accumulated trading fees)
  • View functions (getDgkoReserve, getKlvReserve, etc.)

The Problem

I added a withdrawBalance function to allow the contract owner to recover tokens that are sent to the contract. The function executes without errors (status: “success”, resultCode: “Ok”), but no tokens are transferred.

When I examine the transaction on Kleverscan, there are no Transfer receipts. By comparison, the withdrawFees function (which works correctly) shows clear Transfer receipts with tokens moving from the contract to the owner’s wallet.

Contract Implementation

#[only_owner]
#[endpoint(withdrawBalance)]
fn withdraw_balance(&self, dgko_amount: BigUint, klv_amount: BigUint) {
    let owner = self.blockchain().get_owner_address();
    
    if dgko_amount > 0u64 {
        let dgko_token = TokenIdentifier::from(DGKO_TOKEN);
        self.send().direct_kda(&owner, &dgko_token, 0, &dgko_amount);
    }
    
    if klv_amount > 0u64 {
        self.send().direct_klv(&owner, &klv_amount);
    }
}

Frontend Implementation

// Convert to raw values with precision
const dgkoRaw = Math.floor(dgkoAmount * 10000);  // DGKO has 4 decimals
const klvRaw = Math.floor(klvAmount * 1000000);  // KLV has 6 decimals

// Convert to hex and ensure even length
let dgkoHex = dgkoRaw.toString(16);
if (dgkoHex.length % 2 !== 0) dgkoHex = '0' + dgkoHex;

let klvHex = klvRaw.toString(16);
if (klvHex.length % 2 !== 0) klvHex = '0' + klvHex;

// Encode as base64
const args = [
  Buffer.from(dgkoHex, 'hex').toString('base64'),
  Buffer.from(klvHex, 'hex').toString('base64')
];

const metadata = Buffer.from('withdrawBalance').toString('base64');

// Build and broadcast transaction
await kleverWeb.buildTransaction(
  [{ type: 63, payload: { scType: 0, address: CONTRACT_ADDRESS, callValue: {} } }],
  [metadata, ...args]
);

Example Transaction

Transaction hash: 09bf427806e5ccf1b9ef7deec3789c819c5d2fa5533f11500cad7b1aecd5b48c

Transaction data shows:

  • Function name: withdrawBalance (encoded correctly)
  • First argument: 47868c00 (represents 1,200,000,000 raw units)
  • Second argument: 059682f000 (represents 24,000,000,000 raw units)

Result: Transaction succeeds, but no Transfer receipts appear.

Attempts to Resolve

  1. Initially used OptionalValue parameters with #[allow_multiple_var_args] - received “wrong number of arguments” error
  2. Changed to regular BigUint parameters (current implementation) - transaction executes but no transfer occurs
  3. Tested various encoding methods (with 0x01 prefix, with padding, plain hex)
  4. Rebuilt contract from meta folder using cargo run build
  5. Upgraded contract on mainnet multiple times
  6. Tested with different amounts (full balance, small amounts, zero values)

All attempts result in the same behavior: successful transaction execution with no token transfers.

Questions

  1. Is there a specific encoding requirement for multiple BigUint parameters that differs from single parameters?

  2. The withdrawFees function uses the same self.send().direct_kda() and self.send().direct_klv() methods successfully. What could cause these methods to work in one function but not another?

  3. Is there a way to verify whether the conditional statements (if dgko_amount > 0u64) are executing inside the contract?

  4. Are there existing Klever smart contract examples that accept multiple numeric parameters that I can reference?

Documentation Reviewed

I have reviewed the following documentation:

  • Klever Smart Contracts documentation (serialization, multi-values, encoding)
  • Klever Node API specification
  • Klever Proxy API specification
  • MultiversX serialization format documentation
  • klever-sc framework v0.45.0 source code
  • Crowdfunding example contracts (both KDA and KLV versions)

If there is additional documentation that covers multiple BigUint parameters or debugging contract execution, please let me know.

Thank you for any assistance you can provide.

UPDATE:

After extensive testing, I discovered the correct encoding format for Klever smart contract function calls, but asking for feedback.

The Solution: MultiversX @ Separator Format

The key was using the MultiversX standard encoding format with @ separators:

// ❌ WRONG - What I was doing (separate arguments)
const metadata = [
  Buffer.from('functionName').toString('base64'),
  Buffer.from(argValue).toString('base64')
];

// âś… CORRECT - MultiversX @ format
const argHex = argValue.toString(16);  // Plain hex, no 0x prefix
const scCall = `functionName@${argHex}`;  // Combine with @ separator
const scCallEncoded = Buffer.from(scCall).toString('base64');  // Encode once
const metadata = [scCallEncoded];  // Single element

Working Example: Withdrawal Function

Here’s a complete working example that successfully withdraws DGKO tokens:

const handleWithdraw = async (amount) => {
  // 1. Calculate raw amount (DGKO has 4 decimals)
  const DGKO_PRECISION = 10_000;
  const rawAmount = BigInt(Math.floor(amount * DGKO_PRECISION));
  
  // 2. Convert to plain hex (no 0x prefix)
  const amountHex = rawAmount.toString(16);
  
  // 3. Build function call with @ separator
  const scCall = `withdrawDgkoBalance@${amountHex}`;
  
  // 4. Base64 encode the entire string
  const scCallEncoded = Buffer.from(scCall).toString('base64');
  
  // 5. Build transaction
  const unsignedTx = await window.kleverWeb.buildTransaction(
    [{
      type: 63,
      receiver: DEX_CONTRACT_ADDRESS,
      kda: "KLV"
    }],
    [scCallEncoded]  // Single metadata element
  );
  
  // 6. Sign and broadcast
  const signedTx = await window.kleverWeb.signTransaction(unsignedTx);
  const response = await window.kleverWeb.broadcastTransactions([signedTx]);
  
  return response;
};

Verified Transactions

This approach is working in production:

  • Transaction: 922ecde979372919220434e200f25d305a0c34a7e544fd07b2974a5ad2866b05
  • Data (decoded): withdrawDgkoBalance@47868c00
  • Amount: 1,200,000,000 raw units = 120,000 DGKO (4 decimals)
  • Status: :white_check_mark: Success

You can verify on KleverScan.

Key Points

  1. Arguments as plain hex - Use toString(16), NO 0x prefix
  2. @ separator - Combine function name and arguments: functionName@arg1@arg2
  3. Single base64 encoding - Encode the ENTIRE combined string once
  4. Single metadata element - Pass as one element in array

My Questions

Is this the correct/recommended approach for Klever smart contract calls?

I arrived at this solution after studying the Klever Go SDK examples and MultiversX documentation. It works, but I want to make sure:

  1. Is this the standard way to call smart contract functions in Klever?
  2. Am I overcomplicating something that has a simpler method?
  3. Is there official documentation I missed that explains this encoding format?
  4. Are there any edge cases or gotchas I should be aware of?

Thanks for your help!

Hi @luistorres welcome to the Klever FĂłrum.

The @Blockchain team is checking it and circle back to you soon.

Hello @luistorres!

The way the message is encoded in that example is correct.

For reference, we have a frontend integration example in the docs:

Since you mentioned you’re using the Crowdfunding contract as an example, there’s also a simple dApp that interacts with that smart contract:

Regarding the encoding itself, you can use the abiEncoder as shown in the documentation:

const txData: string = Buffer.from(
  'donate' + '@' + abiEncoder.encodeABIValue(crowdfunding.id, 'String', false),
  'utf8',
).toString('base64')

Thanks Klever Team for the quick response. I’m glad to hear the method is correct, your reference was extremely helpful.

I can now consider this topic 100% solved!

1 Like