How to Read Smart Contracts Without Learning Solidity

In Decentralized Finance, code is law. Instead of legal documents, we have self-enforcing smart contracts. While contracts written in code enable the vast universe of DeFi, they also present a literacy issue. Legalese may be boring, but the average user can still puzzle through it with enough re-reading. In contrast, code is incomprehensible to the average user. The question: can you learn how to read smart contracts without learning Solidity, or do you have to learn to code?

Exacerbating the literacy issue is the educational material available. Most “Learn Solidity” resources are targeted towards programmers. The problem with that: most people are not programmers. Nor should they have to become programmers to use crypto/DeFi. Learning to code is not trivial. It’s a huge time investment, and can be extremely challenging if you don’t have the particular brain wrinkles for it.

The average user should not be burdened with having to learn an entire profession’s worth of technical skills to understand whether they’re about to sign away their life savings to a scammer.

The status quo is not going to work if we want to see large-scale crypto adoption. The entire community has a responsibility to increase the level of user education and create better educational resources.

Who is This Guide For?

This material is for anyone who wants to engage in basic, business-logic level analysis of smart contracts. This includes basic due diligence before aping, strategic research, and fundamental analysis for investment purposes. If your programming knowledge stops at basic Excel formulas, this is the place to be.

Foundational Material

Hopefully if you’re reading this, you have at least a basic grasp of what a blockchain is. Before we get started, we need to have a few more pieces of info under our belts. All my discussion here will be based on Ethereum, but is applicable to any EVM-compatible chain, like Avalanche, Fantom, Polygon, Arbitrum, etc. (See also: A Crash Course on Ethereum)

First things first: what’s an EVM? It stands for Ethereum Virtual Machine. In layman’s terms, this is the giant computer-in-the-sky which all participants of the Ethereum network contribute to. The EVM lets us run code in the context of the blockchain, and those pieces of code are called smart contracts.

At a very high level, smart contracts are black boxes that contain a series of instructions on how to change the state of the blockchain, given a particular input. This has a few implications.

  • Any given contract has a specific set of allowable ways to interact with it.
  • The output of each interaction is deterministic (IE always produces the same output given the same input).
  • Said contract will only have a specific set of allowed interactions with other contracts (if any).

This means that each contract has a finite, clearly defined scope to what it is capable of doing. We can then logically walk through each function, and determine what effects it will have. This guide is NOT going to get into the weeds about all the effects; we’re going to focus on the high level logic flow.

Finally, smart contracts have one more important property. They cannot initiate transactions by themselves. A smart contract has be poked by an Externally Owned Account (EOA) to initiate any activity. The EOA is the traditional “account” or “wallet”, consisting of a public and private key.

The end goal for today is to actually parse through a simple smart contract and understand what it does. Before we get into that, we will unfortunately have to cover a few basic items of code. About 90% of the smart contracts on Ethereum are written in Solidity, with the minority written in Vyper. I am going to use Solidity, for hopefully-obvious reasons.

Overall strategy

When trying to parse a contract for the first time, you want to follow a strategy instead of aimlessly diving into the code. This is especial important if you want to learn how to read smart contracts without learning Solidity, as you can easily get lost in the syntax otherwise. Below is the process I use when trying to understand a new contract.

  1. Read the comments and any high level documentation available from the protocol
  2. Examine the state variables
  3. Quick glance at constructor
  4. Identify pieces of code that can be temporarily ignored
  5. Identify any functions which take in funds or interact with other contracts
  6. Identify any functions which transfer funds out
  7. Work through the exposed functions, tracing their logic through internal calls and prioritizing functions which take in or transfer out assets
  8. Revisit constructor if anything was unclear earlier

We’ll use this outline as we walk through our smart contract later in the post!

Basic Code Literacy

This overview is neither exhaustive nor technical, and is intended only to give you enough basics to understand what you’re looking at when diving into some code. Should you require additional information, there is more information available here.

Variables and Objects

Variables store a value… which can vary. Crazy right? Hopefully you will be conceptually familiar with the idea of a variable from high school algebra. In programming, this concept gets extended slightly with the idea of an object. An object just means that rather than being limited to numbers, strings, or other things you might expect to find in Excel cells, I can assign a variable to represent arbitrarily complex things.

