Buildin' Them Transactions!

A warm welcome to you Amigos! We here at The Bearmint Minin’ Company are passionate about helpin’ developers build amazin’ applications and makin’ the experience real nice and smooth fer them. Now when it comes to blockchains, most folks use ‘em fer sendin’ and receivin’ tokens and managin’ their financial affairs accordingly. Of course you can choose to do somethin’ real easy likes transfer tokens from one account to another, but the term ‘transactions’ actually describes a whole range of different processes that all help folks to manage their assets and move their funds where they’re needed in the safest and most efficient way imaginable!

Whether yer a seasoned code veteran or a young whippersnapper lookin’ to stake yer claim in the world of blockchain, we’re here to guide and help you get things done as painlessly as possible! Yep, ol’ Buck insisted that we take the time to put together a few simple (yet comprehensive) guides on how to do a whole bunch of truly amazin’ stuff, and to kick things off, we have a step-by-step guide on how to build and implement transactions within Bearmint!

There’s All Kinds of New-Fangled Transactions!

If you have a fundamental understanding of transactions, you’ll know that they all go through a certain lifecycle and that various events take place at different stages. In Bearmint, developers can build and implement various transaction types, each of which possesses its own nuances and fulfills a certain function.

The following information illustrates how to build the most fundamental transaction type (a token transfer) and provides code examples throughout in order to remove as much uncertainty as possible. However, before starting, it’s important to understand what certain elements within the code mean and why they appear at certain junctures.

What Are Them Things Yer Talkin’ About?

  • @bearmint/bep9 is an NPM package that offers an implementation of BEP-009 which contains a set of utilities to deal with particularly tedious tasks that do not fall within a single domain
  • @bearmint/bep12 is an NPM package that offers an implementation of BEP-012 which handles important mathematical inputs and prevents said inputs from being concatenated as a result of the inherent limitations of JavaScript
  • @bearmint/bep13 is an NPM package that offers an implementation of BEP-013 which allows for the sharing of a set of types between all the modules that exist within Bearmint
  • @bearmint/bep16 is an NPM package that offers an implementation of BEP-016 which contains a standardized method of bootstrapping the plugins and application via service providers
  • @bearmint/bep22 is an NPM package that offers an implementation of BEP-022 that defines the structure of accounts and their application within Bearmint, and
  • @bearmint/bep51 is an NPM package that offers an implementation of BEP-051 which permits the implementation of various transaction types within Bearmint

Building a Transfer

To begin, it’s necessary to build the most fundamental transaction type a blockchain needs - a token transfer. This type of transaction simply transfers tokens from one account to another (or in this case, many). The following sections outline the various components needed to create such a transaction.

The Handler

The handler is the workhorse in any type of transaction. It will execute upon receiving a transaction that is then matched against a specific handler. Its purpose is to ensure that invalid data does not enter the mempool. In the case of a byzantine validator that circumvents the mempool, it will apply any and all state changes accordingly.

The Protocol Buffer Definition

The protocol buffer definition determines how a transaction will undergo serialization and deserialization. We recommend that you use protoc to compile this and subsequently export the required data from your package to allow clients (such as wallets) to use the definition to create transactions.

syntax = "proto3";
option optimize_for = SPEED;
message BEP54Message {
string denomination = 1;
uint64 amount = 2;
optional string message = 3;
}

The Validation

The validation is the function that basically guards the mempool. As such, any transaction that fails this check will not transfer into the mempool. Note that a byzantine validator can circumvent this and still include an invalid transaction within a block, so it’s vital that we perform this check again in order to prevent it from modifying our application's state.

async function validate() {
return validateMessage(async () => {
let requiredFunds = amount.clone();
if (denomination === denom({ state, type: 'gas' })) {
requiredFunds = requiredFunds.minus(transaction.data.gas);
}
if (sender.balances[denomination].isLessThan(requiredFunds)) {
eventRecorder.captureError({
index: false,
key: 'sender.balance',
value: `Balance of sender (${
transaction.data.sender
}) would go negative (Have ${sender.balances[
denomination
].toString()}, Need ${requiredFunds.toString()})`,
});
}
});
}

The Verification

In this instance, the required verification is relatively rudimentary since we only need to ensure that the transaction does not use the same sender and recipient as this would waste gas. If the transaction passes the following check, it will proceed to the next phase. Should it fail, the protocol will throw an exception and terminate the execution.

