Getting 'undefined' as a return value when invoking a getter smartcontract function
To demonstrate the issue, I built a small full-stack dApp called "Anonymous Feedback dApp" using ReactJS, TailwindCSS, Stellar-SDK, Soroban-SDK, and Freighter Wallet.
GitHub : https://github.com/bhupendra-chouhan/Anonymous-Feedback-Soroban
Please refer to the project's README file for a detailed explanation of the issue, along with the installation/setup guide and steps to reproduce it (screenshots included).
Issue:
In the code below, there is a function named contractInt(), which is used to invoke contract functions. While it successfully stores data on-chain when invoking a setter function, the problem occurs when I try to retrieve a return value using the returnValue() method from a getter function. Instead of the expected output, I receive 'undefined'.
/* Soroban.js */
import {
Contract,
SorobanRpc,
TransactionBuilder,
Networks,
BASE_FEE,
nativeToScVal,
} from "@stellar/stellar-sdk";
import { userSignTransaction } from "./Freighter";
let rpcUrl = "https://soroban-testnet.stellar.org";
let contractAddress =
"CDAN4KQKD633XF6MCOHI7Q3DJQX4E7ENCGKUBHGQKIKJWI6DVDPX54XW";
// coverting String to ScVal form
const stringToScValString = (value) => {
return nativeToScVal(value); // XDR format conversion
};
const numberToU64 = (value) => {
return nativeToScVal(value, { type: "u64" });
};
let params = {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
};
// Transaction Builder Function:
async function contractInt(caller, functName, values) {
const server = new SorobanRpc.Server(rpcUrl, { allowHttp: true });
const sourceAccount = await server.getAccount(caller);
const contract = new Contract(contractAddress);
let builtTransaction;
if (values == null) {
builtTransaction = new TransactionBuilder(sourceAccount, params)
.addOperation(contract.call(functName))
.setTimeout(30)
.build();
} else if (Array.isArray(values)) {
builtTransaction = new TransactionBuilder(sourceAccount, params)
.addOperation(contract.call(functName, ...values))
.setTimeout(30)
.build();
} else {
builtTransaction = new TransactionBuilder(sourceAccount, params)
.addOperation(contract.call(functName, values))
.setTimeout(30)
.build();
}
let _buildTx = await server.prepareTransaction(builtTransaction);
let prepareTx = _buildTx.toXDR(); // pre-encoding (converting it to XDR format)
let signedTx = await userSignTransaction(prepareTx, "TESTNET", caller);
let tx = TransactionBuilder.fromXDR(signedTx, Networks.TESTNET);
try {
let sendResponse = await server.sendTransaction(tx).catch(function (err) {
console.error("Catch-1", err);
return err;
});
if (sendResponse.errorResult) {
throw new Error("Unable to submit transaction");
}
if (sendResponse.status === "PENDING") {
let getResponse = await server.getTransaction(sendResponse.hash);
// we will continously checking the transaction status until it gets successfull added to the blockchain ledger or it gets rejected
while (getResponse.status === "NOT_FOUND") {
getResponse = await server.getTransaction(sendResponse.hash);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
console.log(`getTransaction response: ${JSON.stringify(getResponse)}`);
if (getResponse.status === "SUCCESS") {
// Make sure the transaction's resultMetaXDR is not empty
if (!getResponse.resultMetaXdr) {
throw "Empty resultMetaXDR in getTransaction response";
}
// Find the return value from the contract and return it
let transactionMeta = getResponse.resultMetaXdr;
let returnValue = transactionMeta.v3().sorobanMeta().returnValue();
console.log(`Transaction result: ${returnValue.value()}`);
} else {
throw `Transaction failed: ${getResponse.resultXdr}`;
}
} else {
throw sendResponse.errorResultXdr;
}
} catch (err) {
// Catch and report any errors we've thrown
console.log("Sending transaction failed");
console.log(JSON.stringify(err));
}
}
// Interaction Functions: Built To interact with it's respective smart contract functions:
async function sendFeedback(caller, fbData) {
let value = stringToScValString(fbData); //XDR format let result;
try {
let result = await contractInt(caller, "send_feedback", value);
console.log("Your Feedback ID is: ", result); // ⚠️ 'result' should be an object, but getting 'undefined'
} catch (error) {
console.log("Unable to create Feedback!!, ", error);
}
// Converting to regular Number type:
// let fbId = Number(result?._value?._attributes?.val?._value)
// return fbId;
}
async function fetchFeedback(caller, fb_id) {
let value = numberToU64(fb_id);
let result;
try {
result = await contractInt(caller, "fetch_feedback", value);
console.log(`Fetched Feedback for the feedback-Id ${fb_id} is : ${result}`); // ⚠️ 'result' should be an object, but getting 'undefined'
} catch (error) {
console.log("Unable to fetch Feedback!!, ", error);
}
// Converting to regular string type:
// let feedback = result?._value?._attributes?.val?._value?.toString();
// return feedback;
}
export {
sendFeedback,
fetchFeedback
};
Screenshots of Issue (You can reproduce the issue by following the screenshots):
-
Creating a Feedback and Storing it onchain by invoking the
send_feedback()smartcontract function:Result:
- Expected output:
4 - Output got:
Undefined
- Expected output:
-
Fetching a feedback with feedback-id
4by invoking thefetch_feedback()smartcontract function:Result:
- Expected output:
Feedback Number 4 - Output got:
Undefined
- Expected output:
There's a few things I'd suggest here:
- Your transaction is failing, so there won't be a
returnValue. You should look for errors during simulation or at the diagnostic events within theresultMetaXdr(these are withinresultMetaXdr.v3().sorobanMeta()). - Your
contractIntfunction doesn't return anything in the success case, so it'll beundefinedregardless. - You can use
scValToNativeto convert the return value to something readable instead of digging into the._attributesand whatnot.
Thank you for your response and suggestions, @Shaptic .
Replying to each of the points you mentioned:
- No matter which smart contract function I invoke from the front end, whenever I click the "Sign" button of the Freighter Wallet, the transaction is successfully signed and sent to the blockchain ledger without failing. I verify whether transactions are successful by searching for the smart contract address in the Stellar block explorer (i.e., stellar.expert).
This is the newly deployed smart contract address: CBG7QFA5CWUIJ6QQQSCWS33UNV6TN3EVQHRZLR5VYJWT5X73J6Y46U7A.
You can see all the function calls and successful transactions that have occurred under this deployed smart contract address by visiting this page: https://stellar.expert/explorer/testnet/contract/CBG7QFA5CWUIJ6QQQSCWS33UNV6TN3EVQHRZLR5VYJWT5X73J6Y46U7A
- The
contractIntfunction doesn't return anything right now because I was first trying to print the return value in the browser console. - I will definitely start using
scValToNativefrom now onwards.
I see, thanks for the clarifications.
The contractInt function doesn't return anything right now because I was first trying to print the return value in the browser console.
But the bug report is about you getting undefined as a return value, and you would get that as the return value even if there was success! :laughing:
The reason why I said your transaction failed is because of the console output in your second screenshot (Sending transaction failed). Based on the code, this occurs in your main block of submission code, but it's unclear why its occurring. Keep in mind that catch statement will catch all sorts of other errors. I would recommend running the code without that try/catch block to see what the real error is.
For example, you use the Networks.TESTNET variable in one place and the string "TESTNET" in your invocation of userSignTransaction which may be causing issues. But that's just an example. It could be anything in that block and you just call it "failed to send." Based on what you said, it seems like the transaction actually does successfully send, but you're triggering the catch elsewhere which causes no return value.
I am applying to this issue via OnlyDust platform.
My background and how it can be leveraged
I'm a frontend and smart contract developer. I've contributed to Projects here on onlydust and with that experience, I would handle this task as expected. This would also be my first time contributing to this project.
How I plan on tackling this issue
To implement this,
-
I will create a Feedback and Storing it onchain by invoking the send_feedback() smartcontract function. I will ensure it displays the expeted output which is 4.
-
Fetch a feedback with feedback-id 4 by invoking the fetch_feedback() smartcontract function, and ensure it displays the expected Feedback Number 4.
ETA: 48hrs
I am applying to this issue via OnlyDust platform.
My background and how it can be leveraged
hello i am a frontend dev and blockchain developer please can i work on this issue :) and would love to be a contributor
How I plan on tackling this issue
please kindly assign me and i'll get straight to work