For example, I can make an object which represents an ERC-20 token, and use that to give my smart contract the ability to interact with the token’s functions.

Functions

A function is a package of operations, defined in the code. When you want to use a function, you call the function. It may or may not accept some inputs, which are called arguments. No need to break out the flashcard, there’s not a quiz on these terms. I may use them interchangeably with more plain-language terms, so I want to define them now for your familiarity.

As a conceptual exercise, let’s think about a daily activity as a function. Suppose we have the function makeSandwich. This function might accept an argument typeOfSandwich, or an argument for quantityOfSandwich. Within the function, we would have operations to grab plate, grab a knife, open the refrigerator, etc. You could also call other functions from within this function – like say cutSandwich.

Brackets and Braces and Semicolons, Oh My!

Solidity is a curly-bracket language. For the non-nerds, that just means it follows a particular set of conventions about the structure of the code. It uses curly brackets { } to enclose pieces of code, and each line terminates with a semicolon. Parentheses ( ) are used to enclose function arguments, and for order of operations purposes.

You can safely ignore most of this syntactic detail 90% of the time. I mention it so you don’t freak out when you lay eyes on the forest of brackets, and also so you know what pieces of code are grouped together.

Visibility

Solidity requires you to set a modifier called visibility on each function and variable, which determines who can call it. They are, as follows,

  • Public – anyone can call this function
  • External – anyone who is not this contract can call this function
  • Internal – may only be called by this contract or a contract that inherits it
  • Private – may only be called by this contract

For the purposes of basic analysis, you mainly need to care about the groupings of Public+External (exposed functions) and Internal+Private (unexposed).

Any function which is exposed can be called at will by external actors. Depending on the function there may be further checks that prevent unauthorized access, but there is nothing stopping anyone on this planet from poking that function with a stick. Each exposed function is an entry point that can become an attack vector if there is a bug in the underlying code.

Unexposed functions can only be called by the smart contract itself, ie. they will be triggered by calls within the code of exposed functions. For example, a call to an external function which deposits assets into an LP pool will trigger several bits of code, including an internal function call to mint LP tokens to your address.

Data Types

Solidity requires variables to be assigned a type. Simply, the type determines what operations are permissible with that variable, and how the computer should treat it. It doesn’t make much sense to divide a string by 2, or attempt to transfer USDC to the number 531. Common data types and the layman’s definition are as follows.

  • uint256: a big f****** integer that cannot go negative.
  • address: a public address on the blockchain.
  • bool: a Boolean value. Either true or false.
  • bytes32: an sequence of 32 bytes. These are generally used for storing hashes, Chainlink API keys, and other such data.
  • bytes: an arbitrary length sequence of bytes.

A type requiring a bit of special attention is the mapping. A mapping maps a unique key to its value. For example, you could have a mapping like so, mapping(address=>bool) public friends, which has public visibility and maps addresses to a true/false of whether they are your friend. Notably, you can’t search for an element of a mapping, so you can’t extract a list of all your friends from this mapping without some external information to help out.

Comments

Text in comments is provided for informational purposes by the developer, and does not affect the code in any way.

// This is a basic comment

/// This is a special comment type called NatSpec.
/// It is used to automatically generate documentation.

/**
 * This is a multiline NatSpec comment
 * Everything in here is commented
 */

Design Patterns: Account Balances

One last piece of information. A widely used pattern is the concept of a balance, or the number of whatever the case may be that’s attributed to your account. This is usually implemented as a mapping from an address to a uint256. For example, whenever you transfer 1000 units of an ERC-20 token, all that happens under the hood of the token contract is:

  1. Your address balance decreases by 1000
  2. The target address balance increases by 1000

That’s it. We (myself included) tend to think of transfers as sending brightly colored poker chips whizzing around the internet, but that is simply not the case.

Dissecting a Smart Contract

Let’s tie it all together by looking at a real smart contract line by line. We’ll investigate OpenZeppelin’s PaymentSplitter contract, which provides a trustless way to proportionally split funds transferred to a single address between multiple destinations. The code for this is available on GitHub here. I recommend using GitHub or a code editor to follow along, since the code in this post does not have syntax highlighting.

I’ll also use diagrams.net to make a diagram as we go along. I highly recommend this tool for helping keep a high level overview as your understanding grows. Ready? Here we go!

