Let’s BUIDL: SmartWeave Contracts
A few months ago, the Arweave team released an amazing feature called SmartWeave contracts. Before we start BUIDLing, make sure to check out this article about SmartWeave contracts to learn what they are. Here, we will instead focus on helping you get started on coding your own on top of the Arweave ecosystem.
Most of the devs coming to Arweave are already part of the world of crypto and most come from Ethereum and the smart contracts world, so they understand what a SmartWeave contract is, but most of the time find it hard to understand the differences between both projects.
The first difference between Ethereum smart contracts and SmartWeave (SW) contracts is that SmartWeave contracts are coded in JavaScript, you don’t need to learn a new programming language like you had to do with Solidity. If you know how to build a website or a Node.js project, then you know how to build a SmartWeave contract.
This also makes it super flexible, since you can use a SmartWeave contract to do pretty much anything you can do with JavaScript.
Another huge difference between the two contract types is that on Ethereum, the client that updates to the latest state the contracts are the miners, while on Arweave, the states are updated every time on the client, which most of the time is the visitor’s web browser. This is something very important to keep in mind while building a SmartWeave contract — they are not your only option, and shouldn’t be used for everything.
Since the SW contract itself is updated on the client, to get the latest state of a contract, the client needs to go through each interaction to find its latest valid state. It can be slow compared to other options like using ardb to grab transactions from Arweave.
An example of that are the Opportunities on CommunityXYZ. Opportunities are normal transactions, which makes them much faster to load since we only have to do a request which returns all the necessary fields without having to go through multiple transactions to find it. While loading a Community takes longer, we had to set a cache in place to help with that one.
How SmartWeave contracts are built
One thing that is always hard to understand for new devs working on top of Arweave is that a SmartWeave contract is separated into three pieces:
- The contract source
- The state
- The executor (SmartWeaveJS)
The contract source
The contract is the code itself, written in JavaScript. This is the contract that will be run on the client (by the executor) to update the states and get the latest and valid one.
The contract source never changes, this is what guarantees your users that what they are using today, will always be true. It’s always good, as a dev user, to read the contract source, so we know what the owner (or anyone else) can do with the projects you’re using.
Multiple projects can use the same contract source. An example of that are Profit Sharing Communities, by creating a Community on https://community.xyz you’re using the same contract source as all the others, even CommunityXYZ itself uses the same contract source. However, each is separated by their own states and that state is deployed by the founder of that Community.
Let’s BUIDL!
The state is normally the first thing we write, but the contract should be the first thing we think about, since this defines what the project will be, and what you want it to do.
In this test, let’s write some simple code which will increase the caller’s balance every time they call the increment
function. Caller is the wallet address that interacts with this contract source.
export function handle(state, action) {
const balances = state.balances;
const input = action.input;
const caller = action.caller;
if(input.function === 'increment') {
// If the caller already is a key of balances, increment, if not, set it to 1.
if(caller in balances) {
balances[caller]++;
} else {
balances[caller] = 1;
}
}
}
All the SmartWeave contracts must start with export function handle(state, action){}
since this is the function that is called by the executor. Everything else should be inside that handle
function. Right now the handle
itself is confusing since we don’t know what the state
and action
params are. Let’s first explain the action.
The action param
The action param is the action that you send while executing a SmartWeave contract, every time you want to do an update on a SW contract you need to send an input transaction, this one normally includes a function name input.function
, and any other params that you need to send with it. In this case we don’t need to send anything else, but for example we could send input.qty
to specify the quantity we want to increment our balance, this also needs to be specified on the contract source code for it to work. Remember that this is controlled by the wallet owner, so we need to always have conditions when receiving data from the user, to prevent anything unwanted.
We will see the input
again when talking about the executor (SmartWeaveJS).
The action param also has the caller
as a key of that object. The caller, again, is who is running the function. These are the two only keys in action
: input
and caller
.
The state
The state is the contract state, which lets users know what the updates of your contract are, and where it is at, at the moment of execution. This get’s updated over time (on the client), while the contract source never changes.
When we first create a contract, we also need to send a state with it, which is the initial state.
Let’s BUIDL!
We can see on our contract source that the state is expecting a balances
object which includes wallet address (caller
) as keys, and each caller key has a number as its value. In this example let’s add to ourselves some balances as the initial state, the state is a JSON object:
{
balances: {
"BPr7vrFduuQqqVMu_tftxsScTKUq9ke0rx4q5C9ieQU": 1000
}
}
And this means that when the first person goes and reads our contract, the first state is that we have 1,000 of this token balance.
Both the state and the contract must be separate transactions sent to Arweave, and both are deployed only once. After that, the state is updated by sending little transactions that includes input
interactions.
Deploying
Before talking about the executor, let’s first see what we should do with the contract source and the initial state. Both of these are just normal transactions, but with some special tags. We could use SmartWeaveJS as a CLI tool to deploy these contracts, but since we are here to learn, let’s do it manually by using ArweaveJS to create these transactions. If you don’t know what ArweaveJS is or how to use it, click here to read the docs.
import Arweave from 'arweave';const arweave = Arweave.init({
host: 'arweave.net',
protocol: 'https',
port: 443
});async function createContract() {
// Let's first create the contract transaction.
const contractTx = await arweave.createTransaction({ data: contractSource }, wallet);
contractTx.addTag('App-Name', 'SmartWeaveContractSource');
contractTx.addTag('App-Version', '0.3.0');
contractTx.addTag('Content-Type', 'application/javascript');
// Sign
await arweave.transactions.sign(contractTx, wallet);
// Let's keep the ID, it will be used in the state transaction.
const contractSourceTxId = contractTx.id;
// Deploy the contract source
await arweave.transactions.post(contractTx);
// Now, let's create the Initial State transaction
const initialStateTx = await arweave.createTransaction({ data: initialState }, wallet);
initialState.addTag('App-Name', 'SmartWeaveContract');
initialState.addTag('App-Version', '0.3.0');
initialState.addTag('Contract-Src', contractSourceTxId);
initialState.addTag('Content-Type', 'application/json');
// Sign
await arweave.transactions.sign(initialState, wallet);
const initialStateTxId = initialState.id;
// Deploy
await arweave.transactions.post(initialState);
}
createContract();
Congratulations! You have just deployed your first SmartWeave contract.
What we will use to interact with your newly-created SW contract is that last initialStateTxId
, so make sure to keep it somewhere safe, since we will need it later.
Next time, we have to learn how “the executor” works, since this is what we need to use to read our contract’s latest states, and also to update the states by sending transactions to Arweave with it.
Thanks for reading and if you like these Let’s BUIDL series let me know by giving it a thumb up and sharing. I’ll make sure to keep doing more of these!
If you have further questions or simply want to join the fun, join us on the Arweave Discord, and follow me on twitter.
—
EDIT: Second part is out. Keep reading here: https://cedriking.medium.com/lets-buidl-smartweave-contracts-2-16c904a8692d