Mar 22, 2024

Building a Fully On-Chain Game on Oasis Sapphire

One key difference between off-chain and on-chain games is that on-chain games grant players actual ownership of in-game assets. Imagine playing a game where you collect trading cards and compete against players worldwide. Initially, after the game’s launch, it might not matter much that you collect cards in an account logged in with your email, with the game company storing the cards and your gameplay history in a centralized database.

Disclaimer: Playing on-chain games involves financial risks. Please be aware that all NFTs minted in the course of gameplay may potentially become worthless. Furthermore, there are no guaranteed rewards through playing. Participate with caution and consider your financial situation carefully.

Unlocking the Future: The Case for Fully On-Chain Gaming

"An onchain game is a game that completely runs on and has all of its data on a decentralized blockchain. Fully onchain games do not use centralized servers. Instead, a network of decentralized nodes handles the game’s logic, history, and ensures that players are making valid moves. Games that are onchain are, therefore, more secure, more resilient (they last as long as their blockchain is around), and are infinitely extensible by anyone." — Amitt Mahajan, Proof of Play

True Ownership for In-Game Collectibles

One key difference between off-chain and on-chain games is that on-chain games grant players actual ownership of in-game assets. Imagine playing a game where you collect trading cards and compete against players worldwide. Initially, after the game’s launch, it might not matter much that you collect cards in an account logged in with your email, with the game company storing the cards and your gameplay history in a centralized database.

However, consider a scenario where the Trading Card Game (TCG) gains popularity, and you become one of the top players, accumulating the rarest cards and defeating renowned opponents. Despite years of play, you’re still confined to an account where, in many cases, the game company’s terms and conditions prevent you from selling the account on eBay or other marketplaces. Furthermore, game upgrades could lead to the loss and irrelevance of some of your playing history.

In an on-chain game, every aspect of your gaming history is recorded on the blockchain. If you were one of the first to obtain a rare card and used it to battle numerous original players (OGs) in the early days, this history would be captured in your cards’ (NFTs) records. Your cards could gain historical significance, allowing you to sell them on any NFT marketplace of your choice. You might even sell a portion of your collection to reap rewards for years of loyal gameplay.

More Fun Through Community Creativity

Real on-chain games can be expanded by their community in a permissionless manner. Consider the example of a Trading Card Game (TCG) to understand how on-chain technology can create an impact. A TCG may feature gameplay that is programmed and launched by the company that issues the initial set of trading cards. Community members might have ideas to refine the gameplay or introduce new modes. In the case of a fully on-chain game, they can fork specific contracts, allowing players to interact with their versions rather than the official ones. Additionally, they can collect gas fees or other types of transaction fees for offering their gameplay enhancements. While this still necessitates licenses from the game creators to permit such modifications, on-chain game developers are, by default, more community-oriented than their Web2 counterparts.

Why build on Oasis Sapphire?

"Sapphire is the first and only confidential EVM that empowers Web3 with Smart Privacy — privacy that evolves with developers, users, and brands. Developers can build EVM-based on-chain dApps with smart contracts that are 100% confidential, 100% public, or anywhere in between." — Oasis Sapphire Website

The On-Chain Transparency Problem

Envision a game where two players compete in successive rounds. Each round necessitates that the players make a move, such as an attack or a defensive action. For an on-chain game, this implies that a transaction must be executed for each player. In a smart contract, the function could be simplified as follows:

contract FOCG {
    /// @∂ev Round => Player => Move
    mapping(uint256 => mapping(address => uint256)) public moves;

    function submitMove(uint256 round, uint256 move) public {
        moves[round][msg.sender] = move;
    }
}

Now, as soon as the first player submits their move, the other player will be able to see on-chain which move was submitted and could thus base their decision for the next move on the first player’s move. This might not be an issue if rounds are very short and someone tries to do this manually. However, if bots are playing, then these bots can easily adapt their decisions to what is happening on-chain.

Commit & Reveal as a Solution

To address the issue of on-chain transparency, a commit-reveal scheme can serve as a solution. Here, both players initially commit to their moves by sending an encrypted version on-chain. Once both have committed, they reveal their moves by submitting the unencrypted versions on-chain. The main drawback of this approach is that each round necessitates an additional transaction per player. Below is a code snippet illustrating an excerpt from the Polychain Monsters on-chain game’s reveal logic:

contract MatchMakerCommitExample {
  function revealMove(
        Match storage _match,
        address player,
        address move,
        bytes32 secret
    ) internal {
        // ...

        require(
            address(relevantMove.move) == address(0),
            "MatchMakerV3: already revealed"
        );

        // verify if the commit was made with the secret
        require(
            keccak256(abi.encodePacked(move, secret)) == relevantMove.commit,
            "MatchMakerV3: invalid secret"
        );

        relevantMove.move = IMoveV1(move);

        logger.log(LOG_REVEAL, player, address(move));
    }
}