async function verify() {
return verifyMessage(async () => {
eventRecorder.captureErrors(
await validateNotSelfReceiving({
addressFactory: cradle.AddressFactory,
recipient: transaction.data.recipient,
sender: transaction.data.sender,
}),
);
});
}

The Execution

We finally execute the transaction, meaning we take the memo property of the transaction and apply any requested state changes. At this point, we still have the ability to roll back any changes we don’t want to commit, and it is important that we do so if we don’t want to corrupt the application's state.

Termination may occur if the protocol throws an exception along with a reason for the termination. In the event that such an exception results, the protocol will restore the previous state, providing a clean slate for the next transaction, thus avoiding any chance of contamination.

async function run() {
return runMessage(async () => {
decreaseBalance({
account: sender,
amount,
denomination,
});
increaseBalance({
account: recipient,
amount,
denomination,
});
await state.getAccounts().index([recipient, sender]);
});
}

The Complete Handler

Having implemented all of the required functions, we can combine everything into a handler and proceed to the service provider where we will eventually register our new transaction handler.

import { readPackageJson } from '@bearmint/bep9';
import { makeBigNumber } from '@bearmint/bep12';
import { Cradle, denom, TransactionHandler } from '@bearmint/bep13';
import { decreaseBalance, increaseBalance } from '@bearmint/bep22';
import {
createContextFunctions,
getSenderWithRecipient,
makeTransactionHandler,
validateNotSelfReceiving,
} from '@bearmint/bep51';
import { BEP54Message } from './proto';
const packageJSON = readPackageJson(__dirname);
export const HANDLER = packageJSON.name;
export const VERSION = '0.0.0';
export function makeHandler(cradle: Cradle): TransactionHandler {
return {
...makeTransactionHandler(cradle, HANDLER, VERSION),
async context({ eventRecorder, state, transaction }) {
async function canonicalize() {
const { amount, denomination, message } = BEP54Message.decode(
transaction.data.message.content,
);
return {
amount: makeBigNumber(amount),
denomination,
message,
};
}
const message = await canonicalize();
const { runMessage, validateMessage, verifyMessage } = createContextFunctions({
cradle,
eventRecorder,
handler: `${HANDLER}/${VERSION}`,
message,
state,
transaction,
});
const { amount, denomination } = message;
const { recipient, sender } = await getSenderWithRecipient({
state,
transaction,
});
return {
canonicalize,
async run() {
return runMessage(async () => {
decreaseBalance({
account: sender,
amount,
denomination,
});
increaseBalance({
account: recipient,
amount,
denomination,
});
await state.getAccounts().index([recipient, sender]);
});
},
async validate() {
return validateMessage(async () => {
let requiredFunds = amount.clone();
if (denomination === denom({ state, type: 'gas' })) {
requiredFunds = requiredFunds.minus(transaction.data.gas);
}
if (sender.balances[denomination].isLessThan(requiredFunds)) {
eventRecorder.captureError({
index: false,
key: 'sender.balance',
value: `Balance of sender (${
transaction.data.sender
}) would go negative (Have ${sender.balances[
denomination
].toString()}, Need ${requiredFunds.toString()})`,
});
}
});
},
async verify() {
return verifyMessage(async () => {
eventRecorder.captureErrors(
await validateNotSelfReceiving({
addressFactory: cradle.AddressFactory,
recipient: transaction.data.recipient,
sender: transaction.data.sender,
}),
);
});
},
};
},
};
}

The Service Provider

The service provider is much more straightforward than the handler. Here we inject the application so that we can access the transaction registry and register the TransferTransactionHandler function that we just created. This will effectively register the handler and fees, storing it under its unique checksum.

import { Cradle, ServiceProvider } from '@bearmint/bep13';
import { makeServiceProviderSkeleton } from '@bearmint/bep16';
import { makeHandler } from './handler';
export function makeServiceProvider(cradle: Cradle): ServiceProvider {
return {
...makeServiceProviderSkeleton(__dirname),
async register() {
cradle.TransactionHandlerRegistry.set(makeHandler);
},
};
}

So What Transactions Do I Need?

As always, different applications and use-cases require different approaches. If you just want to be able to send and receive tokens, a simple token transfer like the one described above will do just fine. However, if you need more advanced transaction types such as MuSig, it’s a little more complicated, but entirely doable.

Whatever the case, Buckley and the Bearmint Gang will be more than happy to talk to you about yer unique requirements and help you build and implement the transaction types you need fer yer project!

In future blog posts we’ll explain how to build and implement more helpful functions into your application, so keep an eye out for those in the coming weeks!