by BytePitch’s Anıl Helvacı and Alexander Lastovski

Good devs are hard to find, and good crypto devs are pretty scarce. Agoric’s platform is something people can learn from scratch. At BytePitch, we can help our devs come up to speed on blockchain fundamentals, and then with the Agoric tools and SDK they can get to work quickly. 

We are a dev house of about 45 employees currently. Originally based in Portugal, BytePitch now has a presence in Turkey, the UK, Poland, Brazil, and Ukraine. Post-Covid, we are turning into a fully remote organization.

We discovered Agoric because we were looking to build smart contracts on JavaScript. We were already pretty exposed to Cosmos, but for some reason we weren’t aware of Agoric until around November of 2021. We’ve already converted two people at BytePitch who had no commercial experience with blockchain before, and now they are just delivering smart contracts on Agoric.

Prior Experience

Anıl was the primary dev on this bounty, and he didn’t have a lot of previous JavaScript experience. He had done a lot of Hyperledger projects before. He was doing B2B private network stuff with the Hyperledger Fabric, and with Hyperledger Aries, he had focused on supply chain and self-sovereign identity solutions using blockchain. So, he wasn’t a stranger to the blockchain environments, but his experience with JavaScript came down to essentially one Hyperledger Fabric project that had a Node.js and Express.js backend and used a Vue.js client. 

Everyone in the (Liquidity) Pool

The first Agoric bounty BytePitch did was for a liquidity pool, which is a pool-based decentralized lending protocol. How this happened was that we’d actually first inquired about an NFT ticket bounty on Agoric. Someone from the Agoric team explained to us that the NFT bounty had already been completed. They followed up by asking if we could try the liquidity pool one, instead, and we said yes. 

For the liquidity pool, Agoric was looking to create Compound Finance functionality. We started with deep research, reading everything we could find about Compound Finance, and we came to the conclusion that our bounty is about replacing banks’ borrowing and lending functionalities with a decentralized service. How the pool works is that people deposit their money, and then they get interest against their money. Now let’s discuss it in more technical detail.

The Highest Hurdles

The hardest part of this bounty project was the protocol math. There are complicated mathematical functions involved. Fortunately, Agoric has a solid mathematical library.

Here’s an example of the sort of problem we had to handle: Say that there is 2% annual interest on something. Then we agree we’ll charge that interest biweekly, prorated over the course of the year. We need to know how much interest per week we need to charge on things, because that also will be compounded.

Here’s a more technical question worth mentioning: Agoric has a native feature called Price Authority, which is an interface that connects you to the price oracles. That interface lets you know when you hit some price you specified earlier. We found that for the liquidity pool we needed not one but two price authorities: one for collateral and the compared currency, and also one for the debt and the compared currency. 

The term “underwater” means that the value of the collateral is less than the specified allowed limit. This can play out in a lot of different ways. If the debt of that value goes up against the compared currency, you can get liquidated. If the value of the collateral goes down against the compared currency, you can get liquidated. There is no single built-in feature to know that.

Agoric has a library called PromiseKit, which returns an object that contains one promise and also one method that you can tell that promise to resolve whenever you want. We make the user create our observer object and then we return the promise to them. Then we start listing the prices, and when there is a liquidation occurring — when the loan with the highest collateral/debt ratio gets underwater — that promise gets resolved. Then some other module does whatever it wants with that information. 

There was one related problem: sometimes two separate sources of information might arrive at once. To address that possibility, we used the generator function to keep the state and keep things clean.

Those were the two hardest parts we had to deal with: the math and the liquidation-observing aspect.

Source Code

The method below belongs to a module that we rely on to let us know when the loan with the highest Collateral/Debt ratio is underwater. You can find the full code for this module at liquidationObserver.js.

By “underwater,” we mean that the value of the debt exceeded the allowed limit. The term quote means an object that contains an output amount in exchange for an input amount.

