Skip to main content

Implement contract logic in Javascript

Let's create a new file contract.js and add the following initial code:

// file: contract.js

export async function handle(state, action) {
// All the smart contract code should be be placed inside the handle function
// ...
}

As you can see the handle function has 2 arguments that will be fulfilled with values during interaction with the contract. The state argument will contain (guess what) the state and the action argument will contain information about the interaction function name and (optionally) its arguments.

🔩 Implement the first method name

Let's implement the first (and the simplest) function for getting the name of the loot pool.

// file: contract.js

switch (action.input.function) {
case 'name': {
return { result: state.name };
}

default: {
throw new ContractError(`Unsupported contract function: ${functionName}`);
}
}

💡 Note! This function reads from state, but doesn't change it.

🔨 Implement getOwner and transfer methods

Let's add some token-like methods inside the swtich block.

// file: contract.js

case "getOwner": {
const asset = action.input.data.asset;
if (state.assets[asset]) {
return { result: state.assets[asset] };
} else {
return { result: `The asset "${asset}" doesn't exist yet` };
}
}

case "transfer": {
const toAddress = action.input.data.to;
const asset = action.input.data.asset;
if (state.assets[asset] !== action.caller) {
throw new ContractError("Can not transfer asset that doesn't belong to sender");
}
state.assets[asset] = toAddress;
return { state };
}

As you can see, the getOwner function also doesn't change the contract state, while the transfer function updates the owner of the provided asset. This interaction will require sending a transaction to Arweave and the sender will need to pay some fee in AR tokens.

🪚 Implement generate method

Great! Now we can implement the core function for the whole contract.

We can add the following arrays right after the first contract line.

// file: contract.js

const COLORS = [
'green',
'red',
'yellow',
'blue',
'black',
'brown',
'pink',
'orange',
'purple',
'gray',
];
const MATERIALS = [
'gold',
'wood',
'silver',
'fire',
'diamond',
'platinum',
'palladium',
'bronze',
'lithium',
'titanium',
];
const ITEMS = [
'sword',
'shield',
'robe',
'stone',
'crown',
'katana',
'dragon',
'ring',
'axe',
'hammer',
];

Then, we add a generate function inside the switch .. case block:

// file: contract.js

case "generate": {
const colorIndex = await getRandomIntNumber(COLORS.length, "color");
const materialIndex = await getRandomIntNumber(MATERIALS.length, "material");
const itemIndex = await getRandomIntNumber(ITEMS.length, "item");
const asset = COLORS[colorIndex] + " " + MATERIALS[materialIndex] + " " + ITEMS[itemIndex];

if (!state.assets[asset]) {
state.assets[asset] = action.caller;
} else {
throw new ContractError(
`Generated item (${asset}) is already owned by: ${state.assets[asset]}`);
}
return { state };
}

But it would need a help function getRandomIntNumber for random number generation. It's not an easy task to generate random numbers in Smart Contracts. For the sake of simplicity we'll create a pseudo-random number generation, which will be based on current block height, transaction id, caller address, timestamp and some unique value.

// file: contract.js

function bigIntFromBytes(byteArr) {
let hexString = '';
for (const byte of byteArr) {
hexString += byte.toString(16).padStart(2, '0');
}
return BigInt('0x' + hexString);
}

// This function calculates a pseudo-random int value,
// which is less then the `max` argument.
// Note! To correctly generate several random numbers in
// a single contract interaction, you should pass different
// values for the `uniqueValue` argument
async function getRandomIntNumber(max, uniqueValue = '') {
const pseudoRandomData = SmartWeave.arweave.utils.stringToBuffer(
SmartWeave.block.height +
SmartWeave.block.timestamp +
SmartWeave.transaction.id +
action.caller +
uniqueValue
);
const hashBytes = await SmartWeave.arweave.crypto.hash(pseudoRandomData);
const randomBigInt = bigIntFromBytes(hashBytes);
return Number(randomBigInt % BigInt(max));
}

💡 Note! You can use the global variable SmartWeave inside your contract code. You can learn more about it here.

🗜️ Add bonus methods generatedAssets and assetsLeft

Ok, the core part of the contract is ready. So let's add 2 bonus methods inside the switch .. case block.

// file: contract.js

case "generatedAssets": {
return { result: Object.keys(state.assets) };
}

case "assetsLeft": {
const allAssetsCount = COLORS.length * MATERIALS.length * ITEMS.length;
const generatedAssetsCount = Object.keys(state.assets).length;
const assetsLeftCount = allAssetsCount - generatedAssetsCount;
return { result: assetsLeftCount };
}

Boom! The contract code is ready to use test.