0

I'm trying to create a function that inserts an item and returns the resulting ID through web3 to my frontend. It looks like this:

function newTranslation(string str, uint lang1, uint lang2) payable returns (uint translationID) {
    if (msg.value > 0) {
        translationID = translations.length;
        Translation memory t; 

        t.originAddress = msg.sender;
        t.originStr = str;
        t.originLanguage = lang1;
        t.destLanguage = lang2;
        t.bounty = msg.value;  // use the amount in txn
        t.time = now;
        t.completed = false;

        translations.push(t);
        numTranslations++;
    }
}

Problem is, when I try to call instance.newTranslation() through web3, I get the transaction object rather than the actual uint id. However, if I do instance.newTranslation.call(), I get the uint id, but the function doesn't actually change the state of the contract.

I also found another SO post about this issue and it recommended using events to do this. However, I found this to be a little too hacky and wondered if there was a preferred method of accomplishing the same thing? Or if there isn't, am I architecting my code in a non-standard way?

Much thanks for any help.

3

1 Answer 1

1

As of now, you can't. As you've observed, you can get the return values when you "dry run" the transaction, but you only get a txnHash when you send a transaction.

Assuming you have a getter function to retrieve the state, a common pattern is something like:

instance.newTranslation({from: account})
.then(function(txn) {
  return getNewTranslation(row, {from: account}); // <== what row?
});
function(response) {
  console.log(response);
});

Consider the commented line (what row?). You could, for example, interrogate a function that returns return translations.length and that helps iterate the list, but it would be unsafe to assume that "this" transaction has landed in the expected row. It would also be unsafe to assume that it's the last/latest row in the list. Both are unsafe assumptions because a single block could contain multiple near-simultaneous sendTranslation() transactions. It will be challenging to sort it out without consulting an event log.

This sort of challenge is why a lot of use-cases lend themselves to event listeners that capture mined/confirmed transactions from all sources. You can make senders that "fire and forget" so-to-speak, and listeners that respond to confirmed transactions appropriately without regard to transaction origin.

Main takeaway, don't shy from events. They are a really important element of the overall system. It's worth taking the time to carefully contruct event logs that chronical every important state change such the entire state history of the contract can theoretically be reconstructed from logs.

Hope it helps.

2
  • Thanks, this is a great answer. However, I have a follow-up question. Which is, in my JS code, how would I be able to get the new transaction id from events so I can save it to the frontend? As you've noted, there is no way to guarantee that a particular event corresponds to the specific transaction that the user triggered.
    – adrianmcli
    Commented Oct 23, 2017 at 0:08
  • 1
    I would incline to rethink it so you're unconcerned about that. For example, if a new record is inserted (who cares where it came from?) insert it into the UI so everyone can see it. Alternatively, you could watch for a particular record if it has a unique identifier and that's included in the emitted event. Your code doesn't have unique identifiers - identical records could append to the list. That might not be ideal. Some patterns over here might be helpful. ethereum.stackexchange.com/questions/13167/… Commented Oct 23, 2017 at 0:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.