The Complete Guide to Full Stack Bitcoin SV Development
Tags: scrypt, smartcontract, blockchain, bitcoin
Published: 2024-03-28 17:09:25


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tm5pvyc6z2gnte17j9i.png)

In this guide, you’ll learn a web3 tech stack that will allow you to build full stack decentralized apps on the Bitcoin SV blockchain. We will walk through the entire process of building a full stack decentralized Tic-Tac-Toe, including:

- Write a smart contract.
- Deploy the contract
- Add a front-end (React)
- Integrate Yours wallet

By the end, you will have a fully functional [Tic-Tac-Toe](https://scrypt.io/tic-tac-toe/) App running on Bitcoin.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thk87mdca6vvokpxxp7k.png)

**What we will use**

Let’s go over the main pieces we will be using and how they fit into the stack.

**1 - sCrypt Framework**

sCrypt is a TypeScript framework to develop smart contracts on Bitcoin. It offers a complete tech stack:

- [the sCrypt language](https://docs.scrypt.io/): an embedded 
  Domain Specific Language 
  (eDSL) based on TypeScript, which allows developers to write 
  smart contracts directly in TypeScript. Developers don’t have 
  to learn a new Web3 programming language like Solidity, and can 
  reuse their favorite tools, like IDEs and NPM.

- [library (scrypt-ts)](https://www.npmjs.com/package/scrypt-ts): 
  a comprehensive and concise library 
  designed for client-side JavaScript applications, such as 
  React, Vue, Angular, or Svelte, to interact with the Bitcoin SV 
  Blockchain and its ecosystem.

- [sCrypt CLI](https://github.com/sCrypt-Inc/scrypt-cli): CLI to 
  easily create, compile and publish sCrypt 
  projects. The CLI provides best practice project scaffolding.


**2. Yours Wallet**

Yours Wallet is an open-source digital wallet for BSV and [1Sat Ordinals](https://docs.1satordinals.com/) that enables access to decentralized applications developed on [Bitcoin SV](https://bitcoinsv.com/). Yours Wallet generates and manages private keys for its users in a non-custodial manner, ensuring that the users have full control over their funds and transactions. These keys can be utilized within the wallet to securely store funds and authorize transactions.


**3. React**

React.js, often simply referred to as React, is a JavaScript library developed by Facebook. It’s primarily used for building user interfaces (UIs) for web applications. It simplifies the process of building dynamic and interactive web applications and continues to be seemingly dominating the front-end space.


**What we will Build**

We will build a very simple Tic-Tac-Toe game on chain. It uses the Bitcoin addresses of two players (Alice and Bob respectively) to initialize a smart contract. They each bet the same amount and lock it into the contract. The winner takes all bitcoins locked in the contract. If no one wins and there is a draw, the two players can each withdraw half of the money. Tic-Tac-Toe, the age-old game of strategy and skill, has now found its way onto the blockchain thanks to the power of sCrypt.


**Prerequisites**

1 - Install [node.js](https://nodejs.org/en/download) and npm (node.js ≥ version 16)
2 - Install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
3 - [Yours wallet](https://chromewebstore.google.com/detail/yours-wallet/mlbnicldlpdimbjdcncnklfempedeipj) Chrome extension installed in your browser
4 - Install sCrypt CLI
`npm install -g scrypt-cli`


**Getting Started**

Let’s simply create a new React project.

Firstly let’s create a new React project with TypeScript template.

`npx create-react-app tic-tac-toe --template typescript`

Then, change the directory to the tic-tac-toe project directory and also run the `init` command of the [CLI ](https://docs.scrypt.io/installation#the-scrypt-cli-tool)to add `sCrypt` support in your project.

`cd tic-tac-toe
npx scrypt-cli@latest init`

**Tic-tac-toe Contract**
Next, let’s create a contract at `src/contracts/tictactoe.ts`:

```
import {
    prop, method, SmartContract, PubKey, FixedArray, assert, Sig, Utils, toByteString, hash160,
    hash256,
    fill,
    ContractTransaction,
    MethodCallOptions,
    bsv
} from "scrypt-ts";

export class TicTacToe extends SmartContract {
    @prop()
    alice: PubKey;
    @prop()
    bob: PubKey;
    @prop(true)
    isAliceTurn: boolean;
    @prop(true)
    board: FixedArray<bigint, 9>;
    static readonly EMPTY: bigint = 0n;
    static readonly ALICE: bigint = 1n;
    static readonly BOB: bigint = 2n;
    
    constructor(alice: PubKey, bob: PubKey) {
        super(...arguments)
        this.alice = alice;
        this.bob = bob;
        this.isAliceTurn = true;
        this.board = fill(TicTacToe.EMPTY, 9);
    }
    
    @method()
    public move(n: bigint, sig: Sig) {
        // check position `n`
        assert(n >= 0n && n < 9n);
        // check signature `sig`
        let player: PubKey = this.isAliceTurn ? this.alice : this.bob;
        assert(this.checkSig(sig, player), `checkSig failed, pubkey: ${player}`);
        // update stateful properties to make the move
        assert(this.board[Number(n)] === TicTacToe.EMPTY, `board at position ${n} is not empty: ${this.board[Number(n)]}`);
        let play = this.isAliceTurn ? TicTacToe.ALICE : TicTacToe.BOB;
        this.board[Number(n)] = play;
        this.isAliceTurn = !this.isAliceTurn;
        
        // build the transation outputs
        let outputs = toByteString('');
        if (this.won(play)) {
            outputs = Utils.buildPublicKeyHashOutput(hash160(player), this.ctx.utxo.value);
        }
        else if (this.full()) {
            const halfAmount = this.ctx.utxo.value / 2n;
            const aliceOutput = Utils.buildPublicKeyHashOutput(hash160(this.alice), halfAmount);
            const bobOutput = Utils.buildPublicKeyHashOutput(hash160(this.bob), halfAmount);
            outputs = aliceOutput + bobOutput;
        }
        else {
            // build a output that contains latest contract state.
            outputs = this.buildStateOutput(this.ctx.utxo.value);
        }
        if (this.changeAmount > 0n) {
            outputs += this.buildChangeOutput();
        }
        // make sure the transaction contains the expected outputs built above
        assert(this.ctx.hashOutputs === hash256(outputs), "check hashOutputs failed");
    }
    
    @method()
    won(play: bigint): boolean {
        let lines: FixedArray<FixedArray<bigint, 3>, 8> = [
            [0n, 1n, 2n],
            [3n, 4n, 5n],
            [6n, 7n, 8n],
            [0n, 3n, 6n],
            [1n, 4n, 7n],
            [2n, 5n, 8n],
            [0n, 4n, 8n],
            [2n, 4n, 6n]
        ];
        let anyLine = false;
        for (let i = 0; i < 8; i++) {
            let line = true;
            for (let j = 0; j < 3; j++) {
                line = line && this.board[Number(lines[i][j])] === play;
            }
            anyLine = anyLine || line;
        }
        return anyLine;
    }
    
    @method()
    full(): boolean {
        let full = true;
        for (let i = 0; i < 9; i++) {
            full = full && this.board[i] !== TicTacToe.EMPTY;
        }
        return full;
    }

}
```

**Properties**
The Tic-Tac-Toe contract features several essential properties that define its functionality:

1 - **Alice and Bob**: Public keys of the two players.
2 - **is_alice_turn**: A boolean flag indicating whose turn it is 
     to play.
3 - **board**: A representation of the game board, stored as a 
     fixed-size array.
4 - **Constants**: Three static properties defining game symbols 
    and empty squares.

**Constructor**

```
constructor(alice: PubKey, bob: PubKey) {
        super(...arguments)
        this.alice = alice;
        this.bob = bob;
        this.isAliceTurn = true;
        this.board = fill(TicTacToe.EMPTY, 9);
    }
```

Upon deployment, the constructor initializes the contract with the public keys of Alice and Bob. Additionally, it sets up an empty game board to kickstart the game play.

**Public methods**
Each contract must have at least one public `@method`. It is denoted with the `public` modifier and does not return any value. It is visible outside the contract and acts as the main method into the contract (like `main` in C and Java).

The public method in the contract is `move()`, which allows players to make their moves on the board. This method validates the moves, checks the player's signature, updates the game state, and determines the outcome of the game.

**Signature verification**

Once the game contract is deployed, anyone can view and potentially interact with it. We need an authentication mechanism to ensure only the desired player can update the contract if it’s their turn. This is achieved using ditigal signatures.

Only the authorized player can make a move during their turn, validated through their respective public key stored in the contract.

```
// check signature `sig`
let player: PubKey = this.isAliceTurn ? this.alice : this.bob;
assert(this.checkSig(sig, player), `checkSig failed, pubkey: ${player}`);
```
**Non-Public methods**

The contract includes two non-public methods, `won()` and `full()`, responsible for determining whether a player has won the game and if the board is full, leading to a draw.

**Tx Builder: buildTxForMove**

Bitcoin transaction can have multiple inputs and outputs. We need to build a transaction when calling a contract.

Here, we have implement a [customize transaction](https://docs.scrypt.io/how-to-deploy-and-call-a-contract/how-to-customize-a-contract-tx) builder for the move() method as below:

```
static buildTxForMove(
    current: TicTacToe,
    options: MethodCallOptions<TicTacToe>,
    n: bigint
  ): Promise<ContractTransaction> {
    const play = current.isAliceTurn ? TicTacToe.ALICE : TicTacToe.BOB;
    const nextInstance = current.next();
    nextInstance.board[Number(n)] = play;
    nextInstance.isAliceTurn = !current.isAliceTurn;
    const unsignedTx: bsv.Transaction = new bsv.Transaction().addInput(
      current.buildContractInput(options.fromUTXO)
    );
    if (nextInstance.won(play)) {
      const script = Utils.buildPublicKeyHashScript(
        hash160(current.isAliceTurn ? current.alice : current.bob)
      );
      unsignedTx.addOutput(
        new bsv.Transaction.Output({
          script: bsv.Script.fromHex(script),
          satoshis: current.balance,
        })
      );
      if (options.changeAddress) {
        unsignedTx.change(options.changeAddress);
      }
      return Promise.resolve({
        tx: unsignedTx,
        atInputIndex: 0,
        nexts: [],
      });
    }
    if (nextInstance.full()) {
      const halfAmount = current.balance / 2;
      unsignedTx
        .addOutput(
          new bsv.Transaction.Output({
            script: bsv.Script.fromHex(
              Utils.buildPublicKeyHashScript(hash160(current.alice))
            ),
            satoshis: halfAmount,
          })
        )
        .addOutput(
          new bsv.Transaction.Output({
            script: bsv.Script.fromHex(
              Utils.buildPublicKeyHashScript(hash160(current.bob))
            ),
            satoshis: halfAmount,
          })
        );
      if (options.changeAddress) {
        unsignedTx.change(options.changeAddress);
      }
      return Promise.resolve({
        tx: unsignedTx,
        atInputIndex: 0,
        nexts: [],
      });
    }
    unsignedTx.setOutput(0, () => {
      return new bsv.Transaction.Output({
        script: nextInstance.lockingScript,
        satoshis: current.balance,
      });
    });
    if (options.changeAddress) {
      unsignedTx.change(options.changeAddress);
    }
    const nexts = [
      {
        instance: nextInstance,
        atOutputIndex: 0,
        balance: current.balance,
      },
    ];
    return Promise.resolve({
      tx: unsignedTx,
      atInputIndex: 0,
      nexts,
      next: nexts[0],
    });
  }
```

**Integrate Front-end (React)**

After we have written/tested our contract, we can integrate it with front-end so that users can play our game.

First, let’s compile the contract and get the contract artifact json file by running the command below :

`npx scrypt-cli@latest compile`


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eokg204zc8uyse41z7tw.png)

You should see an artifact file `tictactoe.json` in the `artifacts` directory. It can be used to initialize a contract at the front end.

```
import { TicTacToe } from './contracts/tictactoe';
import artifact from '../artifacts/tictactoe.json';

TicTacToe.loadArtifact(artifact);
```

**Install and Fund Wallet**

Before deploying a contract, we need to connect a wallet first. We use [Yours Wallet](https://chromewebstore.google.com/detail/panda-wallet/mlbnicldlpdimbjdcncnklfempedeipj), a MetaMask-like wallet.

After installing the wallet, click the `settings` button in the upper right corner to switch to testnet. Then copy your wallet address and go to our [faucet ](https://scrypt.io/#faucet)to fund it.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wofo7au1rjo5w4819fdi.png)

**Connect to wallet**

We call `requestAuth()` to request to connect to the wallet. If the request is approved by the user, we now have full access to the wallet. We can, for example, call `getDefaultPubKey()` to get its public key.

```
const walletLogin = async () => {
    try {
      const provider = new DefaultProvider({
          network: bsv.Networks.testnet
      });
      const signer = new PandaSigner(provider);
      signerRef.current = signer;
      
      const { isAuthenticated, error } = await signer.requestAuth()
      if (!isAuthenticated) {
        throw new Error(error)
      }
      setConnected(true);
      const alicPubkey = await signer.getDefaultPubKey();
      setAlicePubkey(toHex(alicPubkey))
      // Prompt user to switch accounts
    } catch (error) {
      console.error("pandaLogin failed", error);
      alert("pandaLogin failed")
    }
};
```

**Initialize the contract**

We have obtained the contract class `Tictactoe` by loading the contract artifact file. When a user clicks the start button, the contract is initialized with the public keys of two players `alice` and `bob`. The public key can be obtained through calling `getDefaultPubKey()` of `Signer`.

The following code initializes the contract.

```
const [alicePubkey, setAlicePubkey] = useState("");
const [bobPubkey, setBobPubkey] = useState("");
...
const startGame = async (amount: number) => {
  try {
    const signer = signerRef.current as PandaSigner;
    const instance = new TicTacToe(
        PubKey(toHex(alicePubkey)),
        PubKey(toHex(bobPubkey))
      );
    await instance.connect(signer);

  } catch(e) {
    console.error('deploy TicTacToe failes', e)
    alert('deploy TicTacToe failes')
  }
};
```

**Call the contract**

Now we can start playing the game. Every move is a call to the contract and triggers a change in the state of the contract.

```
const { tx: callTx } = await p2pkh.methods.unlock(
    (sigResponses: SignatureResponse[]) => findSig(sigResponses, $publickey),
    $publickey,
    {
        pubKeyOrAddrToSign: $publickey.toAddress()
    } as MethodCallOptions<P2PKH>
);
```
After finishing with the front-end you can simply run :
`npm start`

You can now view it at `http://localhost:3000/` in your browser.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lngrx7slzrxzh7hmha0h.png)

**Conclusion**

Congratulations! You have just built your first full stack dApp on Bitcoin. Now you can play [tic-tac-toe](https://scrypt.io/tic-tac-toe/) or build your favorite game on Bitcoin. Now would be a good time to pop some champagne if you haven’t already :).

One session of play can be viewed here:

https://youtu.be/l65Ha5AmKjk

All the code can be found at [this github repo](https://github.com/sCrypt-Inc/tic-tac-toe).

By default, we deploy the contract on testnet. You can easily change it to mainnet.