TX Lego Tutorial

Background:#

Transactions are one of the most ubiquitous and bulky blocks of JS in the app. Since the UI syncs to the graph 90% of the time, the app needed a way to make sure that what the user was seeing represented what was on the contract, but also represented what the subgraph had indexed.

This resulted in a complex pattern of Services (Higher order functions for directing contract interaction), polls (checking new graph data after the user fires the transaction), and tests (confirming that the graph data had synced).

Here's an example of what a transaction looked like before.

And here's an example of what that same transaction looks like expressed as a JS lego:

// code from src/data/contractTX.js
TX: {
...
GUILDKICK_PROPOSAL: {
contract: 'Moloch',
name: 'submitGuildKickProposal',
poll: 'subgraph',
onTxHash: ACTIONS.PROPOSAL,
display: 'Submit GuildKick Proposal',
errMsg: 'Error submitting proposal',
successMsg: 'Guild Kick Proposal submitted!',
detailsJSON: DETAILS.STANDARD_PROPOSAL,
createDiscourse: true,
gatherArgs: ['applicant', 'detailsToJSON'],
},
}

And placed into a form lego like this:

PROPOSAL_FORMS: {
GUILDKICK: {
...
tx: TX.GUILDKICK_PROPOSAL,
...
},
}

Because the Form lego composes the tx lego, and it has its own way of firing off transactions, the dev never has to introduce the transaction to a react component at all.

With JS Legos, devs are able to create their transactions as clean, static data. This will allow core devs to store, compose, and organize the applications functionality as they please, while allowing community devs a flexible yet concise interface for boost development.

NOTE: TX legos are currently only available with a DAO context. App wide txs will be available on the next update.

Tutorial#

However, there are many cases where transactions will still need to be placed in a React component.

Let's go through an example. Let's say we want to wire up a button to sponsor a proposal when the user clicks it.

First we would have to write out our TX as a lego. We would start by finding the src/data/contractTX.js and creating a new field on the TX object.

We use all caps to let other devs know that this value is constant

TX:{
export const TX = {
SPONSOR_PROPOSAL: {
},
}

Then we want to direct this transaction by telling it which contract ABI to interact with and what function to call. For this transaction, we're calling the DAO's Moloch contract. So we'll use the string 'Moloch' under the field contract.

  • We have a Contract Service to interact with this ABI already.
  • So all we need to do is travel to MolochService.js in src/services and find the function that we want to hit. In this case, the name is sponsorProposal
  • Let's add these to the Lego:
TX:{
SPONSOR_PROPOSAL: {
contract: 'Moloch',
name: 'sponsorProposal',
},
}

Note: At the moment, TXlegos only interact with local ABIs through contract services. In the future, we will dynamically fetch network ABIs from block explorers. Services will also be phased out to ensure a single source of truth.

Next we'll need to tell the app what to poll to ensure that UI only updates once all the backend data has been updated. We're interacting with the DAOhaus subgraph, so all we need to add is 'subgraph' to the poll field.

TX:{
SPONSOR_PROPOSAL: {
contract: 'Moloch',
name: 'sponsorProposal',
poll: 'subgraph'
},
}

Many transactions require a UI change when a transaction hash is created. In this situation, all we need is the TxModal to appear.

onTxHash only accepts an array of strings. What we want to do is tell the transaction matrix what actions we want the app to take, and in what order.

Since there's only one instruction, we could use ['openTxModal'] (the key string for closing the TxModal wrapped in an array). Fortunately we have a set of constants for actions saved in contractTX.js, so we can use that instead.

const ACTIONS = {
PROPOSAL: ['closeProposalModal', 'openTxModal'],
BASIC: ['openTxModal'],
};
TX:{
SPONSOR_PROPOSAL: {
contract: 'Moloch',
name: 'sponsorProposal',
poll: 'subgraph',
onTxHash: ACTIONS.BASIC,
},
}

Using the constant ensures that the UX for basic contract interactions remains the same if any changes are made.

Finally, we need to tell the transaction what to display in the UI. display controls what is displayed in the TxModal and Txlist (in future updates), while errMsg and successMsg control what is displayed once the transaction resolves. Let's add these as well.

TX:{
SPONSOR_PROPOSAL: {
contract: 'Moloch',
name: 'sponsorProposal',
poll: 'subgraph',
onTxHash: ACTIONS.BASIC,
display: 'Sponsor Proposal',
errMsg: 'Error sponsoring proposal',
successMsg: 'Proposal Sponsored!',
},
}

Because we'll be adding the function arguments inside the transaction itself, this lego is finished. All we need to do is hook it up.

// in src/components/proposalActions.js
const sponsorProposal = async id => {
setLoading(true);
await submitTransaction({
tx: TX.SPONSOR_PROPOSAL,
args: [id],
});
setLoading(false);
};

That's it.

There's a lot more to this pattern. Be sure to read more about it the API section.