/**

   * @template T

   * @param {CheckLiquidationOptions<T>} options

   * @return {Generator<{state: string}, {state: string}, *>}

   */

  function* checkLiquidation({ colQuote, debtQuote, liqPromiseKit, loan }) {

    let colLatestQuote = colQuote;

    let debtLatestQuote = debtQuote;

    let state = "initial"

    const { brand: collateralUnderlyingBrand } = getAmountIn(colLatestQuote);

    let updates = {};

    while (true) {

      updates = yield {state};

      colLatestQuote = updates.colQuote && updates.colQuote !== undefined ? updates.colQuote : colLatestQuote;

      debtLatestQuote = updates.debtQuote && updates.debtQuote !== undefined ? updates.debtQuote : debtLatestQuote;

      tracer('Quotes & Updates', {

        colQuoteOut: getAmountOut(colLatestQuote),

        debtQuoteOut: getAmountOut(debtLatestQuote),

        updates

      });

      const collateral = loan.getCollateralAmount();

      const debt = loan.getCurrentDebt();

      const colValInCompare = getValInCompareCurrency(collateral, colLatestQuote,

        collateralUnderlyingBrand, collateralUnderlyingDecimals, getExchangeRateForPool(collateralUnderlyingBrand));

      const debtValInCompare = getValInCompareCurrency(debt, debtLatestQuote,

        debt.brand, debtDecimals, undefined);

      const colToDebtRatio = makeRatioFromAmounts(colValInCompare, debtValInCompare);

      if (ratioGTE(liquidationMargin, colToDebtRatio)) {

        liqPromiseKit.resolve({colLatestQuote, debtLatestQuote, loan});

        return { state: 'Liquidating' };

      }

      state = 'Active';

    }

  }

The method checkLiquidation is a generator function that keeps the state for latest debt and collateral prices. When there is an update to any one of the prices, this function;

  1. Takes over the control flow
  2. Check if the price update causes a liquidation
  • If the price does cause a liquidation, this resolves the promise liqPromiseKit with the exact prices that caused the liquidation, and then gives up the control flow for good.
  • If the price doesn’t cause a liquidation, this simply does nothing and gives up the control flow again. This loop goes on until a price update does cause a liquidation.

A promiseKit is a special kind of object that makes it easier for developers who are creating and handing out promises. It is maintained under @endo/promise-kit. A promiseKit has two main properties we use:

  1. promiseKit.promise: The promise we hand to the consumer.
  2. promiseKit.resolve: The method we use when we want to resolve the promise.

We use a promiseKit here because we want the consumer of this liquidationObserver to wait until the there’s an underwater loan.

What’s Next

We’ve done three bounties with Agoric — this liquidity pool one, and also IBC Liquidity Mining and AMM Stop Loss Bounties. We’re doing a fourth currently that expands the pool-based lending protocol discussed in this article. We’ll almost certainly do more in the future. We’re one of their main next launch partners with our own internal product. 

At the Gateway conference in Prague earlier this year, we delivered a workshop. It was me and Nuno Leite, with Anıl helping out remotely. The experience gave me a lot of insight about how open and friendly the whole Cosmos community is, and just reinforced our desire to transform our Agoric work into a long-term thing. About the same time we decided that the next crypto native startup that we’re going to build in-house, it’s going to be built in Agoric.

The next thing we’re doing is going to be an in-house crypto native startup for NFT rentals, like short term rentals for NFTs with clear utility. Imagine a student only plays a character in a game during the afternoon. The student can set up the system so that during morning hours and at night, people just can rent it. We have a lot of ideas to apply to this, but for now it’s like a fully collateralized protocol for short-term NFT rentals with clear utility. 

We’ve also launched something called chainboard.academy that’s a bootcamp for developers who want to jump from Web2 to Web3. Right now it’s only for Solidity but the next protocol we’ll add is probably going to be Agoric. You can take a full-stack or a backend engineer, and within 10-12 intense weeks they can become smart contract developers.

Check us out at bytepitch.com and at our GitHub.