Using Oasis Sapphire for the most Elegant Solution

Oasis Sapphire offers a very elegant solution for solving the on-chain transparency problem. Here, if used properly, the submission of the move can be encrypted on a network level. By using the Sapphire Paratime SDK a transaction can be sent to the network confidentially. In addition, the state of a contract can also be confidential. So once the first player has submitted their move, the other player won’t be able to look it up on-chain. Let’s see how that looks in practice:

contract MatchMakerV3Confidential {

  // @dev explicitly internal to conceal revealed moves
  mapping(uint256 => Match) internal matches;

  function reveal(
    uint256 matchId,
    address move
  ) public hasMsgSender isInMatch(matchId) {
    Match storage _match = matches[matchId];

    ...

    executeMovesAndApplyEffects(_match, msg.sender, move);

    ...
  }
  
  function getMatchById(uint256 id) public view returns (MatchView memory) {
    Match memory _match = matches[id];

    // If only one move is revealed, hide it
    address currentMove1 = address(_match.currentChallengerMove.move);
    address currentMove2 = address(_match.currentOpponentMove.move);
    if (currentMove1 != address(0) && currentMove2 == address(0)) {
      _match.currentChallengerMove.move = IMoveV1(address(0));
    } else if (currentMove1 == address(0) && currentMove2 != address(0)) {
      _match.currentOpponentMove.move = IMoveV1(address(0));
    }

    return
      MatchView(
        ...
      );
    }
  }  
}

First, it is important to note that all matches are now stored in an internal variable, which makes it impossible to access the match information from the contract state externally. To access the match-related information, one must use the getMatchById function, which will not return information about the opponent’s move unless both players have submitted their moves. In addition, the commit function has been completely dropped, and the reveal function has been simplified.

Transaction Cost & Speed on Sapphire

In addition to the changes made to the smart contracts to utilize Oasis Sapphire, it is important to check whether the transaction fee is low enough to support fully on-chain games. For instance, consider this transaction, which involves a complex, confidential player move. At the time of writing, the transaction fee is 0.21 ROSE, equivalent to approximately 4 cents. It is assumed that the contracts can still be verified, potentially saving 90% of the gas costs and reducing the fees to below 1 cent. The main reason for the initial high costs is that they were built specifically to support a custom L3.

Additionally, Sapphire generates a block approximately every 5 to 6 seconds, enabling the possibility of a game where players submit moves roughly every 1 to 2 minutes.

PWA: A Mobile Frontend with Social Login

For crypto games, it’s difficult to get them into app stores like the Apple Store or the Epic Games Store. And even if successful, a significant portion of potential revenues goes to the storefronts. For instance, if you wish to sell NFTs through the App Store, Apple will charge a 30% fee. Additionally, having an in-game marketplace for those NFTs is impractical.

That’s why PWAs (Progressive Web Apps) have become very popular for crypto games (and crypto apps in general) recently. A PWA is a mobile website that you can add to your homescreen, which then gets its own app icon and can receive push notifications. The performance may not be as good as that of a native app, but it’s considered a fair trade-off.

To facilitate the easiest possible wallet connection in a PWA, a tool like Privy can be used. Privy supports various login methods, such as email, phone, or social, and will generate a secure embedded wallet for the user. The screenshot below shows the Privy login form.

Sapphire PWA Template

To make the Sapphire game’s PWA compatible with confidential features, some adjustments need to be made in the frontend code. Most importantly, certain smart contract calls must be submitted through a specific provider. The code below demonstrates the integration of this provider when using React + Privy.

import { sapphireWrapProvider } from "./sapphire";

const configureChainsConfig = configureChains(
  [activeChain],
  [sapphireWrapProvider(publicProvider())]
);

function App() {
  
  return (
    ...
      <PrivyProvider
        appId={TEST_PRIVY_ID}
        config={privyClientConfig}
      >
        <PrivyWagmiConnector wagmiChainsConfig={configureChainsConfig}>
          ...
        </PrivyWagmiConnector>  
      </PrivyProvider>  
    ...  
  );
}

In addition to wrapping the provider, writing smart contract calls must be encrypted. To make this as easy as possible, a complete template repository can be found here which allows you to get started with Sapphire in a matter of minutes. The logic for encrypting the smart contract calls can be found here.

The First OCB Tournament on Sapphire

We will soon provide dates and prizes for the first on-chain battle tournament on Oasis Sapphire. Make sure to follow both Oasis and Polychain Monsters on Twitter.

About Polychain Monsters

Polychain Monsters is an enchanting cross-chain realm pulsing with dynamic, gamified collectibles — fueled by PMON. Here, you’re not merely a collector but a brave adventurer forging bonds with Polymon — your metaverse-ready frens bursting with personality. With stunning designs and lifelike animations, Polychain Monsters bring a whole new dimension to the world of NFTs.


Web | Discord | Twitter | LinkTree