This submit was first revealed on Medium.
On this information, you’ll study a Web3 tech stack that can help you construct full stack decentralized apps on the Bitcoin SV blockchain. We are going to stroll by way of the whole means of constructing a full stack decentralized Tic-Tac-Toe, together with:
- Write a sensible contract.
- Deploy the contract
- Add a front-end (React)
- Combine Yours pockets
By the top, you should have a completely useful Tic-Tac-Toe App operating on Bitcoin.
What we are going to use
Let’s go over the principle items we can be utilizing and the way they match into the stack.
1. sCrypt Framework
sCrypt is a TypeScript framework to develop sensible contracts on Bitcoin. It gives an entire tech stack:
- the sCrypt language: an embedded Area Particular Language (eDSL) primarily based on TypeScript, which permits builders to jot down sensible contracts immediately in TypeScript. Builders don’t should study a brand new Web3 programming language like Solidity, and may reuse their favourite instruments, like IDEs and NPM.
- library (scrypt-ts): a complete and concise library designed for client-side JavaScript functions, reminiscent of React, Vue, Angular, or Svelte, to work together with the Bitcoin SV Blockchain and its ecosystem.
- sCrypt CLI: CLI to simply create, compile and publish sCrypt initiatives. The CLI supplies finest apply mission scaffolding.
2. Yours Pockets
Yours Pockets is an open-source digital wallet for BSV and 1Sat Ordinals that permits entry to decentralized functions developed on Bitcoin SV. Yours Pockets generates and manages personal keys for its customers in a non-custodial method, guaranteeing that the customers have full management over their funds and transactions. These keys could be utilized throughout the pockets to securely retailer funds and authorize transactions.
3. React
React.js, typically merely known as React, is a JavaScript library developed by Fb. It’s primarily used for constructing person interfaces (UIs) for net functions. It simplifies the method of constructing dynamic and interactive net functions and continues to be seemingly dominating the front-end house.
What we are going to Construct
We are going to construct a quite simple Tic-Tac-Toe recreation on chain. It makes use of the Bitcoin addresses of two gamers (Alice and Bob respectively) to initialize a sensible contract. They every wager the identical quantity and lock it into the contract. The winner takes all bitcoins locked within the contract. If nobody wins and there’s a draw, the 2 gamers can every withdraw half of the cash. Tic-Tac-Toe, the age-old recreation of technique and ability, has now discovered its manner onto the blockchain because of the ability of sCrypt.
Stipulations
- Set up node.js and npm (node.js ≥ model 16)
- Set up Git
- Yours wallet Chrome extension put in in your browser
- Set up sCrypt CLI
npm set up -g scrypt-cli
Getting Began
Let’s merely create a brand new React mission.
Firstly let’s create a brand new React mission with TypeScript template.
npx create-react-app tic-tac-toe --template typescript
Then, change the listing to the tic-tac-toe mission listing and in addition run the init command of the CLI so as to add sCrypt help in your mission.
cd tic-tac-toe npx scrypt-cli@newest init
Tic-tac-toe Contract
Subsequent, let’s create a contract at src/contracts/tictactoe.ts:
import { prop, technique, 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) { tremendous(...arguments) this.alice = alice; this.bob = bob; this.isAliceTurn = true; this.board = fill(TicTacToe.EMPTY, 9); } @technique() public transfer(n: bigint, sig: Sig) { // test place `n` assert(n >= 0n && n < 9n); // test signature `sig` let participant: PubKey = this.isAliceTurn ? this.alice : this.bob; assert(this.checkSig(sig, participant), `checkSig failed, pubkey: ${participant}`); // replace stateful properties to make the transfer assert(this.board[Number(n)] === TicTacToe.EMPTY, `board at place ${n} is just not empty: ${this.board[Number(n)]}`); let play = this.isAliceTurn ? TicTacToe.ALICE : TicTacToe.BOB; this.board[Number(n)] = play; this.isAliceTurn = !this.isAliceTurn; // construct the transation outputs let outputs = toByteString(''); if (this.received(play)) { outputs = Utils.buildPublicKeyHashOutput(hash160(participant), this.ctx.utxo.worth); } else if (this.full()) { const halfAmount = this.ctx.utxo.worth / 2n; const aliceOutput = Utils.buildPublicKeyHashOutput(hash160(this.alice), halfAmount); const bobOutput = Utils.buildPublicKeyHashOutput(hash160(this.bob), halfAmount); outputs = aliceOutput + bobOutput; } else { // construct a output that comprises newest contract state. outputs = this.buildStateOutput(this.ctx.utxo.worth); } if (this.changeAmount > 0n) { outputs += this.buildChangeOutput(); } // be certain the transaction comprises the anticipated outputs constructed above assert(this.ctx.hashOutputs === hash256(outputs), "test hashOutputs failed"); } @technique() received(play: bigint): boolean { let strains: 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; } @technique() 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 a number of important properties that outline its performance:
- Alice and Bob: Public keys of the 2 gamers.
- is_alice_turn: A boolean flag indicating whose flip it’s to play.
- board: A illustration of the sport board, saved as a fixed-size array.
- Constants: Three static properties defining recreation symbols and empty squares.
Constructor
constructor(alice: PubKey, bob: PubKey) { tremendous(...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 general public keys of Alice and Bob. Moreover, it units up an empty recreation board to kickstart the sport play.
Public strategies
Every contract will need to have not less than one public @technique. It’s denoted with the public modifier and doesn’t return any worth. It’s seen exterior the contract and acts as the principle technique into the contract (like major in C and Java).
The general public technique within the contract is transfer(), which permits gamers to make their strikes on the board. This technique validates the strikes, checks the participant’s signature, updates the sport state, and determines the result of the sport.
Signature verification
As soon as the sport contract is deployed, anybody can view and doubtlessly work together with it. We want an authentication mechanism to make sure solely the specified participant can replace the contract if it’s their flip. That is achieved utilizing digital signatures.
Solely the licensed participant could make a transfer throughout their flip, validated by way of their respective public key saved within the contract.
// test signature `sig` let participant: PubKey = this.isAliceTurn ? this.alice : this.bob; assert(this.checkSig(sig, participant), `checkSig failed, pubkey: ${participant}`);
Non-Public strategies
The contract contains two personal strategies, received() and full(), answerable for figuring out whether or not a participant has received the sport and if the board is full, resulting in a draw.
Tx Builder: buildTxForMove
Bitcoin transaction can have a number of inputs and outputs. We have to construct a transaction when calling a contract.
Right here, we’ve got implement a customize transaction builder for the transfer() technique as beneath:
static buildTxForMove( present: TicTacToe, choices: MethodCallOptions<TicTacToe>, n: bigint ): Promise<ContractTransaction> { const play = present.isAliceTurn ? TicTacToe.ALICE : TicTacToe.BOB; const nextInstance = present.subsequent(); nextInstance.board[Number(n)] = play; nextInstance.isAliceTurn = !present.isAliceTurn; const unsignedTx: bsv.Transaction = new bsv.Transaction().addInput( present.buildContractInput(choices.fromUTXO) ); if (nextInstance.received(play)) { const script = Utils.buildPublicKeyHashScript( hash160(present.isAliceTurn ? present.alice : present.bob) ); unsignedTx.addOutput( new bsv.Transaction.Output({ script: bsv.Script.fromHex(script), satoshis: present.stability, }) ); if (choices.changeAddress) { unsignedTx.change(choices.changeAddress); } return Promise.resolve({ tx: unsignedTx, atInputIndex: 0, nexts: [], }); } if (nextInstance.full()) { const halfAmount = present.stability / 2; unsignedTx .addOutput( new bsv.Transaction.Output({ script: bsv.Script.fromHex( Utils.buildPublicKeyHashScript(hash160(present.alice)) ), satoshis: halfAmount, }) ) .addOutput( new bsv.Transaction.Output({ script: bsv.Script.fromHex( Utils.buildPublicKeyHashScript(hash160(present.bob)) ), satoshis: halfAmount, }) ); if (choices.changeAddress) { unsignedTx.change(choices.changeAddress); } return Promise.resolve({ tx: unsignedTx, atInputIndex: 0, nexts: [], }); } unsignedTx.setOutput(0, () => { return new bsv.Transaction.Output({ script: nextInstance.lockingScript, satoshis: present.stability, }); }); if (choices.changeAddress) { unsignedTx.change(choices.changeAddress); } const nexts = [ { instance: nextInstance, atOutputIndex: 0, balance: current.balance, }, ]; return Promise.resolve({ tx: unsignedTx, atInputIndex: 0, nexts, subsequent: nexts[0], }); }
Combine Entrance-end (React)
After we’ve got written/examined our contract, we will combine it with front-end in order that customers can play our recreation.
First, let’s compile the contract and get the contract artifact json file by operating the command beneath:
npx scrypt-cli@newest compile
It’s best to see an artifact file tictactoe.json within the artifacts listing. It may be used to initialize a contract on the entrance finish.
import { TicTacToe } from './contracts/tictactoe'; import artifact from '../artifacts/tictactoe.json'; TicTacToe.loadArtifact(artifact);
Set up and Fund Pockets
Earlier than deploying a contract, we have to join a pockets first. We use Yours Wallet, a MetaMask-like pockets.
After putting in the pockets, click on the settings button within the higher proper nook to change to testnet. Then copy your pockets deal with and go to our faucet to fund it.
Connect with pockets
We name requestAuth() to request to hook up with the pockets. If the request is permitted by the person, we now have full entry to the pockets. We will, for instance, name getDefaultPubKey() to get its public key.
const walletLogin = async () => { attempt { const supplier = new DefaultProvider({ community: bsv.Networks.testnet }); const signer = new PandaSigner(supplier); signerRef.present = signer; const { isAuthenticated, error } = await signer.requestAuth() if (!isAuthenticated) { throw new Error(error) } setConnected(true); const alicPubkey = await signer.getDefaultPubKey(); setAlicePubkey(toHex(alicPubkey)) // Immediate person to change accounts } catch (error) { console.error("pandaLogin failed", error); alert("pandaLogin failed") } };
Initialize the contract
We now have obtained the contract class Tictactoe by loading the contract artifact file. When a person clicks the begin button, the contract is initialized with the general public keys of two gamers alice and bob. The general public key could be obtained by way of calling etDefaultPubKey()of Signer.
The next code initializes the contract.
const [alicePubkey, setAlicePubkey] = useState(""); const [bobPubkey, setBobPubkey] = useState(""); ... const startGame = async (quantity: quantity) => { attempt { const signer = signerRef.present as PandaSigner; const occasion = new TicTacToe( PubKey(toHex(alicePubkey)), PubKey(toHex(bobPubkey)) ); await occasion.join(signer); } catch(e) { console.error('deploy TicTacToe failes', e) alert('deploy TicTacToe failes') } };
Name the contract
Now we will begin taking part in the sport. Each transfer is a name to the contract and triggers a change within the state of the contract.
const { tx: callTx } = await p2pkh.strategies.unlock( (sigResponses: SignatureResponse[]) => findSig(sigResponses, $publickey), $publickey, { pubKeyOrAddrToSign: $publickey.toAddress() } as MethodCallOptions<P2PKH> );
After ending with the front-end you’ll be able to merely run :
npm begin
Now you can view it at `http://localhost:3000/` in your browser.
Conclusion
Congratulations! You’ve simply constructed your first full stack dApp on Bitcoin. Now you’ll be able to play tic-tac-toe or construct your favourite recreation on Bitcoin. Now can be an excellent time to pop some champagne if you happen to haven’t already :).
One session of play could be seen right here:
All of the code could be discovered at this github repo.
By default, we deploy the contract on testnet. You may simply change it to mainnet.
Watch: sCrypt Hackathon 2024 (seventeenth March 2024, PM)
New to blockchain? Take a look at CoinGeek’s Blockchain for Beginners part, the final word useful resource information to study extra about blockchain expertise.