Klever SC invoke via Node SDK / Troubleshooting + Fix

Hey @Duka

I’m currently building a Node.js backend to interact with a smart contract using @klever/sdk-node. I’m following your official documentation and explicitly setting the node and API endpoints to Testnet like your code above so:

import { Account, utils, TransactionType } from '@klever/sdk-node';

utils.setProviders({
  node: 'https://node.testnet.klever.org',
  api: 'https://api.testnet.klever.org',
});

However, when the server runs, I still get the following error:

❌ Error: fetch failed
  ...
  cause: Error: getaddrinfo ENOTFOUND node.mainnet.klever.finance

This indicates that the SDK is still trying to connect to the outdated node.mainnet.klever.finance endpoint… even though I never set this in my code or .env file.

What I’ve already confirmed:

  • My .env variables point to Testnet (.org domain, not .finance).
  • I’m explicitly calling utils.setProviders(...) before initializing Account or making any transaction.
  • I’m using @klever/sdk-node@2.4.1 (latest).

Is utils.setProviders() currently being ignored or overwritten internally by the SDK?
How can I ensure that the correct endpoints (e.g., https://node.testnet.klever.org) are being used for signing and broadcasting?

Thanks for any insight! :folded_hands:

EDIT:

node_modules/@klever/sdk-node/dist/cjs/index.js

Here is the domain is hardcoded in multiple places.

Thanks @Maik for splitting the thread. :sweat_smile: :raising_hands:

So, after changing the hardcoded domain, I can now carry out normal transfers via the SDK without any issues. However, triggering a Smart Contract still fails.

Here’s what I’ve tried:

import 'dotenv/config';
import { Account, utils, TransactionType } from '@klever/sdk-node';

utils.setProviders({
  api: 'https://api.testnet.klever.org',
  node: 'https://node.testnet.klever.org'
});

const account = new Account(process.env.PRIVATE_KEY);
await account.ready;

const tx = await account.buildTransaction([
  {
    type: TransactionType.SCInvoke,
    address: process.env.CONTRACT_ADDRESS,
    payload: {
      function: 'add_referral',
      args: ['klv1referrer...', 'klv1referred...', '1', 'free']
    }
  }
]);

const result = await account.broadcast(tx, { awaitReceipt: true });
console.log('TX Hash:', result.txHash);

Observed error:
validation error: invalid receiver address

Normal transfers (TransactionType.Transfer) work fine.
The contract is deployed and callable via the Klever IDE.

Questions:
Is it even possible to invoke a smart contract via the Node SDK?
Am I using the correct approach with TransactionType.SCInvoke?
If not, is there another method or workaround to trigger SC functions from a backend?

Any guidance or examples would be greatly appreciated!

1 Like

Hello @Andreas_Hennersdorf How have you been, my friend? I’m asking to the @Blockchain ninjas to bring exemples to you.

Hey hey! :grinning_face_with_smiling_eyes:
I’m doing great, hope you are too! :rocket:
Thanks a lot for checking it
I’m really curious to see where my thinking went off track.
It’s probably the classic case of “problem exists between keyboard and chair” :sweat_smile:
Looking forward to the wisdom of the Blockchain Ninjas! :ninja:

2 Likes

Hi @Andreas_Hennersdorf,

Yes, it’s possible to send a Smart Contract (SC) transaction via the Node SDK. Let me show you how.

First, here’s the correct approach to send a Smart Contract invoke transaction:

const unsignedTx = await account.buildTransaction(
  [
    {
      payload: {
        scType: InvokeType,
        address: contractAddress,
      },
      type: SmartContractType,
    },
  ],
  [metadata],
  {
    nonce: account.getNonce(),
  }
);

The metadata variable is where you specify the function to be called and its encoded arguments, following this format:

function_name@param1@param2@param3@…@lastparam

If you check any smart contract transaction, you’ll see the metadata structured in this way:

withdraw@0000000000000006

The arguments must be encoded according to their types defined in the contract’s ABI.

Example ABI:

(...)
{
  "name": "change_expiration_date",
  "onlyOwner": true,
  "mutability": "mutable",
  "inputs": [
    {
      "name": "expiration_date",
      "type": "u64"
    }
  ],
  "outputs": []
}
(...)

Encoding the data:

import { abiEncoder } from '@klever/sdk-node';
(...)

const expirationDateEncoded = abiEncoder.encodeABIValue(expirationDate, 'u64');

const metadata = ["change_expiration_date", expirationDateEncoded].join('@');

Final Result is

change_expiration_date@0000000067fe6684

A more complete version of the examples I used can be found here:

Smart Contract: GitHub - klever-io/klever-certification-sc
JS-SDK: GitHub - klever-io/klever-certificate-issuer

1 Like

Great, thanks for the quick reply. I’ll try it out in a few minutes.

2 Likes

Thanks again for the detailed explanation!

I tried following your example closely, using the exact metadata format (even directly copying working examples from the IDE). However, I’m still running into this error:

node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
                                ^
validation error: illegal base64 data at input byte 3
Thrown at:
    at loadESM (node:internal/process/esm_loader:40:33)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Would appreciate any hint on what else might be wrong with the transaction format or encoding.
Thanks in advance!

Best,
Andreas

Hi @Andreas_Hennersdorf,

Sorry for the late response. I believe this error occurs because the resulting metadata value needs to be converted to Base64.

To make things easier, I created a contract with a function based on your previous parameters (['klv1referrer...', 'klv1referred...', '1', 'free']) and prepared an example of how to send this transaction:

const functionName = "test";

const encodeAddress = abiEncoder.encodeAddress(
  "klv1fpwjz234gy8aaae3gx0e8q9f52vymzzn3z5q0s5h60pvktzx0n0qwvtux5"
);
console.log("Encoded Address:", encodeAddress);

const encodedBigUint = abiEncoder.encodeABIValue(1, "BigUint");
console.log("Encoded BigUint:", encodedBigUint);

const encodedUint = abiEncoder.encodeABIValue(1, "u64");
console.log("Encoded u64:", encodedUint);

const encodedString = abiEncoder.encodeABIValue("free", "String");
console.log("Encoded String:", encodedString);

const data = [functionName, encodeAddress, encodedBigUint, encodedUint, encodedString].join("@");
console.log("Metadata:", data);

const metadata = Buffer.from(data).toString("base64"); // converts to base64
console.log("Metadata Base64:", metadata);

await account.ready;

const unsignedTx = await account.buildTransaction(
  [
    {
      payload: {
        scType: 0, // 0 - Invoke
        address: "klv1qqqqqqqqqqqqqpgqtm5k2r8t8ewm5p29jjpaj63w7cjs5cn50n0q684ecp", // Contract address
      },
      type: TransactionType.SmartContract, // 63
    },
  ],
  [metadata],
  {
    nonce: account.getNonce(),
  }
);

You can also check a real transaction example here.

The ABI for this example contract is:

[
  {
    "name": "address",
    "type": "Address"
  },
  {
    "name": "big",
    "type": "BigUint"
  },
  {
    "name": "uint",
    "type": "u64"
  },
  {
    "name": "string",
    "type": "bytes"
  }
]
3 Likes

Thanks for your help. I’ll test it right away and get back to you. Thank you very much.

Tested and approved!
Thank you very much, works without any problems. :raising_hands:

2 Likes