Administrative Etcetera

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (finance/PaymentSplitter.sol)

pragma solidity ^0.8.0;

import "../token/ERC20/utils/SafeERC20.sol";
import "../utils/Address.sol";
import "../utils/Context.sol";

These lines tell us the license applied to the code and the Solidity version it uses. Investors may care whether a piece of code is open source or rights reserved, but otherwise these lines are basically irrelevant to a high level overview. It also tells us what files are imported, which is important. Imported files are pieces of code which are accessible in this smart contract, but are not written out in this smart contract.

In this particular case, the imported files are library contracts, not application logic, so we don’t have to dive into them. Libraries are reusable pieces of code that are not specific to any one contract. You could make a library, for instance, that contains math functions for doing calculus, or for finding square roots, and use that across multiple files wherever it is required.

Contract Declaration and Events

/**
 * @title PaymentSplitter
 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware
 * that the Ether will be split in this way, since it is handled transparently by the contract.
 *
 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each
 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim
 * an amount proportional to the percentage of total shares they were assigned.
 *
 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the
 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
 * function.
 *
 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and
 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you
 * to run tests before sending real value to this contract.
 */
contract PaymentSplitter is Context {
    event PayeeAdded(address account, uint256 shares);
    event PaymentReleased(address to, uint256 amount);
    event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
    event PaymentReceived(address from, uint256 amount);

Comments! In this case, the OpenZeppelin devs have kindly laid out what the contract does for us. Take a moment and read through the comment. The actual contract starts at Line 27, with contract PaymentSplitter is Context {. This line states the name of the contract, and tells us that it inherits the Context contract.

Inheritance means that all the functions of the inherited contract are available to this contract. In this case Context is a library used in OpenZeppelin’s contracts for some technical reasons. We can safely ignore it for today.

The next four lines define events. Events are log messages emitted by the code for integration and good practice purposes, and don’t form part of the core logic. You can gloss over them when reading for code detail, but you should pay close attention to where they are emitted.

Why? Events are used to make it easier for parties outside the blockchain to monitor what happens on the blockchain.

Good design practice emits events whenever something important from an application-logic perspective happens. ERC-20 standard tokens emit Transfer events whenever you send tokens somewhere. Lending markets may emit events whenever someone borrows, or withdraws, or lends, or a protocol fee changes. The presence of an event clues you in that this is important, and someone external to the blockchain could care when this happens.

State Variables

    uint256 private _totalShares;
    uint256 private _totalReleased;

    mapping(address => uint256) private _shares;
    mapping(address => uint256) private _released;
    address[] private _payees;

    mapping(IERC20 => uint256) private _erc20TotalReleased;
    mapping(IERC20 => mapping(address => uint256)) private _erc20Released;

Here we get into a core part of the contract: state variable definition. State variables are variables that the contract indicates should form part of the broader blockchain state. The value of each of the state variables can change of course, but whenever it changes, the state is eternally recorded in the storage of the blockchain.

Stepping through these variables, we see that they are all marked private visibility – so other contracts can’t access them. The two uint256s will store the total shares of the payment splitter, and the total Ether (as wei) released from the contract. The two mappings store the shares allotted to each address, and Ether released to each address.

I haven’t mentioned the next data type yet: a list. The notation [] appended to the address type means that this is an array or list of addresses.

These last two mappings are a little more complex. _erc20TotalReleased maps an IERC20 object (which represents an ERC-20 token and allows us to call the ERC-20 functions from this smart contract) to a uint256 representing the total amount of that token released.

Extending that, the next mapping is a nested mapping! It maps an IERC20 token object to a mapping of addresses to uint256. This allows us to answer the following question posed in natural language: How much of a given ERC-20 token has this contract transferred to a given user address?

Constructor

    /**
     * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at
     * the matching position in the `shares` array.
     *
     * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no
     * duplicates in `payees`.
     */
    constructor(address[] memory payees, uint256[] memory shares_) payable {
        require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
        require(payees.length > 0, "PaymentSplitter: no payees");

        for (uint256 i = 0; i < payees.length; i++) {
            _addPayee(payees[i], shares_[i]);
        }
    }

Next up is the constructor. The constructor is a special function used to initially setup the contract. It is only called once, when the contract is deployed to the blockchain. This particular constructor requires two inputs: a list of addresses, and a list of uint256s. This will represent the addresses which can receive payments from the splitter, and their shares of the split. This constructor also has the payable modifier which means the contract can receive Ether.

The require statements are protective statements. They will revert the transaction if the condition inside them is not met. Ensuring that require statements are correct and adequate to protect the function is the domain of smart contract auditors, not something that we can cover in a guide of this scope. Nevertheless, these particular requirements read pretty easily in plain text and should be pretty self-explanatory.

The last part of the constructor is a for loop. All this does is go down the two lists and call the _addPayee function for each pair of entries. Instead of walking down the contract line by line, we will jump to the _addPayee function at the bottom of the contract and follow the logic.

    /**
     * @dev Add a new payee to the contract.
     * @param account The address of the payee to add.
     * @param shares_ The number of shares owned by the payee.
     */
    function _addPayee(address account, uint256 shares_) private {
        require(account != address(0), "PaymentSplitter: account is the zero address");
        require(shares_ > 0, "PaymentSplitter: shares are 0");
        require(_shares[account] == 0, "PaymentSplitter: account already has shares");

        _payees.push(account);
        _shares[account] = shares_;
        _totalShares = _totalShares + shares_;
        emit PayeeAdded(account, shares_);
    }

Glossing over the require statements for now, let’s walk through line by line.

_payees.push(account); – add account to the list of addresses represented by _payees.

_shares[account] = shares_; – map the desired amount of shares to account‘s address.

_totalShares = _totalShares + shares_; – add the amount of new shares to the total shares count.

It then emits the PayeeAdded event to let observers know that a payee has been added.

Backtracking to the constructor, we now have the information to tell what the constructor actually does. Recall that for each entry in payees and shares_, the constructor calls _addPayee. This means for each entry, the contract will:

  1. Add the user’s address to the list of users who can be paid
  2. Allocate that user their proportion of the shares
  3. Increase the total quantity of shares to include the newly added shares

Congratulations! We’ve just dissected the application logic of a function! We now know that when the contract is deployed, it will set all the users who can retrieve funds, and assign them each their share.

Here’s a quick diagram. Pretty simple so far.

Getter Functions

    /**
     * @dev Getter for the total shares held by payees.
     */
    function totalShares() public view returns (uint256) {
        return _totalShares;
    }

    /**
     * @dev Getter for the total amount of Ether already released.
     */
    function totalReleased() public view returns (uint256) {
        return _totalReleased;
    }

    /**
     * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20
     * contract.
     */
    function totalReleased(IERC20 token) public view returns (uint256) {
        return _erc20TotalReleased[token];
    }

    /**
     * @dev Getter for the amount of shares held by an account.
     */
    function shares(address account) public view returns (uint256) {
        return _shares[account];
    }

    /**
     * @dev Getter for the amount of Ether already released to a payee.
     */
    function released(address account) public view returns (uint256) {
        return _released[account];
    }

    /**
     * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an
     * IERC20 contract.
     */
    function released(IERC20 token, address account) public view returns (uint256) {
        return _erc20Released[token][account];
    }

    /**
     * @dev Getter for the address of the payee number `index`.
     */
    function payee(uint256 index) public view returns (address) {
        return _payees[index];
    }

Let’s get these functions out of the way next. Remember that the state variables for this contract are private. This means they can’t be accessed outside of this contract. Nothing on the blockchain is invisible, and you can still view those values, but it requires some trickery and is quite inconvenient. It also makes it impossible for other contracts to programmatically access the values, even if they have legitimate need to.

To counteract this, we have getter functions. These are a design pattern that uses a view state mutability (meaning it cannot change the state and costs no gas) and simply returns the value of a state variable. Getters have to be explicitly written for private and internal variables, but the Solidity compiler will automatically generate getters for external and public variables.

Putting Funds In

    /**
     * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully
     * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the
     * reliability of the events, and not the actual splitting of Ether.
     *
     * To learn more about this see the Solidity documentation for
     * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback
     * functions].
     */
    receive() external payable virtual {
        emit PaymentReceived(_msgSender(), msg.value);
    }

Okay, this contract is supposed to receive funds and split them, but how does it do that? Where does the money come in? Jumping back up to Line 68, we’ll look at a special function. The receive() function is a special function that will be called if the contract receives a direct Ether transfer. It must be included, otherwise the contract will not be able to receive a standard Ether transfer and will revert the transaction.

This particular receiver doesn’t do anything besides emitting an event to alert us of the incoming transfer. Its presence, however, means we can draw the conclusion that this contract is intended to directly receive funds via direct transfer. This is a key piece of information about the design!

Now we’re starting to get some more detail.

Taking Funds Out: Ether

Lines 127 and 147 contain two function definitions with the same name. How is that possible? These functions use a feature called overloading, which means that since they take different arguments, they are allowed to have the same name. We’re going to look first at the function release(address payable account) function, which handles the withdrawal of Ether.

    /**
     * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
     * total shares and their previous withdrawals.
     */
    function release(address payable account) public virtual {
        require(_shares[account] > 0, "PaymentSplitter: account has no shares");

        uint256 totalReceived = address(this).balance + totalReleased();
        uint256 payment = _pendingPayment(account, totalReceived, released(account));

        require(payment != 0, "PaymentSplitter: account is not due payment");

        _released[account] += payment;
        _totalReleased += payment;

        Address.sendValue(account, payment);
        emit PaymentReleased(account, payment);
    }

The two uint256s are defined inside a function body, which means they are local variables. Unlike the state variables, these are limited in scope to this function, and will be discarded after everything is done. They do not persist on the blockchain.

First, the function calculates the total amount of Ether it has ever received. It does this by adding its current Ether balance ( address(this) means the address of this contract) plus the amount of Ether it has previously released.

Next, it makes an internal function call to _pendingPayment. We’ll jump to that in a moment, so for now we’ll use context clues to assume it calculates the amount of Ether to transfer.

After a require check to prevent paying out 0 wei, we update the balances. The += notation is shorthand for setting a variable equal to its current value plus an addend. The amount released in total and the amount released to this address are both increased, and then the actual Ether transfer is made via Address.sendValue(account, payment), and an event puts the cherry on top.

Subfunction: _pendingPayment

    /**
     * @dev internal logic for computing the pending payment of an `account` given the token historical balances and
     * already released amounts.
     */
    function _pendingPayment(
        address account,
        uint256 totalReceived,
        uint256 alreadyReleased
    ) private view returns (uint256) {
        return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
    }

Now we go back to the function we skipped. This contains the core math for attributing the amount of funds that the user is owed. The calculation is this user’s portion of the total funds ever received, minus the amount already released to them. Pretty self explanatory?

Note the return keyword here, which I’ve previously ignored. Return just means that the function will hand back a value to whatever called it. In this case it returns the amount the user is owed.

Notably, this calculation is agnostic whether it refers to tokens or to Ether. We will see it again when we deal with the ERC-20 token withdrawal.

Taking Funds Out: ERC-20

    /**
     * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their
     * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20
     * contract.
     */
    function release(IERC20 token, address account) public virtual {
        require(_shares[account] > 0, "PaymentSplitter: account has no shares");

        uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
        uint256 payment = _pendingPayment(account, totalReceived, released(token, account));

        require(payment != 0, "PaymentSplitter: account is not due payment");

        _erc20Released[token][account] += payment;
        _erc20TotalReleased[token] += payment;

        SafeERC20.safeTransfer(token, account, payment);
        emit ERC20PaymentReleased(token, account, payment);
    }

Finally, we have the ERC-20 withdrawal. This is very similar to the Ether withdrawal, with a few differences. It takes an additional argument, which is the token to withdraw from. Note that this is treated as an ERC-20 Interface object, so we can access ERC-20 functions from it. When the totalReceived variable is being calculated, we call the balanceOf function to determine the contract’s balance of the ERC-20 token, versus using the address.balance form we used to retrieve the Ether balance.

The erc20Released[token][account] += payment; line deserves a quick mention. The brackets are being used to reference the specific key of the mapping. Since this is a nested mapping, two keys are needed, and this is why we see two bracketed values.

Finally, the Ether sending function is replaced with an ERC-20 transfer call, using the SafeERC20 library to perform the call.

Now we have the whole suite of interactions!

Wrapping Up

Congratulations! You just walked through an entire smart contract. That’s not a trivial accomplishment, so take a moment to reflect and revisit any points that were confusing. At a high level, we determined that this contract:

  • Is designed to receive Ether and ERC-20 tokens by direct transfer
  • Accepts a list of users and their proportionate shares at construction, which cannot be changed after construction
  • Proportionately transfers out funds to its users according to their shares
  • Transfers out funds via two different functions, depending on if the funds are Ether or ERC-20
  • Exposes some of its variables via getter functions so others can retrieve their values

I want to finish up by looking at the process we used to walk through this contract.

  1. Read the primary comments (if any) to gain a holistic sense of what the contract does
  2. Examine the state variables to see what’s important enough to this application that it has to be permanently tracked on the blockchain
  3. Figure out what the constructor does
  4. Identify getter functions and anything else that you can safely ignore
  5. Determine how money comes into the splitter
  6. Determine how money exits the splitter

This general approach is how I’d tackle any contract analysis. Abstracting it slightly:

  1. Read the comments and any high level documentation available from the protocol
  2. Examine the state variables
  3. Quick glance at constructor
  4. Identify getters and other pieces of code that can be temporarily ignored
  5. Identify any functions which take in funds or interact with other contracts
  6. Identify any functions which transfer funds out
  7. Work through the exposed functions, tracing their logic through internal calls and prioritizing functions which take in or transfer out assets
  8. Revisit constructor if anything was unclear earlier

You shouldn’t have to become a smart contract auditor to not lose your shirt in DeFi. Nor should you be required to become a software developer to conduct diligent investment analysis. I’ve also written another smart contract comprehension article for non-coders, on 5 Hazards To Watch For In DeFi. Check that out to put your new knowledge to use finding hazardous patterns!

I hope this article helps laymen gain confidence in scanning smart contracts. To that end, I would love feedback to help make any future articles better. Please DM me on twitter @bowtiedpickle if you have questions, comments, or input!

Photo of author

Written By BowTiedPickle

Anonymous cartoon pickle inspired by BowTiedBull. Degen chemical engineer, moonlighting as a Solidity developer.

Disclosure

This article may contain links to third-party websites or other content for information purposes. BowTiedIsland may receive a commission at no cost to you if you purchase a product after clicking one of these links. The Third-Party Sites are not under the control of BowTiedIsland, and BowTiedIsland is not responsible for the content of any Third-Party Site. All information contained herein is the opinion of the writer and does not constitute financial advice. We aim to act as a neutral third party and aid in your research and analysis.


The Jungle


Crypto, Investing, and E-Commerce with BowTied Bull

The future is internet based, therefore we have a triangle based approach with crypto, e-commerce business making and Investing in traditional assets

The Culture War with BowTiedRanger

Whether you’re a political junkie or just interested in current events. 

You’ve come to the right place for analysis of the most relevant current events and political issues.

Fitness With BowTiedOx

BowTiedOx provides you a place to find all of his latest programs and guides.

Weekly newsletters that cover fitness, health, and mindset, all grounded in the fundamentals of physiology.

Media Production with BowTied Turkey and BowTied Tamarin

Video is no longer optional.

Don’t get left behind.

Your brand deserves professional videos to engage your audience.

Art & Graphic Design with BowTied Patriot

BowTied Patriot is a graphic artist who specializes in photography, mixed medium custom artwork, and NFT creation.

Join BowTiedPatriot as he dives into making Art in Web3.0 and The Metaverse.

Cooking with BowTiedOctopod

Learn secrets from a fine dining chef for maximum flavor and time-saving efficiency

Newsletters on Ingredients, Techniques and Flavor hacks that will have you eating better. We will never eat bugs!

Meme Warfare with DgenFren

Increase your online engagement, organically influence narratives, and build your online persona by using marketing that your target audience actually wants: memes.

Learn How to Sell with BowTiedSalesGuy

Sales is one of the most transferrable life skills, yet few know how to actually sell.

Traditional sales tactics don’t cut it in today’s hyper competitive world.

Learn the secrets from a Chad Salesman and change your Life forever.

Ecommerce with BowTiedOpossum

Learn the skills to start and build your first online business.

Want to build a business that travels with you?

Learn from an industry veteran that has worked on and with brands you already know.